unit dflash_cache;

//
//  The unit
//    * loads, validates and parses caches of previously loaded .swf
//    * simplifies generating of new caches
//

{$MODE FPC}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
{$MODESWITCH ADVANCEDRECORDS}

interface

uses
  dansiview,
  dansibuffer,
  dansibuffer_ansiview,
  dengcache;

const
  FLASH_CACHE_VERSION   = 5;
  //  Should be incremented after almost any change in cache loader or
  //  cache writer (here and in dflash_holder) to prevent reading old
  //  files.

type
PFlashCache = ^TFlashCache;

TFlashCache = object
public type
  PHeader = ^THeader;
  THeader = packed record
    Version: UInt32;
    Adler32: UInt32;
    Dict: UInt64; // offset to the dictionary table
    DictCount: UInt32;
  end;
  PCharacter = Pointer;
  PLayer = ^TLayer;
  TLayer = packed record
    LayerType: UInt32;
    Color: UInt32;
    Count: UInt32;
    Offset: UInt64;
  end;
  PDictItem = ^TDictItem;
  TDictItem = packed record
    CharacterId: UInt32;
    CharacterType: UInt32;
    Count: UInt32;
    Offset: UInt64;
  end;
private
  FCache: dengcache.TCache;
  FLoaded: Boolean;
  FSwfPath: AnsiString;
  FAdler: UInt32;
  FFileStart: Pointer;
  FDictCount: UInt32;
  FDict: PDictItem;
  function CheckHeader(Adler32: UInt32): Boolean;
public
  procedure Init;
  procedure Done;
  function Load(SwfPath: TAnsiView; Adler32: UInt32): Boolean;
  function Loaded: Boolean;

  function FindCharacter(CharacterId: UInt32): PCharacter;
  function CharacterType(Character: PCharacter): UInt32; inline;
  function ShapeLayersCount(Character: PCharacter): UInt32; inline;
  function ShapeLayer(Character: PCharacter; Index: SizeUInt): PLayer; inline;
  function LayerType(Layer: PLayer): UInt32; inline;
  function LayerColor(Layer: PLayer): UInt32; inline;
  function LayerCount(Layer: PLayer): UInt32; inline;
  function LayerTriangles(Layer: PLayer): Pointer; inline;
end;

TFlashCacheWriter = object
private
  FFlashCache: PFlashCache;
  FData: array of Byte;
public
  procedure Init(FlashCache: PFlashCache);
  procedure Done;
  procedure Start;
  function Finish: Boolean;

  function AllocData(Size: SizeUInt): UInt64;
  //
  //  Returns absolute offset inside file.
  //
  function AppendData(Data: Pointer; Size: SizeUInt): UInt64;
  //
  //  Returns absolute offset inside file.
  //
  procedure RewriteData(Offset: UInt64; Data: Pointer; Size: SizeUInt);
  //
  //  Rewrites data at the specified offset.
  //
end;

implementation

function TFlashCache.CheckHeader(Adler32: UInt32): Boolean;
var
  Header: PHeader;
begin
  FFileStart := FCache.Data;
  Header := FFileStart;
  if (Header^.Version <> FLASH_CACHE_VERSION) or (Header^.Adler32 <> Adler32) then
    Exit(False);
  FDict := FFileStart + Header^.Dict;
  FDictCount := Header^.DictCount;
  Exit(True);
end;

procedure TFlashCache.Init;
begin
  FCache.Init;
  FDict := nil;
  FDictCount := 0;
end;

procedure TFlashCache.Done;
begin
  FCache.Done;
end;

function TFlashCache.Load(SwfPath: TAnsiView; Adler32: UInt32): Boolean;
var
  Id: TAnsiBuffer;
begin
  FSwfPath := SwfPath.ToString;
  FAdler := Adler32;
  Id.Init;
  dengGenerateCacheId(SwfPath, 'swf', Id);
  if FCache.Load(Id) and CheckHeader(Adler32) then begin
    // TODO check Adler hash here
    FFileStart := FCache.Data;
    FLoaded := True;
  end else
    FLoaded := False;
  Id.Done;
  Exit(FLoaded);
end;

function TFlashCache.Loaded: Boolean;
begin
  Exit(FLoaded);
end;

function TFlashCache.FindCharacter(CharacterId: UInt32): PCharacter;
var
  I: PDictItem;
begin
  if FDict <> nil then begin
    I := FDict;
    while I < FDict + FDictCount do begin
      if I^.CharacterId = CharacterId then
        Exit(I);
      Inc(I);
    end;
  end;
  Exit(nil);
end;

function TFlashCache.CharacterType(Character: PCharacter): UInt32;
begin
  Exit(PDictItem(Character)^.CharacterType);
end;

function TFlashCache.ShapeLayersCount(Character: PCharacter): UInt32;
begin
  Exit(PDictItem(Character)^.Count);
end;

function TFlashCache.ShapeLayer(Character: PCharacter; Index: SizeUInt): PLayer;
begin
  Exit(FFileStart + PDictItem(Character)^.Offset + Index * SizeOf(TLayer));
end;

function TFlashCache.LayerType(Layer: PLayer): UInt32;
begin
  Exit(Layer^.LayerType);
end;

function TFlashCache.LayerColor(Layer: PLayer): UInt32;
begin
  Exit(Layer^.Color);
end;

function TFlashCache.LayerCount(Layer: PLayer): UInt32;
begin
  Exit(Layer^.Count);
end;

function TFlashCache.LayerTriangles(Layer: PLayer): Pointer;
begin
  Exit(FFileStart + Layer^.Offset);
end;

procedure TFlashCacheWriter.Init(FlashCache: PFlashCache);
begin
  FFlashCache := FlashCache;
  SetLength(FData, 0);
end;

procedure TFlashCacheWriter.Done;
begin
end;

procedure TFlashCacheWriter.Start;
begin
  SetLength(FData, 0);
end;

function TFlashCacheWriter.Finish: Boolean;
begin
  Result := FFlashCache^.FCache.Update(@FData[0], Length(FData));
end;

function TFlashCacheWriter.AllocData(Size: SizeUInt): UInt64;
begin
  Result := Length(FData);
  SetLength(FData, Length(FData) + Size);
end;

function TFlashCacheWriter.AppendData(Data: Pointer; Size: SizeUInt): UInt64;
begin
  Result := Length(FData);
  SetLength(FData, Length(FData) + Size);
  Move(Data^, FData[Result], Size);
end;

procedure TFlashCacheWriter.RewriteData(Offset: UInt64; Data: Pointer; Size: SizeUInt);
begin
  Move(Data^, FData[Offset], Size);
end;

end.
