// Unit simplifies loading tiled files
{$MODE FPC}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
{$MODESWITCH ADVANCEDRECORDS}
unit dtiled_utils;

interface

uses
  dstrnum,
  dansiview,
  dansibuffer,
  dansibuffer_ansiview,
  dtiled,
  djson,
  deng,
  dsprite;

type
PCachedTileset = ^TCachedTileset;
TCachedTileset = record
  FirstGid: LongInt;
  TileCount: LongInt;
  TileWidth: LongInt;
  TileHeight: LongInt;
  Columns: LongInt;
  FileName: TAnsiBuffer;
  Tiles: array of TTiledTileInfo;
end;

// TODO Use local TTiledMaps
// Хранит состояние загруженной Tiled карты
PTiledMap = ^TTiledMap;
TTiledMap = object
private
  FWidth: LongInt;
  FHeight: LongInt;
  FTilesets: array of TCachedTileset;
  // nil означает, что для заданного id спрайт не сгенерён
  FSprites: array of PSprite;
  // pool for objects
  FObjects: array of PJson;
  FReferenceTileset: PTiledTilesetInfo;
public
  procedure Init;
  procedure Done;
  property Width: LongInt read FWidth write FWidth;
  property Height: LongInt read FHeight write FHeight;
end;

//procedure StartTiledLoading;
//procedure FinishTiledLoading;

function FindTilesetByGid(var TiledMap: TTiledMap; Gid: Cardinal; out T: PCachedTileset; out Index: LongInt): Boolean;
    //  Находит тайлсет для заданного Gid
    //
    //  T -- найденный тайлсет, Index -- индекс тайла в тайлсете

function GenerateSprite(var TiledMap: TTiledMap;
                        const FileName: TAnsiView;
                        X, Y, W, H: LongInt;
                        FlipD, FlipH, FlipV: Boolean;
                        Tileset: PCachedTileset;
                        Tile: PTiledTileInfo): PSprite;
    //
    //  Генерирует объект-спрайт
    //

procedure GetSpriteByGid(var TiledMap: TTiledMap; const BasePath: TAnsiView; Gid: Cardinal; out Sprite: PSprite);
    //  Генерирует объект-спрайт для заданного Gid
    //
    //  Если ранее для заданного Gid уже был сгенерирован спрайт, будет взят он

function SumPaths(const Base, Path: AnsiString): AnsiString;

procedure UploadTiledTileset(var TiledMap: TTiledMap; TilesetInfo: PTiledTilesetInfo);

procedure SetReferenceTileset(var TiledMap: TTiledMap; TilesetInfo: PTiledTilesetInfo);

implementation

//var
//  TiledMap: TTiledMap;

function SumPaths(const Base, Path: AnsiString): AnsiString;
begin
  Result := Base + Path;
end;

procedure TTiledMap.Init;
begin
  SetLength(FTilesets, 0);
  SetLength(FSprites, 0);
end;

procedure TTiledMap.Done;
var
  I: LongInt;
begin
  for I := 0 to High(FTilesets) do begin
    FTilesets[I].FileName.Done;
  end;
  for I := 0 to High(FSprites) do begin
    if FSprites[I] <> nil then
      Dispose(FSprites[I], Done);
  end;
end;

//procedure StartTiledLoading;
//begin
//  TiledMap.Init;
//end;

//procedure FinishTiledLoading;
//begin
//  TiledMap.Done;
//end;

function FindTilesetByGid(var TiledMap: TTiledMap; Gid: Cardinal; out T: PCachedTileset; out Index: LongInt): Boolean;
var
  I: LongInt;
begin
  if Gid = 0 then
    Exit(False);
  for I := 0 to High(TiledMap.FTilesets) - 1 do begin
    if Gid < TiledMap.FTilesets[I + 1].FirstGid then begin
      T := @TiledMap.FTilesets[I];
      Index := Gid - T^.FirstGid;
      Exit(True);
    end;
  end;
  T := @TiledMap.FTilesets[High(TiledMap.FTilesets)];
  Index := Gid - T^.FirstGid;
  Result := Index < T^.TileCount;
end;

procedure ComputeTextureRect(Index, Columns, TileWidth, TileHeight: LongInt; out X, Y: LongInt);
begin
  X := Index mod Columns;
  Y := Index div Columns;
end;

function GenerateSprite(var TiledMap: TTiledMap;
                        const FileName: TAnsiView;
                        X, Y, W, H: LongInt;
                        FlipD, FlipH, FlipV: Boolean;
                        Tileset: PCachedTileset;
                        Tile: PTiledTileInfo): PSprite;
var
  I: LongInt;
  JsonValue: TJsonValue;
  Json: PJson;
  xywh: PJsonArray;
  frames: PJsonArray;
  frame: PJson;
begin
  //Log('GeneratingSprite: ' + FileName.ToString + ' ' + IntToStr(X) + ' ' + IntToStr(Y) + ' ' + IntToStr(W) + ' ' + IntToStr(H));
  New(Json, Init);
  Json^.Add('filename', FileName.ToString);
  New(xywh, Init);
  xywh^.Add(X);
  xywh^.Add(Y);
  xywh^.Add(W);
  xywh^.Add(H);
  Json^.Add('xywh', PJsonArray(xywh));
  if (Tileset <> nil) and (Tile <> nil) and (Length(Tile^.Anim.Frames) <> 0) then begin
    New(frames, Init);
    for I := 0 to High(Tile^.Anim.Frames) do begin
      New(frame, Init);
      New(xywh, Init);
      ComputeTextureRect(Tile^.Anim.Frames[I].TileId, Tileset^.Columns, Tileset^.TileWidth, Tileset^.TileHeight, X, Y);
      xywh^.Add(X * Tileset^.TileWidth);
      xywh^.Add(Y * Tileset^.TileHeight);
      xywh^.Add(W);
      xywh^.Add(H);
      frame^.Add('xywh', xywh);
      frame^.Add('dt', Tile^.Anim.Frames[I].Duration);
      frames^.Add(frame);
    end;
    Json^.Add('frames', frames);
  end;
  Json^.Add('flipd', Ord(FlipD));
  Json^.Add('fliph', Ord(FlipH));
  Json^.Add('flipv', Ord(FlipV));
  New(Result, Init);
  JsonValue._Type := JSON_OBJECT;
  JsonValue.O := Json;
  Result^.LoadFromJson(nil, @Eng, @JsonValue);
end;

procedure GetSpriteByGid(var TiledMap: TTiledMap; const BasePath: TAnsiView; Gid: Cardinal; out Sprite: PSprite);
var
  T: PCachedTileset;
  Old: LongInt;
  Index: LongInt;
  X, Y: LongInt;
  I: LongInt;
  Tile: PTiledTileInfo;
  Flipped: Cardinal;
begin
  // TODO check flip flags
  Flipped := Gid and TILED_FLIPPED_MASK;
  if Flipped <> 0 then begin
    Gid := Gid and not Flipped;
  end;
  if Gid > High(TiledMap.FSprites) then begin
    Old := Length(TiledMap.FSprites);
    SetLength(TiledMap.FSprites, Gid + 1);
    for Index := Old to High(TiledMap.FSprites) do
      TiledMap.FSprites[Index] := nil;
  end;
  Sprite := TiledMap.FSprites[Gid];
  if Sprite <> nil then
    Exit;
  if not FindTilesetByGid(TiledMap, Gid, T, Index) then
    Exit;
  Tile := nil;
  for I := 0 to High(T^.Tiles) do begin
    if T^.Tiles[I].Id = Index then begin
      //Log('GID: ' + IntToStr(Gid) + ' ' + IntToStr(Flipped) + ' -> ' + T^.FileName.ToString + ' ' + IntToStr(T^.FirstGid) + ' found tile');
      Tile := @T^.Tiles[I];
      break;
    end;
  end;
  X := Index mod T^.Columns;
  Y := Index div T^.Columns;
  ComputeTextureRect(Index, T^.Columns, T^.TileWidth, T^.TileHeight, X, Y);
  // TODO fix path
  // TODO free memory
  //Log('GID: ' + IntToStr(Gid) + ' ' + IntToStr(Flipped) + ' -> ' + T^.FileName.ToString + ' ' + IntToStr(T^.FirstGid) + ' ' + IntToStr(X) + ' ' + IntToStr(Y) + ' ' + IntToStr(Ord(Flipped and TILED_FLIPPED_DIAGONALLY_FLAG <> 0)) + ' ' + IntToStr(Ord(Flipped and TILED_FLIPPED_HORIZONTALLY_FLAG <> 0)) + ' ' + IntToStr(Ord(Flipped and TILED_FLIPPED_VERTICALLY_FLAG <> 0)));
  Sprite := GenerateSprite(TiledMap,
                           SumPaths(BasePath.ToString, T^.FileName.ToString),
                           X * T^.TileWidth,
                           Y * T^.TileHeight,
                           T^.TileWidth,
                           T^.TileHeight,
                           Flipped and TILED_FLIPPED_DIAGONALLY_FLAG <> 0,
                           Flipped and TILED_FLIPPED_HORIZONTALLY_FLAG <> 0,
                           Flipped and TILED_FLIPPED_VERTICALLY_FLAG <> 0,
                           T,
                           Tile);
end;

procedure UploadTiledTileset(var TiledMap: TTiledMap; TilesetInfo: PTiledTilesetInfo);
var
  T: PCachedTileset;
  I, J: LongInt;
begin
  SetLength(TiledMap.FTilesets, Length(TiledMap.FTilesets) + 1);
  T := @TiledMap.FTilesets[High(TiledMap.FTilesets)];
  T^.FirstGid := TiledMap.FReferenceTileset^.FirstGid;
  T^.TileCount := TilesetInfo^.TileCount;
  T^.TileWidth := TilesetInfo^.TileWidth;
  T^.TileHeight := TilesetInfo^.TileHeight;
  T^.Columns := TilesetInfo^.Columns;
  T^.FileName.Init(TilesetInfo^.Image.Source);
  SetLength(T^.Tiles, Length(TilesetInfo^.Tiles));
  for I := 0 to High(TilesetInfo^.Tiles) do begin
    T^.Tiles[I].Id := TilesetInfo^.Tiles[I].Id;
    SetLength(T^.Tiles[I].Anim.Frames, Length(TilesetInfo^.Tiles[I].Anim.Frames));
    for J := 0 to High(TilesetInfo^.Tiles[I].Anim.Frames) do begin
      T^.Tiles[I].Anim.Frames[J].TileId := TilesetInfo^.Tiles[I].Anim.Frames[J].TileId;
      T^.Tiles[I].Anim.Frames[J].Duration := TilesetInfo^.Tiles[I].Anim.Frames[J].Duration;
    end;
  end;
end;

procedure SetReferenceTileset(var TiledMap: TTiledMap; TilesetInfo: PTiledTilesetInfo);
begin
  TiledMap.FReferenceTileset := TilesetInfo;
end;

end.
