unit dtiled_manager;

//
//  Simplifies loading tiled files with its tilesets to RAM.
//

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

interface

uses
  dstrnum,
  dansiview,
  dansibuffer,
  dansibuffer_ansiview,
  dstream,
  dstreamfile,
  dstreammem,
  djson,
  dxml_sax,
  deng,
  dsprite,
  dtiled;

type
PTiledManager = ^TTiledManager;
TTiledManager = object
    //  Provides features for cacheing and loading several tiled files and
    //  reiterating it with the same interface as the dtiled library.
    //
    //  It keeps in RAM all tiled related files so they can be fast parsed.
public type
  PCachedTileset = ^TCachedTileset;
  TCachedTileset = object
    TileCount: LongInt;             // tiles in the tileset
    TileWidth: LongInt;             // width of a tile
    TileHeight: LongInt;            // height of a tile
    Columns: LongInt;               // number of columns
    FileName: TAnsiBuffer;          // filename of texture (not the tileset)
    Tiles: array of TTiledTileInfo; // info about tiles
    IsExternal: Boolean;            // loaded from separated file
    Path: TAnsiBuffer;              // path to the tileset (only if IsExternal)
    Data: PAnsiChar;                // loaded source (may be nil)
    DataLength: SizeUInt;           // size of loaded source (may be 0)
  end;
  PCachedTile = ^TCachedTile;
  TCachedTile = object
    Index: Cardinal;                // index of the tile inside tileset
    Flipped: Cardinal;              // flipped bits (see TILED_FLIPPED_MASK)
    Tileset: PCachedTileset;        // tileset of the tile
    Info: PTiledTileInfo;           // info about the tile (may be nil)
    Json: PJson;                    // json for generating sprite (may be nil)
    Sprite: PSprite;                // generated sprite (may be nil)
  end;
  PCachedMap = ^TCachedMap;
  TCachedMap = object
    Path: TAnsiBuffer;              // path to the file of the map
    Header: TTiledHeader;           // header of the map
    Data: PAnsiChar;                // loaded source (may be nil)
    DataLength: SizeUInt;           // size of the loaded source (may be 0)
    Tilesets: array of record       // tilesets inside the map
      FirstGid: Cardinal;           // starting Index of the tileset
      Cached: PCachedTileset;       // pointer to the tileset
    end;
    CachedTiles: array of record    // cached generated tiles
      Gid: Cardinal;                // Gid of the tile (Index+Flipped)
      Tile: PCachedTile;            // the tile
    end;
  end;
  TOnTiledObjectManager = procedure (G: PTiledObjectGroupInfo;
                         Obj: PTiledObject;
                         Props: PTiledProperties;
                         M: PCachedMap);
      // Callback to be used by client code.
      // Read Obj and Props fields/properties/methods for getting object data.
      // Use GetSpriteByGid to generating Sprite for the object.
      // G may be nil, otherwise it represents objects layer (?)
private
  FOnHeader: TOnTiledHeader;
  FOnTilesetInfo: TOnTiledTilesetInfo;
  FOnLayerStart: TOnTiledLayerStart;
  FOnLayerEnd: TOnTiledLayerEnd;
  FOnTile: TOnTiledTile;
  FOnObject: TOnTiledObjectManager;
  FOnTileObject: TOnTiledObjectManager;
  FOnImageLayer: TOnTiledImagerLayer;

  // while loading
  FTiledBasePath: TAnsiBuffer;
  FMap: PCachedMap;

  FTilesets: array of PCachedTileset;
  FMaps: array of PCachedMap;

  function FindTileset(const Path: TAnsiView; out Tileset: PCachedTileset): Boolean;
      // Searches for cached tileset from the specified Path, returns it in the
      // specified Tileset and True, False otherwise.
  function FindMap(const Path: TAnsiView; out Map: PCachedMap): Boolean;
      // Searches for cached map from the specified Path, returns it in the
      // specified Map and True, False otherwise.
  function AddExternalTileset(const Path: TAnsiView; out Tileset: PCachedTileset): Boolean;
  function AddInternalTileset(out Tileset: PCachedTileset): Boolean;
  function AddMap(const Path: TAnsiView; out Map: PCachedMap): Boolean;

  function GenerateJson(Map: PCachedMap;
                        const FileName: TAnsiView;
                        X, Y, W, H: LongInt;
                        FlipD, FlipH, FlipV: Boolean;
                        Tile: PCachedTile): PJson;
  function LoadTile(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
  function LoadTileJson(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
  function LoadTileSprite(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
  function GetCachedSprite(Map: PCachedMap; Gid: Cardinal): PtrInt; // returns index into CachedTiles or -1
public
  procedure Init;
  procedure Done;

  function LoadTileset(const Path: TAnsiView; out Cached: PCachedTileset): Boolean;
  function LoadTileset(const Path: TAnsiView): Boolean;
    // Loads and returns tileset from the specified Path.

  function LoadMap(const Path: TAnsiView; out Cached: PCachedMap): Boolean; overload;
  function LoadMap(const Path: TAnsiView): Boolean; overload;

  function GetSpriteByGid(Map: PCachedMap;
                          Gid: Cardinal;
                          out Sprite: PSprite): Boolean;
    // Generates new sprite (or returns cached) for the specified tile Gid.
    // The Gid contains both index and flipped flags.
    // Returns False and Sprite=nil if sprite cannot be generated.

  function FindTilesetByGid(Map: PCachedMap; Gid: Cardinal; out T: PCachedTileset; out Index: Cardinal): Boolean;
  function GetSpritePathByGid(Map: PCachedMap; Gid: Cardinal): AnsiString;

  property OnTilesetInfo: TOnTiledTilesetInfo read FOnTilesetInfo write FOnTilesetInfo;
  property OnObject: TOnTiledObjectManager read FOnObject write FOnObject;
  property OnTileObject: TOnTiledObjectManager read FOnTileObject write FOnTileObject;
  property OnImageLayer: TOnTiledImagerLayer read FOnImageLayer write FOnImageLayer;
  property OnTile: TOnTiledTile read FOnTile write FOnTile;
end;

implementation

var
  TiledManager: PTiledManager;
  GlobalT: TTiledManager.PCachedTileset;
  GlobalFirstGid: LongInt;

procedure OnTiledHeader(Header: PTiledHeader);
begin
  Log('  Level Version: ' + Header^.Version.ToString);
  Log('  Tiled Version: ' + Header^.TiledVersion.ToString);
  Log('  Orientation:   ' + Header^.Orientation.ToString);
  Log('  RenderOrder:   ' + Header^.RenderOrder.ToString);
  Log('  Width:         ' + IntToStr(Header^.Width));
  Log('  Height:        ' + IntToStr(Header^.Height));
  Log('  TileWidth:     ' + IntToStr(Header^.TileWidth));
  Log('  TileHeight:    ' + IntToStr(Header^.TileHeight));
  Log('  Infinite:      ' + IntToStr(Header^.Infinite));
  Log('  NextLayerId:   ' + IntToStr(Header^.NextLayerId));
  Log('  NextObjectId:  ' + IntToStr(Header^.NextObjectId));
  TiledManager^.FMap^.Header.Version.Init(Header^.Version);
  TiledManager^.FMap^.Header.TiledVersion.Init(Header^.TiledVersion);
  TiledManager^.FMap^.Header.Orientation.Init(Header^.Orientation);
  TiledManager^.FMap^.Header.RenderOrder.Init(Header^.RenderOrder);
  TiledManager^.FMap^.Header.Width := Header^.Width;
  TiledManager^.FMap^.Header.Height := Header^.Height;
  TiledManager^.FMap^.Header.TileWidth := Header^.TileWidth;
  TiledManager^.FMap^.Header.TileHeight := Header^.TileHeight;
  TiledManager^.FMap^.Header.Infinite := Header^.Infinite;
  TiledManager^.FMap^.Header.BackgroundColor := Header^.BackgroundColor;
  TiledManager^.FMap^.Header.NextLayerId := Header^.NextLayerId;
  TiledManager^.FMap^.Header.NextObjectId := Header^.NextObjectId;
  TiledManager^.FMap^.Header.Properties.Init;
end;

function GetFileDirectory(const S: TAnsiView): TAnsiView;
var
  I: Int32;
begin
  I := S.High;
  while (I >= 0) and not (S.Data[I] in ['/', '\']) do
    Dec(I);
  if I < 0 then
    Exit('');
  Exit(AnsiView(S.Data, I + 1));
end;

procedure UploadTileset(T: TTiledManager.PCachedTileset; TilesetInfo: PTiledTilesetInfo; FirstGid: Cardinal);
var
  I, J: LongInt;
begin
  T^.TileCount := TilesetInfo^.TileCount;
  T^.TileWidth := TilesetInfo^.TileWidth;
  T^.TileHeight := TilesetInfo^.TileHeight;
  T^.Columns := TilesetInfo^.Columns;
  // T^.FileName.Init(PAnsiChar(TiledManager^.FTiledBasePath.ToString + '/' + TilesetInfo^.Image.Source.ToString));
  T^.FileName.Init(PAnsiChar(GetFileDirectory(T^.Path).ToString + TilesetInfo^.Image.Source.ToString));
  TilesetInfo^._AbsoluteSourcePath.ReInit(T^.FileName);
  SetLength(T^.Tiles, Length(TilesetInfo^.Tiles));
  // Animated tiles and tiles with additional data
  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;
  // Dont forget to save the info about the tile to the loading Map
  // FIXME: do that for duplicated loaded tilesets too
  with TiledManager^ do if FMap <> nil then begin
    SetLength(FMap^.Tilesets, Length(FMap^.Tilesets) + 1);
    FMap^.Tilesets[High(FMap^.Tilesets)].FirstGid := FirstGid;
    FMap^.Tilesets[High(FMap^.Tilesets)].Cached := T;
  end else
    Log('ERROR: TTiledManager.AddExternalTileset: FMap=nil');
end;

procedure UploadInternalTileset(TilesetInfo: PTiledTilesetInfo);
var
  T: TTiledManager.PCachedTileset;
begin
  TiledManager^.AddInternalTileset(T);
  UploadTileset(T, TilesetInfo, TilesetInfo^.FirstGid);
end;

procedure UploadExternalTileset(TilesetInfo: PTiledTilesetInfo);
begin
  UploadTileset(GlobalT, TilesetInfo, GlobalFirstGid);
end;

procedure GlobalTilesetInfo(TilesetInfo: PTiledTilesetInfo);
      //  Saves the parsed TilesetInfo. It may be a tileset description itself
      //  or a reference Tileset^.Source to a tileset stored in external file.
      //  It uses globally declared manager TiledManager.
      //  Supposed to be used as a callback in tiled loading.
var
  Tileset: TTiledManager.PCachedTileset;
begin
  Log('  Tileset:');
  Log('    FirstGid:      ' + IntToStr(TilesetInfo^.FirstGid));
  Log('    Source:        ' + TilesetInfo^.Source.ToString);
  GlobalFirstGid := TilesetInfo^.FirstGid;
  if TiledManager^.FOnTilesetInfo <> nil then
    TiledManager^.FOnTilesetInfo(TilesetInfo);
  if TilesetInfo^.Source <> '' then begin
    // External tileset
    TiledManager^.LoadTileset(TiledManager^.FTiledBasePath.ToString + '/' + TilesetInfo^.Source.ToString);
  end else begin
    // Internal (nameless) tileset
    UploadInternalTileset(TilesetInfo);
  end;
end;

procedure GlobalOnObject(ObjectGroup: PTiledObjectGroupInfo; Obj: PTiledObject; Props: PTiledProperties);
begin
  if TiledManager^.FOnObject <> nil then
    TiledManager^.FOnObject(ObjectGroup, Obj, Props, TiledManager^.FMap);
end;

procedure GlobalOnTileObject(ObjectGroup: PTiledObjectGroupInfo; Obj: PTiledObject; Props: PTiledProperties);
begin
  if TiledManager^.FOnTileObject <> nil then
    TiledManager^.FOnTileObject(ObjectGroup, Obj, Props, TiledManager^.FMap);
end;

function TTiledManager.FindTileset(const Path: TAnsiView; out Tileset: PCachedTileset): Boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FTilesets) do begin
    if FTilesets[I]^.IsExternal and (FTilesets[I]^.Path = Path) then begin
      Tileset := FTilesets[I];
      Exit(True);
    end;
  end;
  Exit(False);
end;

function TTiledManager.FindMap(const Path: TAnsiView; out Map: PCachedMap): Boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FMaps) do begin
    if FMaps[I]^.Path = Path then begin
      Map := FMaps[I];
      Exit(True);
    end;
  end;
  Exit(False);
end;

function LoadFileData(const FileName: TAnsiView;
                      out Data: PAnsiChar;
                      out DataLength: SizeUInt): Boolean;
var
  FileStream: TStreamFile;
begin
  FileStream.Init(FileName.ToString); // FIXME support TAnsiView in TStreamFile
  DataLength := FileStream.Size;
  Data := GetMem(DataLength);
  if FileStream.Read(Data, DataLength) < DataLength then begin
    FreeMem(Data);
    Exit(False);
  end else
    Exit(True);
end;

function TTiledManager.AddExternalTileset(const Path: TAnsiView; out Tileset: PCachedTileset): Boolean;
begin
  SetLength(FTilesets, Length(FTilesets) + 1);
  New(FTilesets[High(FTilesets)]);
  Tileset := FTilesets[High(FTilesets)];
  // All meaningful fields of the Tileset will be filled later
  Tileset^.IsExternal := True;
  Tileset^.Path.Init(Path.Data, Path.Length);
  Tileset^.FileName.Init;
  if not LoadFileData(Path, Tileset^.Data, Tileset^.DataLength) then begin
    Tileset^.FileName.Done;
    Tileset^.Path.Done;
    Dispose(Tileset);
    SetLength(FTilesets, Length(FTilesets) - 1);
    Log('ERROR: cannot load ' + Path.ToString);
    Exit(False);
  end;
  Exit(True);
end;

function TTiledManager.AddInternalTileset(out Tileset: PCachedTileset): Boolean;
begin
  SetLength(FTilesets, Length(FTilesets) + 1);
  New(FTilesets[High(FTilesets)]);
  Tileset := FTilesets[High(FTilesets)];
  // All meaningful fields of the Tileset will be filled later
  Tileset^.IsExternal := False;
  Tileset^.Path.Init;
  Tileset^.FileName.Init;
  Tileset^.DataLength := 0;
  Tileset^.Data := nil;
  Exit(True);
end;

function TTiledManager.AddMap(const Path: TAnsiView; out Map: PCachedMap): Boolean;
var
  FileStream: TStreamFile;
begin
  SetLength(FMaps, Length(FMaps) + 1);
  New(FMaps[High(FMaps)]);
  Map := FMaps[High(FMaps)];
  Map^.Path.Init(Path.Data, Path.Length);
  // Map^.Header will be filled later
  if not LoadFileData(Path, Map^.Data, Map^.DataLength) then begin
    Map^.Path.Done;
    Dispose(Map);
    SetLength(FMaps, Length(FMaps) - 1);
    Log('ERROR: cannot load ' + Path.ToString);
    Exit(False);
  end;
  Exit(True);
end;

function TTiledManager.GenerateJson(Map: PCachedMap;
                        const FileName: TAnsiView;
                        X, Y, W, H: LongInt;
                        FlipD, FlipH, FlipV: Boolean;
                        Tile: PCachedTile): PJson;
var
  I: LongInt;
  JsonValue: TJsonValue;
  xywh: PJsonArray;
  frames: PJsonArray;
  frame: PJson;
begin
  New(Result, Init);
  Result^.Add('filename', Tile^.Tileset^.FileName.ToString);
  New(xywh, Init);
  xywh^.Add(X * W);
  xywh^.Add(Y * H);
  xywh^.Add(W);
  xywh^.Add(H);
  Result^.Add('xywh', PJsonArray(xywh));
  if (Tile^.Info <> nil) and (Length(Tile^.Info^.Anim.Frames) <> 0) then begin
    New(frames, Init);
    for I := 0 to High(Tile^.Info^.Anim.Frames) do begin
      New(frame, Init);
      New(xywh, Init);
      X := Tile^.Info^.Anim.Frames[I].TileId mod Tile^.Tileset^.Columns;
      Y := Tile^.Info^.Anim.Frames[I].TileId div Tile^.Tileset^.Columns;
      xywh^.Add(X * Tile^.Tileset^.TileWidth);
      xywh^.Add(Y * Tile^.Tileset^.TileHeight);
      xywh^.Add(W);
      xywh^.Add(H);
      frame^.Add('xywh', xywh);
      frame^.Add('dt', Tile^.Info^.Anim.Frames[I].Duration);
      frames^.Add(frame);
    end;
    Result^.Add('frames', frames);
  end;
  Result^.Add('flipd', Ord(FlipD));
  Result^.Add('fliph', Ord(FlipH));
  Result^.Add('flipv', Ord(FlipV));
end;

function TTiledManager.FindTilesetByGid(Map: PCachedMap; Gid: Cardinal; out T: PCachedTileset; out Index: Cardinal): Boolean;
var
  I: LongInt;
begin
  if Gid = 0 then
    Exit(False);
  if Length(Map^.Tilesets) = 0 then
    Exit(False);
  for I := 0 to High(Map^.Tilesets) - 1 do begin
    if Gid < Map^.Tilesets[I + 1].FirstGid then begin
      T := Map^.Tilesets[I].Cached;
      Index := Gid - Map^.Tilesets[I].FirstGid;
      Exit(True);
    end;
  end;
  T := Map^.Tilesets[High(Map^.Tilesets)].Cached;
  Index := Gid - Map^.Tilesets[High(Map^.Tilesets)].FirstGid;
  Result := Index < T^.TileCount;
  if not Result then
    Log('WARNING: FindTilesetByGid: not enough tiles (need ' + IntToStr(Index) + ', but in last tileset TileCount=' + IntToStr(T^.TileCount));
end;

function TTiledManager.LoadTile(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
var
  T: PCachedTileset;
  I: LongInt;
begin
  Tile^.Flipped := Gid and TILED_FLIPPED_MASK;
  if not FindTilesetByGid(Map, Gid and TILED_ID_MASK, Tile^.Tileset, Tile^.Index) then
    Exit(False);
  Tile^.Json := nil;
  Tile^.Sprite := nil;
  Tile^.Info := nil;
  for I := 0 to High(Tile^.Tileset^.Tiles) do begin
    if Tile^.Tileset^.Tiles[I].Id = Tile^.Index then begin
      Tile^.Info := @Tile^.Tileset^.Tiles[I];
      break;
    end;
  end;
  Exit(True);
end;

function TTiledManager.LoadTileJson(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
begin
  Tile^.Json := GenerateJson(Map,
      Tile^.Tileset^.FileName,
      Tile^.Index mod Tile^.Tileset^.Columns,
      Tile^.Index div Tile^.Tileset^.Columns,
      Tile^.Tileset^.TileWidth,
      Tile^.Tileset^.TileHeight,
      Tile^.Flipped and TILED_FLIPPED_DIAGONALLY_FLAG <> 0,
      Tile^.Flipped and TILED_FLIPPED_HORIZONTALLY_FLAG <> 0,
      Tile^.Flipped and TILED_FLIPPED_VERTICALLY_FLAG <> 0,
      Tile);
  Result := Tile^.Json <> nil;
end;

function TTiledManager.LoadTileSprite(Map: PCachedMap; Tile: PCachedTile; Gid: Cardinal): Boolean;
var
  JsonValue: TJsonValue;
begin
  if Tile^.Json = nil then
    LoadTileJson(Map, Tile, Gid);
  if Tile^.Sprite = nil then begin
    New(Tile^.Sprite, Init);
    JsonValue._Type := JSON_OBJECT;
    JsonValue.O := Tile^.Json;
    Tile^.Sprite^.LoadFromJson(nil, @Eng, @JsonValue);
    Exit(True);
  end else
    Exit(True);
end;

function TTiledManager.GetCachedSprite(Map: PCachedMap; Gid: Cardinal): PtrInt;
var
  I: LongInt;
  Json: PJson;
begin
  // Check for cached tiles in the map
  for I := 0 to High(Map^.CachedTiles) do
    if Map^.CachedTiles[I].Gid = Gid then
      Exit(I);
  SetLength(Map^.CachedTiles, Length(Map^.CachedTiles) + 1);
  I := High(Map^.CachedTiles);
  Map^.CachedTiles[I].Gid := Gid;
  New(Map^.CachedTiles[I].Tile);
  if not LoadTile(Map, Map^.CachedTiles[I].Tile, Gid) then begin
    Dispose(Map^.CachedTiles[I].Tile);
    SetLength(Map^.CachedTiles, Length(Map^.CachedTiles) - 1);
    Exit(-1);
  end;
  Exit(I);
end;

procedure TTiledManager.Init;
begin
  FOnHeader := @OnTiledHeader;
  FOnTilesetInfo := nil;
  FOnLayerStart := nil;
  FOnLayerEnd := nil;
  FOnTile := nil;
  FOnObject := nil;
  FOnTileObject := nil;
  FOnImageLayer := nil;
  FTiledBasePath.Init('');
end;

procedure TTiledManager.Done;
begin
  FTiledBasePath.Done;
end;

function TTiledManager.LoadTileset(const Path: TAnsiView; out Cached: PCachedTileset): Boolean;
var
  TilesetReader: TTiledTSXReader;
  XmlParser: TXmlParser;
  SavedT: PCachedTileset;
begin
  if not FindTileset(Path, Cached) then begin
    if not AddExternalTileset(Path, Cached) then
      Exit(False);
  end;
  XmlParser.Init;
  TilesetReader.Init;
  SavedT := GlobalT;
  GlobalT := Cached;
  TilesetReader.OnTilesetInfo := @UploadExternalTileset;
  TilesetReader.OnTileObject := @GlobalOnTileObject;
  if not XmlParser.Parse(Path.ToString, @TilesetReader) then begin
    Log('Failed reading tileset ' + Path.ToString + ' ' + IntToStr(XmlParser.Error.Id) + ' ' + XmlParser.Error.Msg);
    GlobalT := SavedT;
    Exit(False);
  end;
  GlobalT := SavedT;
  TilesetReader.Done;
  XmlParser.Done;
  Exit(True);
end;

function TTiledManager.LoadTileset(const Path: TAnsiView): Boolean;
var
  Cached: PCachedTileset;
begin
  Result := LoadTileset(Path, Cached);
end;

function TTiledManager.LoadMap(const Path: TAnsiView; out Cached: PCachedMap): Boolean;
var
  TiledReader: TTiledReader;
  XmlParser: TXmlParser;
  E: PAnsiChar;
begin
  E := Path.Tail;
  while E > Path.Data do begin
    Dec(E);
    if (E^ = '/') or (E = '\') then
      break;
  end;
  FTiledBasePath.ReInit(Path.Data, E);
  if not FindMap(Path, FMap) then begin
    if not AddMap(Path, FMap) then
      Exit(False);
  end;
  XmlParser.Init;
  TiledReader.Init;
  TiledReader.OnHeader := FOnHeader;
  TiledReader.OnTilesetInfo := @GlobalTilesetInfo;
  TiledReader.OnLayerStart := FOnLayerStart;
  TiledReader.OnLayerEnd := FOnLayerEnd;
  TiledReader.OnTile := FOnTile;
  TiledReader.OnObject := @GlobalOnObject;
  TiledReader.OnTileObject := @GlobalOnTileObject;
  TiledReader.OnImageLayer := FOnImageLayer;
  TiledManager := @Self;
  if not XmlParser.Parse(Data(FMap^.Data, FMap^.DataLength), @TiledReader) then begin
    Log('Failed reading ' + Path.ToString + ' (' + IntToStr(XmlParser.Error.Id) + '): ' + XmlParser.Error.Msg);
    Exit(False);
  end;
  TiledReader.Done;
  XmlParser.Done;
  Cached := FMap;
  Exit(True);
end;

function TTiledManager.LoadMap(const Path: TAnsiView): Boolean;
var
  Cached: PCachedMap;
begin
  Result := LoadMap(Path, Cached);
end;

function TTiledManager.GetSpriteByGid(Map: PCachedMap;
                         Gid: Cardinal;
                         out Sprite: PSprite): Boolean;
var
  I: LongInt;
begin
  I := GetCachedSprite(Map, Gid);
  if I = -1 then
    Exit(False);
  LoadTileSprite(Map, Map^.CachedTiles[I].Tile, Gid);
  Sprite := Map^.CachedTiles[I].Tile^.Sprite;
  Exit(Sprite <> nil);
end;

function TTiledManager.GetSpritePathByGid(Map: PCachedMap; Gid: Cardinal): AnsiString;
var
  I: LongInt;
begin
  I := GetCachedSprite(Map, Gid);
  if I = -1 then
    Exit('');
  Exit(Map^.CachedTiles[I].Tile^.Tileset^.FileName.ToString);
end;

end.
