unit dtiled;

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

interface

uses
  dansiview,
  dansibuffer,
  dansibuffer_ansiview,
  dparser,
  dparser_json,
  dstreamfile,
  dxml_sax,
  dxml_sax_common;

const
  // Flipped flags in gid
  TILED_FLIPPED_HORIZONTALLY_FLAG = $80000000;
  TILED_FLIPPED_VERTICALLY_FLAG   = $40000000;
  TILED_FLIPPED_DIAGONALLY_FLAG   = $20000000;
  TILED_FLIPPED_MASK              = TILED_FLIPPED_HORIZONTALLY_FLAG or
                                    TILED_FLIPPED_VERTICALLY_FLAG or
                                    TILED_FLIPPED_DIAGONALLY_FLAG;
  TILED_ID_MASK                   = not UInt64(TILED_FLIPPED_HORIZONTALLY_FLAG or
                                         TILED_FLIPPED_VERTICALLY_FLAG or
                                         TILED_FLIPPED_DIAGONALLY_FLAG);
  // Property types (default is string)
  TILED_PROPERTY_STRING     = 0;
  TILED_PROPERTY_INT        = 1;
  TILED_PROPERTY_FLOAT      = 2;
  TILED_PROPERTY_BOOL       = 3;
  TILED_PROPERTY_COLOR      = 4;
  TILED_PROPERTY_FILE       = 5;

type
PTiledTilesetReader = ^TTiledTilesetReader;
PTiledLoader = ^TTiledReader;
PTiledLayerReader = ^TTiledLayerReader;

PTiledProperty = ^TTiledProperty;
TTiledProperty = record
  // TODO заменить AnsiString на TAnsiBuffer, когда TAnsiBuffer можно
  // будет двигать в памяти без последствий
  Name: AnsiString;
  _Type: LongInt;     // One of TILED_PROPERTY_* const
  Raw: AnsiString;    // Raw value
  function RawUnescaped: AnsiString;
  function AsFloat: Double;
  function AsInteger: LongInt;
end;

PTiledProperties = ^TTiledProperties;
TTiledProperties = object
private
  FProperties: array of TTiledProperty;
  procedure Add(const P: TTiledProperty);
  procedure Reset;
public
  procedure Init;
  procedure Init(Other: PTiledProperties);
  procedure Done;
  function Count: LongInt;
  function Get(Index: LongInt): PTiledProperty;
  function GetRaw(Index: LongInt): TAnsiView;
  function Find(Name: TAnsiView; out P: PTiledProperty): Boolean;
  function GetRaw(Name: TAnsiView; Def: TAnsiView): TAnsiView;
  // Checks type of the property
  function GetStr(Name: TAnsiView; Def: TAnsiView): TAnsiView;
  function GetInt(Name: TAnsiView; Def: LongInt): LongInt;
  function GetFloat(Name: TAnsiView; Def: Single): Single;
  function GetBool(Name: TAnsiView; Def: Boolean): Boolean;
  function GetColor(Name: TAnsiView; Def: Cardinal): Cardinal;
  function GetFile(Name: TAnsiView; Def: TAnsiView): TAnsiView;
end;

PTiledHeader = ^TTiledHeader;
TTiledHeader = record
  Version: TAnsiBuffer;       // The TMX format version
  TiledVersion: TAnsiBuffer;  // The Tiled version used to save the file
  Orientation: TAnsiBuffer;   // orthogonal, isometric, staggered or hexagonal
  RenderOrder: TAnsiBuffer;   // right-down, right-up, left-down or left-up
  Width: Int32;           // the map width in tiles
  Height: Int32;          // the map height in tiles
  TileWidth: Int32;       // the width of a tile
  TileHeight: Int32;      // the height of a tile
  Infinite: Int32;        // 0 for false, 1 for true
  BackgroundColor: UInt32;
  NextLayerId: Int32;     // next available ID for new layers
  NextObjectId: Int32;    // next available ID for new objects
  Properties: TTiledProperties;
end;

PTiledFrameInfo = ^TTiledFrameInfo;
TTiledFrameInfo = record
  TileId: LongInt;
  Duration: LongInt;
end;

PTiledAnimInfo = ^TTiledAnimInfo;
TTiledAnimInfo = record
  Frames: array of TTiledFrameInfo;
end;

PTiledTileInfo = ^TTiledTileInfo;
TTiledTileInfo = record
  Id: LongInt;
  Anim: TTiledAnimInfo;
  Props: TTiledProperties;
end;

PTiledImageInfo = ^TTiledImageInfo;
TTiledImageInfo = record
  Format: TAnsiBuffer;  // png, gif, jpg, bmp etc
  Id: TAnsiBuffer;      // Deprecated and unsupported by Tiled Qt
  Source: TAnsiBuffer;  // The reference to the tileset image file
  Trans: Cardinal;      // A specific color that is treated as transparent
  Width: LongInt;       // The image width in pixels
  Height: LongInt;      // The image height in pixels
end;

PTiledTilesetInfo = ^TTiledTilesetInfo;
TTiledTilesetInfo = record
  FirstGid: LongInt;
  // If this tileset is stored in an external TSX (Tile Set XML) file, this
  // attribute refers to that file. That TSX file has the same structure as the
  // <tileset> element described here. (There is the firstgid attribute missing
  // and this source attribute is also not there. These two attributes are kept
  // in the TMX map, since they are map specific.)
  Source: TAnsiBuffer;
  Name: TAnsiBuffer;
  TileWidth: LongInt;
  TileHeight: LongInt;
  Spacing: LongInt;
  Margin: LongInt;
  TileCount: LongInt;
  Columns: LongInt;
  Image: TTiledImageInfo;
  Tiles: array of TTiledTileInfo;
  _AbsoluteSourcePath: TAnsiBuffer; // not a tiled field
end;

PTiledLayerInfo = ^TTiledLayerInfo;
TTiledLayerInfo = record
  Id: LongInt;
  Name: TAnsiBuffer;
  X: LongInt;         // The x coordinate of the layer in tiles. Defaults to 0
  Y: LongInt;         // The y coordinate of the layer in tiles. Defaults to 0
  Width: LongInt;
  Height: LongInt;
  Opacity: Single;    // The opacity of the layer as a value from 0 to 1
  Visible: LongInt;   // Whether the layer is shown (1) or hidden (0)
  OffsetX: LongInt;   // Rendering offset for this layer in pixels
  OffsetY: LongInt;   // Rendering offset for this layer in pixels
end;

PTiledObjectGroupInfo = ^TTiledObjectGroupInfo;
TTiledObjectGroupInfo = record
  Id: LongInt;
  Name: TAnsiBuffer;
  Color: Cardinal;
  X: LongInt;         // The x coordinate of the object group in tiles
  Y: LongInt;         // The y coordinate of the object group in tiles
  Width: LongInt;     // The width of the object group in tiles. Meaningless.
  Height: LongInt;    // The height of the object group in tiles. Meaningless.
  Opacity: Single;
  Visible: LongInt;
  OffsetX: LongInt;
  OffsetY: LongInt;
  DrawOrder: TAnsiBuffer;
end;

//
// > When the object has a gid set, then it is represented by the image of the
// > tile with that global ID. The image alignment currently depends on the map
// > orientation. In orthogonal orientation it’s aligned to the bottom-left
// > while in isometric it’s aligned to the bottom-center.
//
// > When the object has a template set, it will borrow all the properties from
// > the specified template, properties saved with the object will have higher
// > priority, i.e. they will override the template properties.
//
PTiledObject = ^TTiledObject;
TTiledObject = record
  Id: LongInt;
  Name: TAnsiBuffer;
  _Type: TAnsiBuffer;
  X: LongInt;         // The x coordinate of the object in pixels
  Y: LongInt;         // The y coordinate of the object in pixels
  Width: LongInt;     // The width of the object in pixels (defaults to 0)
  Height: LongInt;    // The height of the object in pixels (defaults to 0)
  Rotation: Single;   // The rotation of the object in degrees clockwise (defaults to 0)
  Gid: Cardinal;      // A reference to a tile (optional)
  Visible: LongInt;
  Template: TAnsiBuffer; // A reference to a template file (optional)
end;

PTiledImageLayer = ^TTiledImageLayer;
TTiledImageLayer = record
  Id: LongInt;
  Name: TAnsiBuffer;
  OffsetX: LongInt;
  OffsetY: LongInt;
  Opacity: Single;
  Visible: LongInt;
  //Width: LongInt;
  //Height: LongInt;
  Image: TTiledImageInfo;
  Props: TTiledProperties;
end;

TOnTiledHeader = procedure (Header: PTiledHeader);
TOnTiledMapProperties = procedure (Props: PTiledProperties);
TOnTiledTilesetInfo = procedure (TilesetInfo: PTiledTilesetInfo);
TOnTiledTile = procedure (Layer: PTiledLayerInfo; X, Y: LongInt; TileID: LongInt);
//TOnTiledTileInfo = procedure (Tileset: PTiledTilesetInfo; TileInfo: PTiledTileInfo);
TOnTiledLayerStart = procedure (Layer: PTiledLayerInfo);
TOnTiledLayerEnd = procedure (Layer: PTiledLayerInfo);
TOnTiledObjectGroupStart = procedure (ObjectGroup: PTiledObjectGroupInfo; Props: PTiledProperties);
TOnTiledObjectGroupEnd = procedure (ObjectGroup: PTiledObjectGroupInfo);
TOnTiledObject = procedure (ObjectGroup: PTiledObjectGroupInfo; Obj: PTiledObject; Props: PTiledProperties);
TOnTiledImagerLayer = procedure (ImageLayer: PTiledImageLayer);

//
// Reads <property> tag inside a <properties> tag
//
TTiledPropertyReader = object(TXmlReaderSwitchable)
private
  FProperties: PTiledProperties;
  FProperty: PTiledProperty;
public
  constructor Init(aParentReader: PXmlReader; Properties: PTiledProperties; Prop: PTiledProperty);
  destructor Done; virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnContent(Parser: PXmlParser; const Content: TAnsiView); virtual;
end;

//
//  Reads <properties> tag
//
TTiledPropertiesReader = object(TXmlReaderSwitchable)
private
  FPropertyReader: TTiledPropertyReader;
  FProperty: TTiledProperty;
  FProperties: PTiledProperties;
public
  constructor Init(aParentReader: PXmlReader; Properties: PTiledProperties);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <image> tag
//
TTiledImageReader = object(TXmlReaderSwitchable)
private
  FImageInfo: PTiledImageInfo;
public
  constructor Init(aParentReader: PXmlReader; aImageInfo: PTiledImageInfo);
  destructor Done; virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <frame> tag
//
TTiledFrameReader = object(TXmlReaderSwitchable)
private
  FFrameInfo: PTiledFrameInfo;
public
  constructor Init(aParentReader: PXmlReader; aFrameInfo: PTiledFrameInfo);
  destructor Done; virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <animation> tag
//
TTiledAnimationReader = object(TXmlReaderSwitchable)
private
  FFrameReader: TTiledFrameReader;
  FTileInfo: PTiledTileInfo;
public
  constructor Init(aParentReader: PXmlReader; aTileInfo: PTiledTileInfo);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <object> tag inside a <objectgroup> tag
//
TTiledObjectReader = object(TXmlReaderSwitchable)
private
  FObject: TTiledObject;
  FOnObject: TOnTiledObject;
  FProperties: TTiledProperties;
  FPropertiesReader: TTiledPropertiesReader;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure Reset;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <objectgroup> tag
//
PTiledObjectGroupReader = ^TTiledObjectGroupReader;
TTiledObjectGroupReader = object(TXmlReaderSwitchable)
private
  FObjectGroupInfo: TTiledObjectGroupInfo;
  FObjectReader: TTiledObjectReader;
  FProperties: TTiledProperties;
  FPropertiesReader: TTiledPropertiesReader;
  FOnObjectGroupStart: TOnTiledObjectGroupStart;
  FOnObjectGroupEnd: TOnTiledObjectGroupEnd;
  FCallbackCalled: Boolean;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <tile> tag
//
TTiledTileReader = object(TXmlReaderSwitchable)
private
  FTileInfo: PTiledTileInfo;
  FAnimationReader: TTiledAnimationReader;
  FObjectGroupReader: TTiledObjectGroupReader;
  FProperties: TTiledProperties;
  FPropertiesReader: TTiledPropertiesReader;
public
  constructor Init(aParentReader: PXmlReader; aTileInfo: PTiledTileInfo);
  destructor Done; virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <tileset> tag
//
//  > If there are multiple <tileset> elements, they are in ascending order of
//  > their firstgid attribute. The first tileset always has a firstgid value
//  > of 1.
//
//
TTiledTilesetReader = object(TXmlReaderSwitchable)
private
  FTilesetInfo: TTiledTilesetInfo;
  FOnTilesetInfo: TOnTiledTilesetInfo;
  FImageReader: TTiledImageReader;
  FTileReader: TTiledTileReader;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
  property OnTilesetInfo: TOnTiledTilesetInfo read FOnTilesetInfo write FOnTilesetInfo;
end;

//
//  Reads .tsx file
//
TTiledTSXReader = object(TXmlReaderSwitchable)
private
  FTilesetReader: TTiledTilesetReader;
public
  constructor Init;
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  property OnTilesetInfo: TOnTiledTilesetInfo read FTilesetReader.FOnTilesetInfo write FTilesetReader.FOnTilesetInfo;
  property OnTileObject: TOnTiledObject read FTilesetReader.FTileReader.FObjectGroupReader.FObjectReader.FOnObject write FTilesetReader.FTileReader.FObjectGroupReader.FObjectReader.FOnObject;
end;

//
//  Reads <imagelayer>
//
TTiledImageLayerReader = object(TXmlReaderSwitchable)
private
  FImageReader: TTiledImageReader;
  FPropertiesReader: TTiledPropertiesReader;
  FOnImageLayer: TOnTiledImagerLayer;
  FImageLayer: TTiledImageLayer;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
  property OnImageLayer: TOnTiledImagerLayer read FOnImageLayer write FOnImageLayer;
end;

//
//  Reads <data> tag inside a <layer> tag
//
TTiledLayerDataReader = object(TXmlReaderSwitchable)
private
  FOnTile: TOnTiledTile;
public
  constructor Init(aParentReader: PXmlReader);
  procedure OnContent(Parser: PXmlParser; const Content: TAnsiView); virtual;
  property OnTiledTile: TOnTiledTile read FOnTile write FOnTile;
end;

//
//  Reads <layer> tag
//
//  From TMX format documentation:
//
//  > All <tileset> tags shall occur before the first <layer> tag so that
//  > parsers may rely on having the tilesets before needing to resolve tiles.
//
TTiledLayerReader = object(TXmlReaderSwitchable)
private
  FLayerInfo: TTiledLayerInfo;
  FLayerDataReader: TTiledLayerDataReader;
  FOnLayerStart: TOnTiledLayerStart;
  FOnLayerEnd: TOnTiledLayerEnd;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

//
//  Reads <map> tag
//
TTiledMapReader = object(TXmlReaderSwitchable)
private
  FHeader: TTiledHeader;
  FOnHeader: TOnTiledHeader;
  FOnMapProperties: TOnTiledMapProperties;
  FTilesetReader: TTiledTilesetReader;
  FLayerReader: TTiledLayerReader;
  FObjectGroupReader: TTiledObjectGroupReader;
  FImageLayerReader: TTiledImageLayerReader;
  FProperties: TTiledProperties;
  FPropertiesReader: TTiledPropertiesReader;
public
  constructor Init(aParentReader: PXmlReader);
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView); virtual;
  procedure OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView); virtual;
  procedure OnCloseTag(Parser: PXmlParser; const Name: TAnsiView); virtual;
end;

TTiledReader = object(TXmlReaderSwitchable)
private
  FMapReader: TTiledMapReader;
  function GetOnTileObject: TOnTiledObject; inline;
  procedure SetOnTileObject(aOnTileObject: TOnTiledObject); inline;
public
  constructor Init;
  destructor Done; virtual;
  procedure OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView); virtual;
  property OnHeader: TOnTiledHeader read FMapReader.FOnHeader write FMapReader.FOnHeader;
  property OnMapProperties: TOnTiledMapProperties read FMapReader.FOnMapProperties write FMapReader.FOnMapProperties;
  property OnTilesetInfo: TOnTiledTilesetInfo read FMapReader.FTilesetReader.FOnTilesetInfo write FMapReader.FTilesetReader.FOnTilesetInfo;
  property OnTile: TOnTiledTile read FMapReader.FLayerReader.FLayerDataReader.FOnTile write FMapReader.FLayerReader.FLayerDataReader.FOnTile;
  property OnLayerStart: TOnTiledLayerStart read FMapReader.FLayerReader.FOnLayerStart write FMapReader.FLayerReader.FOnLayerStart;
  property OnLayerEnd: TOnTiledLayerStart read FMapReader.FLayerReader.FOnLayerEnd write FMapReader.FLayerReader.FOnLayerEnd;
  property OnObjectGroupStart: TOnTiledObjectGroupStart read FMapReader.FObjectGroupReader.FOnObjectGroupStart write FMapReader.FObjectGroupReader.FOnObjectGroupStart;
  property OnObjectGroupEnd: TOnTiledObjectGroupEnd read FMapReader.FObjectGroupReader.FOnObjectGroupEnd write FMapReader.FObjectGroupReader.FOnObjectGroupEnd;
  property OnObject: TOnTiledObject read FMapReader.FObjectGroupReader.FObjectReader.FOnObject write FMapReader.FObjectGroupReader.FObjectReader.FOnObject;
  property OnImageLayer: TOnTiledImagerLayer read FMapReader.FImageLayerReader.FOnImageLayer write FMapReader.FImageLayerReader.FOnImageLayer;
  property OnTileObject: TOnTiledObject read GetOnTileObject write SetOnTileObject;
end;

implementation

procedure ParseI32(const Value: TAnsiView; out I: Int32);
var
  S: PAnsiChar;
begin
  S := Value.Data;
  if not ParseDecimalI32(S, Value.Tail, I) then
    ; // Ignore the error
end;

procedure ParseU32(const Value: TAnsiView; out I: UInt32);
var
  S: PAnsiChar;
begin
  S := Value.Data;
  if not ParseDecimalU32(S, Value.Tail, I) then
    ; // Ignore the error
end;

function ParseColor(const Value: TAnsiView; out Color: UInt32): Boolean;
type
  TBytes = array[0 .. 3] of Byte;
begin
  if Value.Data^ = '#' then begin
    if Value.Length >= 7 then begin
      TBytes((@Color)^)[0] := ParseHexU32Unsafe(Value.Data + 1, 2);
      TBytes((@Color)^)[1] := ParseHexU32Unsafe(Value.Data + 3, 2);
      TBytes((@Color)^)[2] := ParseHexU32Unsafe(Value.Data + 5, 2);
      Result := True;
    end else begin
      TBytes((@Color)^)[0] := 0;
      TBytes((@Color)^)[1] := 0;
      TBytes((@Color)^)[2] := 0;
      Result := False;
    end;
    if Value.Length >= 9 then begin
      TBytes((@Color)^)[3] := ParseHexU32Unsafe(Value.Data + 7, 2);
    end else
      TBytes((@Color)^)[3] := 255;
  end else
    Exit(False);
end;

function TTiledProperty.RawUnescaped: AnsiString;
begin
  // TODO Use UnescapeXML
  Result := UnescapeHTML(Raw);
end;

function TTiledProperty.AsFloat: Double;
var
  S: PAnsiChar;
begin
  S := @Raw[1];
  if not ParseJsonFloat64(S, S + Length(Raw), Result) then
    Result := 0;
end;

function TTiledProperty.AsInteger: LongInt;
//var
//  S: PAnsiChar;
begin
  //S := @Raw[1];
  //if not ParseI32(S, S + Length(Raw), Result) then
  //  Result := 0;
  ParseI32(Raw, Result);
end;

procedure TTiledProperties.Add(const P: TTiledProperty);
begin
  SetLength(FProperties, Length(FProperties) + 1);
  FProperties[High(FProperties)] := P;
end;

procedure TTiledProperties.Reset;
begin
  SetLength(FProperties, 0);
end;

procedure TTiledProperties.Init;
begin
  SetLength(FProperties, 0);
end;

procedure TTiledProperties.Init(Other: PTiledProperties);
var
  I: LongInt;
begin
  SetLength(FProperties, Length(Other^.FProperties));
  for I := 0 to High(FProperties) do begin
    FProperties[I] := Other^.FProperties[I];
  end;
end;

procedure TTiledProperties.Done;
begin
  SetLength(FProperties, 0);
end;

function TTiledProperties.Find(Name: TAnsiView; out P: PTiledProperty): Boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FProperties) do begin
    if FProperties[I].Name = Name then begin
      P := @FProperties[I];
      Exit(True);
    end;
  end;
  Exit(False);
end;

function TTiledProperties.Count: LongInt;
begin
  Exit(Length(FProperties));
end;

function TTiledProperties.Get(Index: LongInt): PTiledProperty;
begin
  Exit(@FProperties[Index]);
end;

function TTiledProperties.GetRaw(Index: LongInt): TAnsiView;
begin
  Exit(FProperties[Index].Raw);
end;

function TTiledProperties.GetRaw(Name: TAnsiView; Def: TAnsiView): TAnsiView;
var
  P: PTiledProperty;
begin
  if Find(Name, P) then begin
    Result := P^.Raw;
  end else
    Result := Def;
end;

function TTiledProperties.GetStr(Name: TAnsiView; Def: TAnsiView): TAnsiView;
var
  P: PTiledProperty;
begin
  if Find(Name, P) and (P^._Type = TILED_PROPERTY_STRING) then begin
    Result := P^.Raw;
  end else
    Result := Def;
end;

function TTiledProperties.GetInt(Name: TAnsiView; Def: LongInt): LongInt;
var
  P: PTiledProperty;
begin
  if Find(Name, P) and (P^._Type = TILED_PROPERTY_INT) then begin
    ParseI32(P^.Raw, Result);
  end else
    Result := Def;
end;

function TTiledProperties.GetFloat(Name: TAnsiView; Def: Single): Single;
var
  P: PTiledProperty;
begin
  if Find(Name, P) then begin
    Result := P^.AsFloat;
  end else
    Result := Def;
end;

function TTiledProperties.GetBool(Name: TAnsiView; Def: Boolean): Boolean;
var
  P: PTiledProperty;
begin
  if Find(Name, P) and (P^._Type = TILED_PROPERTY_BOOL) then begin
    // TODO FIXME
    Result := Def;
  end else
    Result := Def;
end;

function TTiledProperties.GetColor(Name: TAnsiView; Def: Cardinal): Cardinal;
var
  P: PTiledProperty;
begin
  if Find(Name, P) and (P^._Type = TILED_PROPERTY_COLOR) then begin
    // TODO FIXME
    Result := Def;
  end else
    Result := Def;
end;

function TTiledProperties.GetFile(Name: TAnsiView; Def: TAnsiView): TAnsiView;
var
  P: PTiledProperty;
begin
  if Find(Name, P) and (P^._Type = TILED_PROPERTY_FILE) then begin
    // TODO FIXME
    Result := Def;
  end else
    Result := Def;
end;

constructor TTiledPropertyReader.Init(aParentReader: PXmlReader; Properties: PTiledProperties; Prop: PTiledProperty);
begin
  inherited Init(aParentReader);
  FProperties := Properties;
  FProperty := Prop;
end;

destructor TTiledPropertyReader.Done;
begin
  inherited Done;
end;

procedure TTiledPropertyReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'name' then begin
    FProperty^.Name := Value.ToString;
  end else if Name = 'value' then begin
    FProperty^.Raw := Value.ToString;
  end else if Name = 'type' then begin
    if (Value = '') or (Value = 'string') then begin
      FProperty^._Type := TILED_PROPERTY_STRING;
    end else if Value = 'int' then begin
      FProperty^._Type := TILED_PROPERTY_INT;
    end else if Value = 'float' then begin
      FProperty^._Type := TILED_PROPERTY_FLOAT;
    end else if Value = 'bool' then begin
      FProperty^._Type := TILED_PROPERTY_BOOL;
    end else if Value = 'color' then begin
      FProperty^._Type := TILED_PROPERTY_COLOR;
    end else if Value = 'file' then begin
      FProperty^._Type := TILED_PROPERTY_FILE;
    end else
      FProperty^._Type := TILED_PROPERTY_STRING;
  end;
end;

procedure TTiledPropertyReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  FProperties^.Add(FProperty^);
  inherited OnCloseTag(Parser, Name);
end;

procedure TTiledPropertyReader.OnContent(Parser: PXmlParser; const Content: TAnsiView);
var
  S, Line, LineEnd: PAnsiChar;
begin
  FProperty^.Raw := Content.ToString;
  Writeln(stderr, 'OnContent: ');
  S := Content.Data;
  while NextLine(S, Content.Tail, Line, LineEnd) do begin
    Writeln(stderr, '    ', AnsiView(Line, LineEnd).ToString);
  end;
end;

constructor TTiledPropertiesReader.Init(aParentReader: PXmlReader; Properties: PTiledProperties);
begin
  inherited Init(aParentReader);
  FPropertyReader.Init(@Self, Properties, @FProperty);
  FProperties := Properties;
end;

destructor TTiledPropertiesReader.Done;
begin
  FPropertyReader.Done;
  inherited Done;
end;

procedure TTiledPropertiesReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'property' then begin
    FProperty.Name := '';
    FProperty._Type := TILED_PROPERTY_STRING;
    FProperty.Raw := '';
    FPropertyReader.FProperties := FProperties;
    Parser^.SetReader(@FPropertyReader);
  end else
    IgnoreSubtags(Parser);
end;

constructor TTiledImageReader.Init(aParentReader: PXmlReader; aImageInfo: PTiledImageInfo);
begin
  inherited Init(aParentReader);
  FImageInfo := aImageInfo;
end;

destructor TTiledImageReader.Done;
begin
  inherited Done;
end;

procedure TTiledImageReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'source' then begin
    AnsiBufferReInit(FImageInfo^.Source, Value);
  end else if Name = 'width' then begin
    ParseI32(Value, FImageInfo^.Width);
  end else if Name = 'height' then begin
    ParseI32(Value, FImageInfo^.Height);
  end else if Name = 'format' then begin
    AnsiBufferReInit(FImageInfo^.Format, Value);
  end else if Name = 'id' then begin
    AnsiBufferReInit(FImageInfo^.Id, Value);
  end else if Name = 'trans' then begin
    // TODO
    FImageInfo^.Trans := 0;
  end;
end;

procedure TTiledImageReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledFrameReader.Init(aParentReader: PXmlReader; aFrameInfo: PTiledFrameInfo);
begin
  inherited Init(aParentReader);
  FFrameInfo := aFrameInfo;
end;

destructor TTiledFrameReader.Done;
begin
  inherited Done;
end;

procedure TTiledFrameReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'tileid' then begin
    ParseI32(Value, FFrameInfo^.TileId);
  end else if Name = 'duration' then begin
    ParseI32(Value, FFrameInfo^.Duration);
  end;
end;

procedure TTiledFrameReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  IgnoreSubtags(Parser);
end;

procedure TTiledFrameReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledAnimationReader.Init(aParentReader: PXmlReader; aTileInfo: PTiledTileInfo);
begin
  inherited Init(aParentReader);
  FFrameReader.Init(@Self, nil);
  FTileInfo := aTileInfo;
end;

destructor TTiledAnimationReader.Done;
begin
  FFrameReader.Done;
  inherited Done;
end;

procedure TTiledAnimationReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
var
  Frame: PTiledFrameInfo;
begin
  if Name = 'frame' then begin
    SetLength(FTileInfo^.Anim.Frames, Length(FTileInfo^.Anim.Frames) + 1);
    Frame := @FTileInfo^.Anim.Frames[High(FTileInfo^.Anim.Frames)];
    Frame^.TileId := 0;
    Frame^.Duration := 200;
    FFrameReader.FFrameInfo := Frame;
    Parser^.SetReader(@FFrameReader);
  end else
    IgnoreSubtags(Parser);
end;

procedure TTiledAnimationReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledTileReader.Init(aParentReader: PXmlReader; aTileInfo: PTiledTileInfo);
begin
  inherited Init(aParentReader);
  FAnimationReader.Init(@Self, nil);
  FObjectGroupReader.Init(@Self);
  FPropertiesReader.Init(@Self, nil);
end;

destructor TTiledTileReader.Done;
begin
  FPropertiesReader.Done;
  FObjectGroupReader.Done;
  FAnimationReader.Done;
  inherited Done;
end;

procedure TTiledTileReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'id' then begin
    ParseI32(Value, FTileInfo^.Id);
  end;
end;

procedure TTiledTileReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'animation' then begin
    FAnimationReader.FTileInfo := FTileInfo;
    Parser^.SetReader(@FAnimationReader);
  end else if Name = 'objectgroup' then begin
    FObjectGroupReader.FCallbackCalled := False;
    FObjectGroupReader.FProperties.Reset;
    Parser^.SetReader(@FObjectGroupReader);
  end else if Name = 'properties' then begin
    FPropertiesReader.FProperties := @FTileInfo^.Props;
    Parser^.SetReader(@FPropertiesReader);
  end else
    IgnoreSubtags(Parser);
end;

procedure TTiledTileReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledTilesetReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FTilesetInfo.FirstGid := 1;
  FTilesetInfo.Source.Init('');
  FTilesetInfo.Name.Init('');
  FTilesetInfo.TileWidth := 0;
  FTilesetInfo.TileHeight := 0;
  FTilesetInfo.Spacing := 0;
  FTilesetInfo.Margin := 0;
  FTilesetInfo.TileCount := 0;
  FTilesetInfo.Columns := 0;
  FTilesetInfo.Image.Format.Init('');
  FTilesetInfo.Image.Id.Init('');
  FTilesetInfo.Image.Source.Init('');
  FTilesetInfo.Image.Trans := 0;
  FTilesetInfo.Image.Width := 0;
  FTilesetInfo.Image.Height := 0;
  FTilesetInfo._AbsoluteSourcePath.Init('');
  FOnTilesetInfo := nil;
  FImageReader.Init(@Self, @FTilesetInfo.Image);
  FTileReader.Init(@Self, nil);
end;

destructor TTiledTilesetReader.Done;
begin
  FTileReader.Done;
  FImageReader.Done;
  FTilesetInfo.Image.Format.Done;
  FTilesetInfo.Image.Id.Done;
  FTilesetInfo.Image.Source.Done;
  FTilesetInfo.Name.Done;
  FTilesetInfo.Source.Done;
  inherited Done;
end;

procedure TTiledTilesetReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
var
  Tile: PTiledTileInfo;
begin
  if Name = 'image' then begin
    Parser^.SetReader(@FImageReader);
  end else if Name = 'tile' then begin
    SetLength(FTilesetInfo.Tiles, Length(FTilesetInfo.Tiles) + 1);
    Tile := @FTilesetInfo.Tiles[High(FTilesetInfo.Tiles)];
    Tile^.Id := 0;
    SetLength(Tile^.Anim.Frames, 0);
    Tile^.Props.Init;
    FTileReader.FTileInfo := Tile;
    Parser^.SetReader(@FTileReader);
  end else
    IgnoreSubtags(Parser);
end;

procedure TTiledTilesetReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'firstgid' then begin
    ParseI32(Value, FTilesetInfo.FirstGid);
  end else if Name = 'source' then begin
    AnsiBufferReInit(FTilesetInfo.Source, Value);
  end else if Name = 'name' then begin
    AnsiBufferReInit(FTilesetInfo.Name, Value);
  end else if Name = 'tilewidth' then begin
    ParseI32(Value, FTilesetInfo.TileWidth);
  end else if Name = 'tileheight' then begin
    ParseI32(Value, FTilesetInfo.TileHeight);
  end else if Name = 'spacing' then begin
    ParseI32(Value, FTilesetInfo.Spacing);
  end else if Name = 'margin' then begin
    ParseI32(Value, FTilesetInfo.Margin);
  end else if Name = 'tilecount' then begin
    ParseI32(Value, FTilesetInfo.TileCount);
  end else if Name = 'columns' then begin
    ParseI32(Value, FTilesetInfo.Columns);
  end;
end;

procedure TTiledTilesetReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnTilesetInfo <> nil then
    FOnTilesetInfo(@FTilesetInfo);
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledTSXReader.Init;
begin
  inherited Init(nil);
  FTilesetReader.Init(@Self);
end;

destructor TTiledTSXReader.Done;
begin
  FTilesetReader.Done;
  inherited Done;
end;

procedure TTiledTSXReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'tileset' then begin
    Parser^.SetReader(@FTilesetReader);
  end else
    IgnoreSubtags(Parser);
end;

constructor TTiledImageLayerReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FOnImageLayer := nil;
  FImageReader.Init(@Self, @FImageLayer.Image);
  FPropertiesReader.Init(@Self, @FImageLayer.Props);
  FImageLayer.Id := 0;
  FImageLayer.Name.Init;
  FImageLayer.OffsetX := 0;
  FImageLayer.OffsetY := 0;
  FImageLayer.Opacity := 1;
  FImageLayer.Visible := 1;
  FImageLayer.Image.Format.Init;
  FImageLayer.Image.Id.Init;
  FImageLayer.Image.Source.Init;
  FImageLayer.Image.Trans := 0;
  FImageLayer.Image.Width := 0;
  FImageLayer.Image.Height := 0;
  FImageLayer.Props.Init;
end;

destructor TTiledImageLayerReader.Done;
begin
  FImageLayer.Name.Done;
  FImageLayer.Image.Format.Done;
  FImageLayer.Image.Id.Done;
  FImageLayer.Image.Source.Done;
  FImageLayer.Props.Done;
  FImageReader.Done;
  FPropertiesReader.Done;
  inherited Done;
end;

procedure TTiledImageLayerReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
var
  OpacityTemp: Double;
  S: PAnsiChar;
begin
  if Name = 'id' then begin
    ParseI32(Value, FImageLayer.Id);
  end else if Name = 'name' then begin
    AnsiBufferReInit(FImageLayer.Name, Value);
  end else if Name = 'offsetx' then begin
    ParseI32(Value, FImageLayer.OffsetX);
  end else if Name = 'offsety' then begin
    ParseI32(Value, FImageLayer.OffsetY);
  end else if Name = 'x' then begin
    ParseI32(Value, FImageLayer.OffsetX);
  end else if Name = 'y' then begin
    ParseI32(Value, FImageLayer.OffsetY);
  end else if Name = 'opacity' then begin
    S := Value.Data;
    if ParseJsonFloat64(S, Value.Tail, OpacityTemp) then
      FImageLayer.Opacity := OpacityTemp;
  end else if Name = 'visible' then begin
    ParseI32(Value, FImageLayer.Visible);
  end;
end;

procedure TTiledImageLayerReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'image' then begin
    Parser^.SetReader(@FImageReader);
  end else if Name = 'properties' then begin
    Parser^.SetReader(@FPropertiesReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

procedure TTiledImageLayerReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnImageLayer <> nil then
    FOnImageLayer(@FImageLayer);
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledLayerDataReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FOnTile := nil;
end;

procedure TTiledLayerDataReader.OnContent(Parser: PXmlParser; const Content: TAnsiView);
var
  S: PAnsiChar;
  Line, LineEnd: PAnsiChar;
  Value, ValueEnd: PAnsiChar;
  X, Y: LongInt;
  Tile: LongInt;
begin
  S := Content.Data;
  Y := 0;
  while NextLine(S, Content.Tail, Line, LineEnd) do begin
    X := 0;
    while NextCSV(Line, LineEnd, Value, ValueEnd) do begin
      ParseI32(AnsiView(Value, ValueEnd), Tile);
      if Tile <> 0 then begin
        FOnTile(@PTiledLayerReader(ParentReader)^.FLayerInfo, X, Y, Tile);
      end;
      Inc(X);
    end;
    Inc(Y);
  end;
end;

constructor TTiledLayerReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FLayerDataReader.Init(@Self);
  FLayerInfo.Id := 0;
  FLayerInfo.Name.Init('Unnamed layer');
  FLayerInfo.X := 0;
  FLayerInfo.Y := 0;
  FLayerInfo.Width := 100;
  FLayerInfo.Height := 100;
  FLayerInfo.Opacity := 1;
  FLayerInfo.Visible := 1;
  FLayerInfo.OffsetX := 0;
  FLayerInfo.OffsetY := 0;
  FOnLayerStart := nil;
  FOnLayerEnd := nil;
end;

destructor TTiledLayerReader.Done;
begin
  FLayerInfo.Name.Done;
  FLayerDataReader.Done;
  inherited Done;
end;

procedure TTiledLayerReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'data' then begin
    Parser^.SetReader(@FLayerDataReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

procedure TTiledLayerReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
var
  OpacityTemp: Double;
  S: PAnsiChar;
begin
  if Name = 'id' then begin
    ParseI32(Value, FLayerInfo.Id);
  end else if Name = 'name' then begin
    AnsiBufferReInit(FLayerInfo.Name, Value);
  end else if Name = 'x' then begin
    ParseI32(Value, FLayerInfo.X);
  end else if Name = 'y' then begin
    ParseI32(Value, FLayerInfo.Y);
  end else if Name = 'width' then begin
    ParseI32(Value, FLayerInfo.Width);
  end else if Name = 'height' then begin
    ParseI32(Value, FLayerInfo.Height);
  end else if Name = 'opacity' then begin
    S := Value.Data;
    if ParseJsonFloat64(S, Value.Tail, OpacityTemp) then
      FLayerInfo.Opacity := OpacityTemp;
  end else if Name = 'visible' then begin
    ParseI32(Value, FLayerInfo.Visible);
  end else if Name = 'offsetx' then begin
    ParseI32(Value, FLayerInfo.OffsetX);
  end else if Name = 'offsety' then begin
    ParseI32(Value, FLayerInfo.OffsetY);
  end;
end;

procedure TTiledLayerReader.OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnLayerStart <> nil then
    FOnLayerStart(@FLayerInfo);
end;

procedure TTiledLayerReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnLayerEnd <> nil then
    FOnLayerEnd(@FLayerInfo);
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledObjectReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FProperties.Init;
  FPropertiesReader.Init(@Self, @FProperties);
  FObject.Id := 0;
  FObject.Name.Init('');
  FObject._Type.Init('');
  FObject.X := 0;
  FObject.Y := 0;
  FObject.Width := 0;
  FObject.Height := 0;
  FObject.Rotation := 0.0;
  FObject.Gid := 0;
  FObject.Visible := 0;
  FObject.Template.Init('');
  FOnObject := nil;
end;

destructor TTiledObjectReader.Done;
begin
  FObject.Name.Done;
  FObject._Type.Done;
  FObject.Template.Done;
  FProperties.Done;
  FPropertiesReader.Done;
  inherited Done;
end;

procedure TTiledObjectReader.Reset;
begin
  FObject.Id := 0;
  FObject.Name.ReInit('');
  FObject._Type.ReInit('');
  FObject.X := 0;
  FObject.Y := 0;
  FObject.Width := 0;
  FObject.Height := 0;
  FObject.Rotation := 0.0;
  FObject.Gid := 0;
  FObject.Visible := 0;
  FObject.Template.ReInit('');
end;

procedure TTiledObjectReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'properties' then begin
    FProperties.Reset;
    Parser^.SetReader(@FPropertiesReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

procedure TTiledObjectReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
var
  S: PAnsiChar;
  Temp: Double;
begin
  if Name = 'id' then begin
    ParseI32(Value, FObject.Id);
  end else if Name = 'name' then begin
    AnsiBufferReInit(FObject.Name, Value);
  end else if Name = 'type' then begin
    AnsiBufferReInit(FObject._Type, Value);
  end else if Name = 'x' then begin
    ParseI32(Value, FObject.X);
  end else if Name = 'y' then begin
    ParseI32(Value, FObject.Y);
  end else if Name = 'width' then begin
    ParseI32(Value, FObject.Width);
  end else if Name = 'height' then begin
    ParseI32(Value, FObject.Height);
  end else if Name = 'rotation' then begin
    S := Value.Data;
    if ParseJsonFloat64(S, Value.Tail, Temp) then
      FObject.Rotation := Temp;
  end else if Name = 'gid' then begin
    ParseU32(Value, FObject.Gid);
  end else if Name = 'visible' then begin
    ParseI32(Value, FObject.Visible);
  end else if Name = 'template' then begin
    AnsiBufferReInit(FObject.Template, Value);
  end;
end;

procedure TTiledObjectReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnObject <> nil then
    FOnObject(@PTiledObjectGroupReader(ParentReader)^.FObjectGroupInfo, @FObject, @FProperties);
  FProperties.Reset;
  Reset;
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledObjectGroupReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FObjectReader.Init(@Self);
  FProperties.Init;
  FPropertiesReader.Init(@Self, @FProperties);
  FCallbackCalled := False;
  FObjectGroupInfo.Id := 0;
  FObjectGroupInfo.Name.Init('Unnamed object group');
  FObjectGroupInfo.Color := $ffffffff;
  FObjectGroupInfo.X := 0;
  FObjectGroupInfo.Y := 0;
  FObjectGroupInfo.Width := 0;
  FObjectGroupInfo.Height := 0;
  FObjectGroupInfo.Opacity := 1.0;
  FObjectGroupInfo.Visible := 1;
  FObjectGroupInfo.OffsetX := 0;
  FObjectGroupInfo.OffsetY := 0;
  FObjectGroupInfo.DrawOrder.Init('topdown');
end;

destructor TTiledObjectGroupReader.Done;
begin
  FObjectGroupInfo.Name.Done;
  FObjectGroupInfo.DrawOrder.Done;
  FPropertiesReader.Done;
  FProperties.Done;
  FObjectReader.Done;
  inherited Done;
end;

procedure TTiledObjectGroupReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if (Name <> 'properties') and not FCallbackCalled then begin
    if FOnObjectGroupStart <> nil then
      FOnObjectGroupStart(@FObjectGroupInfo, @FProperties);
    FCallbackCalled := True;
  end;
  if Name = 'object' then begin
    //FObjectReader.Reset;
    Parser^.SetReader(@FObjectReader);
  end else if Name = 'properties' then begin
    Parser^.SetReader(@FPropertiesReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

procedure TTiledObjectGroupReader.OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView);
begin
end;

procedure TTiledObjectGroupReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
var
  S: PAnsiChar;
  OpacityTemp: Double;
begin
  if Name = 'id' then begin
    ParseI32(Value, FObjectGroupInfo.Id);
  end else if Name = 'name' then begin
    AnsiBufferReInit(FObjectGroupInfo.Name, Value);
  end else if Name = 'color' then begin
    ParseColor(Value, FObjectGroupInfo.Color);
  end else if Name = 'x' then begin
    ParseI32(Value, FObjectGroupInfo.X);
  end else if Name = 'y' then begin
    ParseI32(Value, FObjectGroupInfo.Y);
  end else if Name = 'width' then begin
    ParseI32(Value, FObjectGroupInfo.Width);
  end else if Name = 'height' then begin
    ParseI32(Value, FObjectGroupInfo.Height);
  end else if Name = 'opacity' then begin
    S := Value.Data;
    if ParseJsonFloat64(S, Value.Tail, OpacityTemp) then
      FObjectGroupInfo.Opacity := OpacityTemp;
  end else if Name = 'visible' then begin
    ParseI32(Value, FObjectGroupInfo.Visible);
  end else if Name = 'offsetx' then begin
    ParseI32(Value, FObjectGroupInfo.OffsetX);
  end else if Name = 'offsety' then begin
    ParseI32(Value, FObjectGroupInfo.OffsetY);
  end else if Name = 'draworder' then begin
    AnsiBufferReInit(FObjectGroupInfo.Name, Value);
  end;
end;

procedure TTiledObjectGroupReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if not FCallbackCalled then begin
    if FOnObjectGroupStart <> nil then
      FOnObjectGroupStart(@FObjectGroupInfo, @FProperties);
  end;
  if FOnObjectGroupEnd <> nil then
    FOnObjectGroupEnd(@FObjectGroupInfo);
  inherited OnCloseTag(Parser, Name);
end;

constructor TTiledMapReader.Init(aParentReader: PXmlReader);
begin
  inherited Init(aParentReader);
  FTilesetReader.Init(@Self);
  FLayerReader.Init(@Self);
  FObjectGroupReader.Init(@Self);
  FImageLayerReader.Init(@Self);
  FOnHeader := nil;
  FOnMapProperties := nil;
  FHeader.Version.Init('0');
  FHeader.TiledVersion.Init('0');
  FHeader.Orientation.Init('orthogonal');
  FHeader.RenderOrder.Init('right-down');
  FHeader.Width := 1;
  FHeader.Height := 1;
  FHeader.TileWidth := 64;
  FHeader.TileHeight := 64;
  FHeader.Infinite := 0;
  FHeader.NextLayerId := 0;
  FHeader.NextObjectId := 0;
  FHeader.Properties.Init;
  FPropertiesReader.Init(@Self, @FHeader.Properties);
end;

destructor TTiledMapReader.Done;
begin
  FPropertiesReader.Done;
  FHeader.Properties.Done;
  FHeader.Version.Done;
  FHeader.TiledVersion.Done;
  FHeader.Orientation.Done;
  FHeader.RenderOrder.Done;
  FProperties.Done;
  FImageLayerReader.Done;
  FObjectGroupReader.Done;
  FLayerReader.Done;
  FTilesetReader.Done;
  inherited Done;
end;

procedure TTiledMapReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  inherited OnOpenTagBegin(Parser, Name);
  if Name = 'tileset' then begin
    Parser^.SetReader(@FTilesetReader);
  end else if Name = 'layer' then begin
    Parser^.SetReader(@FLayerReader);
  end else if Name = 'objectgroup' then begin
    FObjectGroupReader.FCallbackCalled := False;
    FObjectGroupReader.FProperties.Reset;
    Parser^.SetReader(@FObjectGroupReader);
  end else if Name = 'imagelayer' then begin
    Parser^.SetReader(@FImageLayerReader);
  end else if Name = 'properties' then begin
    Parser^.SetReader(@FPropertiesReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

procedure TTiledMapReader.OnAttribute(Parser: PXmlParser; const Name, Value: TAnsiView);
begin
  if Name = 'version' then begin
    AnsiBufferReInit(FHeader.Version, Value);
  end else if Name = 'tiledversion' then begin
    AnsiBufferReInit(FHeader.TiledVersion, Value);
  end else if Name = 'orientation' then begin
    AnsiBufferReInit(FHeader.Orientation, Value);
  end else if Name = 'renderorder' then begin
    AnsiBufferReInit(FHeader.RenderOrder, Value);
  end else if Name = 'width' then begin
    ParseI32(Value, FHeader.Width);
  end else if Name = 'height' then begin
    ParseI32(Value, FHeader.Height);
  end else if Name = 'tilewidth' then begin
    ParseI32(Value, FHeader.TileWidth);
  end else if Name = 'tileheight' then begin
    ParseI32(Value, FHeader.TileHeight);
  end else if Name = 'infinite' then begin
    ParseI32(Value, FHeader.Infinite);
  end else if Name = 'backgroundcolor' then begin
    ParseColor(Value, FHeader.BackgroundColor);
  end else if Name = 'nextlayerid' then begin
    ParseI32(Value, FHeader.NextLayerId);
  end else if Name = 'nextobjectid' then begin
    ParseI32(Value, FHeader.NextObjectId);
  end;
end;

procedure TTiledMapReader.OnOpenTagEnd(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnHeader <> nil then
    FOnHeader(@FHeader);
end;

procedure TTiledMapReader.OnCloseTag(Parser: PXmlParser; const Name: TAnsiView);
begin
  if FOnMapProperties <> nil then
    FOnMapProperties(@FHeader.Properties);
  inherited OnCloseTag(Parser, Name);
end;

function TTiledReader.GetOnTileObject: TOnTiledObject;
begin
  Result := FMapReader.FTilesetReader.FTileReader.FObjectGroupReader.FObjectReader.FOnObject;
end;

procedure TTiledReader.SetOnTileObject(aOnTileObject: TOnTiledObject);
begin
  FMapReader.FTilesetReader.FTileReader.FObjectGroupReader.FObjectReader.FOnObject := aOnTileObject;
end;

constructor TTiledReader.Init;
begin
  inherited Init(nil);
  FMapReader.Init(@Self);
end;

destructor TTiledReader.Done;
begin
  FMapReader.Done;
  inherited Done;
end;

procedure TTiledReader.OnOpenTagBegin(Parser: PXmlParser; const Name: TAnsiView);
begin
  if Name = 'map' then begin
    Parser^.SetReader(@FMapReader);
  end else begin
    IgnoreSubtags(Parser);
  end;
end;

end.
