uses
  dvector2i,
  dstrnum,
  dbitmem,
  dfilereader,
  dzlib_decompress_utils,
  dswf_primitives,
  dswf;

var
  //FILENAME: AnsiString = 'shapes.swf';
  //FILENAME: AnsiString = 'stone0_01.swf';
  //FILENAME: AnsiString = 'worms_fisherman0 tests.swf';
  FILENAME: AnsiString = 'worms_hungryman0.swf';

var
  Uncompressed, UncompressedEnd: Pointer;
  Shape: LongInt;

function GetPos(Data: Pointer; Bits: PtrInt): AnsiString;
begin
  Result := IntToStr(Data - Uncompressed) + ':' + IntToStr(8 - Bits);
end;

function Error(Code: LongInt; Msg: PAnsiChar): Boolean;
begin
  Writeln('ERROR: ', Msg);
  Exit(Code = 0);
end;

function AsUInt16(Data: Pointer): UInt16; inline;
begin
  Result := {$IF Defined(ENDIAN_BIG)}SwapEndian{$ENDIF}(UInt16(Data^));
end;

function AsUInt32(Data: Pointer): UInt32; inline;
begin
  Result := {$IF Defined(ENDIAN_BIG)}SwapEndian{$ENDIF}(UInt32(Data^));
end;

function RECTToString(const R: TSwfRECT): AnsiString;
begin
  Result := 'Xmin=' + IntToStr(R.Xmin) + ' ' +
            'Xmax=' + IntToStr(R.Xmax) + ' ' +
            'Ymin=' + IntToStr(R.Ymin) + ' ' +
            'Ymax=' + IntToStr(R.Ymax);
end;

function FIXED8ToString(I: TSwfFIXED8): AnsiString;
begin
  Result := FloatToStr(SwfFIXED8ToSingle(I), 8);
end;

function ColorToString(Color: UInt32): AnsiString;
var
  B: array[0 .. 3] of Byte;
begin
  Move(Color, B[0], 4);
  Result := '#' + HexStr(B[0], 2)  + HexStr(B[1], 2)
                + HexStr(B[2], 2)  + HexStr(B[3], 2);
end;

function MatrixToString(const Matrix: TSwfMATRIX): AnsiString;
begin
  Result := FloatToStr(Matrix[0], 2) + ' ' +
            FloatToStr(Matrix[1], 2) + ' ' +
            FloatToStr(Matrix[2], 2) + ' ' +
            FloatToStr(Matrix[3], 2) + ' ' +
            FloatToStr(Matrix[4], 2) + ' ' +
            FloatToStr(Matrix[5], 2);
end;

function CxformToString(const Cxform: TSwfCXFORM): AnsiString;
begin
  Result := 'HasAddTerms=' + IntToStr(Ord(Cxform.HasAddTerms)) +
            ' HasMultTerms=' + IntToStr(Ord(Cxform.HasMultTerms)) +
            ' HasAlpha=' + IntToStr(Ord(Cxform.HasAlpha));
  if Cxform.HasAddTerms then begin
    Result := Result + ' Add=(' + IntToStr(Cxform.Add[0]) + ',' + IntToStr(Cxform.Add[1]) + ',' + IntToStr(Cxform.Add[2]) + ',' + IntToStr(Cxform.Add[3]) + ')';
  end;
  if Cxform.HasMultTerms then begin
    Result := Result + ' Mult=(' + IntToStr(Cxform.Mult[0]) + ',' + IntToStr(Cxform.Mult[1]) + ',' + IntToStr(Cxform.Mult[2]) + ',' + IntToStr(Cxform.Mult[3]) + ')';
  end;
end;

function FillStyleTypeToString(_Type: PtrUInt): AnsiString;
begin
  case _Type of
    SWF_FILLSTYLE_SOLID: Exit('Solid');
    SWF_FILLSTYLE_LINEAR_GRADIENT: Exit('LinearGradient');
    SWF_FILLSTYLE_RADIAL_GRADIENT: Exit('RadialGradient');
    SWF_FILLSTYLE_FOCAL_GRADIENT: Exit('FocalGradient');
    SWF_FILLSTYLE_REPEAT_BITMAP: Exit('RepeatBitmap');
    SWF_FILLSTYLE_CLIPPED_BITMAP: Exit('ClippedBitmap');
    SWF_FILLSTYLE_NONSMOOTH_REPEAT_BITMAP: Exit('NonsmoothRepeatBitmap');
    SWF_FILLSTYLE_NONSMOOTH_CLIPPED_BITMAP: Exit('NonsmoothClippedBitmap');
  else
    Exit('Unknown');
  end;
end;

function GradientToString(const Gradient: TSwfGRADIENT): AnsiString;
begin
  Result := 'SpreadMode=' + IntToStr(Gradient.SpreadMode) + ' InterpolationMode=' + IntToStr(Gradient.InterpolationMode) + ' NumGradients=' + IntToStr(Gradient.NumGradients) + ' GradientRecords=TODO';
end;

function FocalGradientToString(const FocalGradient: TSwfFOCALGRADIENT): AnsiString;
begin
  Result := 'SpreadMode=' + IntToStr(FocalGradient.SpreadMode) + ' InterpolationMode=' + IntToStr(FocalGradient.InterpolationMode) + ' NumGradients=' + IntToStr(FocalGradient.NumGradients) + ' GradientRecords=TODO ' + ' FocalPoint=' + FIXED8ToString(FocalGradient.FocalPoint);
end;

function DumpFILLSTYLEARRAY(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
var
  FillStyleCount: UInt16;
  I: LongInt;
  FillStyle: TSwfFILLSTYLE;
begin
  FillStyleCount := PByte(Data)^;
  if (FillStyleCount = $ff) and (Shape > 1) then begin
    FillStyleCount := PUInt16(Data + 1)^;
    Inc(Data, 3);
  end;
    Inc(Data, 1);
  Writeln('  FILLSTYLEARRAY (', FillStyleCount, '):');
  for I := 0 to FillStyleCount - 1 do begin
    if not SwfFILLSTYLE_Begin(Data, DataEnd, Shape, FillStyle, Data) then
      Exit(Error(1, 'bad FILLSTYLE'));
    FillStyle.Memory := GetMem(FillStyle.NeedMemory);
    if not SwfFILLSTYLE_End(Data, DataEnd, Shape, FillStyle, Data) then
      Exit(Error(1, 'bad FILLSTYLE'));
    case FillStyle._Type of
      SWF_FILLSTYLE_SOLID: begin
          Writeln('    ', I, ': Solid(Color=', ColorToString(FillStyle.Color) + ')');
        end;
      SWF_FILLSTYLE_LINEAR_GRADIENT,
      SWF_FILLSTYLE_RADIAL_GRADIENT: begin
          Writeln('    ', I, ': ', FillStyleTypeToString(FillStyle._Type), '(', MatrixToString(FillStyle.GradientMatrix) + ', ' + GradientToString(FillStyle.Gradient) + ')');
        end;
      SWF_FILLSTYLE_FOCAL_GRADIENT: begin
          Writeln('    ', I, ': ', FillStyleTypeToString(FillStyle._Type), '(', MatrixToString(FillStyle.GradientMatrix) + ', ' + FocalGradientToString(FillStyle.Focal) + ')');
        end;
      SWF_FILLSTYLE_REPEAT_BITMAP,
      SWF_FILLSTYLE_CLIPPED_BITMAP,
      SWF_FILLSTYLE_NONSMOOTH_REPEAT_BITMAP,
      SWF_FILLSTYLE_NONSMOOTH_CLIPPED_BITMAP: begin
          Writeln('    ', I, ': ', FillStyleTypeToString(FillStyle._Type), '(BitmapId=', FillStyle.BitmapId, ' BitmapMatrix=(', MatrixToString(FillStyle.BitmapMatrix), '))');
        end;
    end;
  end;
  NextData := Data;
  Exit(True);
end;

function DumpLINESTYLEARRAY(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
var
  LineStyleCount: UInt16;
  I: LongInt;
  LineStyle: TSwfLINESTYLE2;
begin
  LineStyleCount := PByte(Data)^;
  if (LineStyleCount = $ff) and (Shape > 1) then begin
    LineStyleCount := PUInt16(Data + 1)^;
    Inc(Data, 3);
  end;
    Inc(Data, 1);
  Writeln('  LINESTYLEARRAY (', LineStyleCount, '):');
  for I := 0 to LineStyleCount - 1 do begin
    if not SwfLINESTYLE(Data, DataEnd, Shape, LineStyle, Data) then
      Exit(False);
    Write('    Width=' + IntToStr(LineStyle.Width), ' ');
    Write('Color=' + ColorToString(LineStyle.Color));
    Writeln;
  end;
  NextData := Data;
  Exit(True);
end;

function DumpLINESTYLE2ARRAY(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
var
  LineStyleCount: UInt16;
  I: LongInt;
  LineStyle: TSwfLINESTYLE2;
begin
  LineStyleCount := PByte(Data)^;
  if (LineStyleCount = $ff) and (Shape > 1) then begin
    LineStyleCount := PUInt16(Data + 1)^;
    Inc(Data, 3);
  end;
    Inc(Data, 1);
  Writeln('  LINESTYLEARRAY (', LineStyleCount, '):');
  for I := 0 to LineStyleCount - 1 do begin
    if not SwfLINESTYLE2_Begin(Data, DataEnd, LineStyle, Data) then
      Exit(Error(1, 'bad LINESTYLE2'));
    LineStyle.Memory := GetMem(LineStyle.NeedMemory);
    if not SwfLINESTYLE2_End(Data, DataEnd, LineStyle, Data) then
      Exit(Error(1, 'bad LINESTYLE2'));
    Write('    ', I, ': Width=' + IntToStr(LineStyle.Width));
    Write(' Color=' + ColorToString(LineStyle.Color));
    Write(' StartCapStyle=', LineStyle.StartCapStyle);
    Write(' JoinStyle=', LineStyle.JoinStyle);
    Write(' EndCapStyle=', LineStyle.EndCapStyle);
    if LineStyle.JoinStyle = SWF_JOIN_MITER then
      Write(' MiterLimitFactor=', FIXED8ToString(LineStyle.MiterLimitFactor));
    Writeln;
    if LineStyle.HasFillFlag then begin
      Writeln('      FILL=TODO');
    end;
    FreeMem(LineStyle.Memory);
  end;
  NextData := Data;
  Exit(True);
end;

function DumpSHAPEWITHSTYLE(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
var
  NumFillBits, NumLineBits: Byte;
  Bits: PtrInt;
  TempData: Pointer;
  TempBits: PtrInt;
  StyleChangeRecord: TSwfStyleChangeRecord;
  EdgeRecord: TSwfEdgeRecord;
  Cur: TVec2i;
begin
  Result := False; // make compiler happy
  if not DumpFILLSTYLEARRAY(Data, DataEnd, Data) then
    Exit(False);
  if Shape < 4 then begin
    if not DumpLINESTYLEARRAY(Data, DataEnd, Data) then
      Exit(False);
  end else begin
    if not DumpLINESTYLE2ARRAY(Data, DataEnd, Data) then
      Exit(False);
  end;

  NumFillBits := PByte(Data)^ shr 4;
  NumLineBits := PByte(Data)^ and $f;
  Inc(Data);
  Writeln('  NumFillBits=' + IntToStr(NumFillBits) + ' NumLineBits=' + IntToStr(NumLineBits));

  Cur := Vec2i(0, 0);
  Writeln('  Shapes');
  Bits := 8;
  while (Data < DataEnd) or (Bits > 0) do begin
    if ReadBit(Data, Bits, Data, Bits) = 0 then begin // non-edge record
      if ReadBits(Data, Bits, 5, TempData, TempBits) = 0 then begin // EndShapeRecord
        NextData := AlignBits(TempData, TempBits);
        Writeln('    EndShapeRecord');
        Exit(True);
      end;
      if not SwfStyleChangeRecord(Data, DataEnd, Bits, NumFillBits, NumLineBits, StyleChangeRecord, Data, Bits) then
        Exit(Error(1, 'bad StyleChangeRecord'));
      //Writeln('    StyleChangeRecord StateNewStyles=', Ord(StyleChangeRecord.StateNewStyles), ' StateLineStyle=', Ord(StyleChangeRecord.StateLineStyle), ' StateFillStyle1=', Ord(StyleChangeRecord.StateFillStyle1), ' StateFillStyle0=', Ord(StyleChangeRecord.StateFillStyle0), ' StateMoveTo=', Ord(StyleChangeRecord.StateMoveTo));
      if StyleChangeRecord.StateMoveTo then begin
        Cur := Vec2i(0, 0) + Vec2i(StyleChangeRecord.MoveDeltaX,
                                   StyleChangeRecord.MoveDeltaY);
        //Writeln('      MoveDelta: ', StyleChangeRecord.MoveDeltaX, ' ', StyleChangeRecord.MoveDeltaY, ' twips (', FloatToStr(Cur.X / 20, 2), ' ', FloatToStr(Cur.Y / 20, 2), ')');
      end;
      //if StyleChangeRecord.StateFillStyle1 then
        //Writeln('      FillStyle1: ', StyleChangeRecord.FillStyle1);
      //if StyleChangeRecord.StateFillStyle0 then
      //  Writeln('      FillStyle0: ', StyleChangeRecord.FillStyle0);
      //if StyleChangeRecord.StateLineStyle then
      //  Writeln('      LineStyle: ', StyleChangeRecord.LineStyle);
      if StyleChangeRecord.StateNewStyles then begin
        Data := AlignBits(Data, Bits);
        Bits := 8;
        if not DumpFILLSTYLEARRAY(Data, DataEnd, Data) then
          Exit(False);
        if Shape < 4 then begin
          if not DumpLINESTYLEARRAY(Data, DataEnd, Data) then
            Exit(False);
        end else begin
          if not DumpLINESTYLE2ARRAY(Data, DataEnd, Data) then
            Exit(False);
        end;
        NumFillBits := ReadBits(Data, Bits, 4, Data, Bits);
        NumLineBits := ReadBits(Data, Bits, 4, Data, Bits);
      end;
    end else begin
      if not SwfEdgeRecord(Data, DataEnd, Bits, EdgeRecord, Data, Bits) then
        Exit(Error(1, 'bad EdgeRecord'));
      //Write('    EdgeRecord');
      if EdgeRecord.Curved then begin
        Cur := Cur + Vec2i(EdgeRecord.AnchorDeltaX,
                           EdgeRecord.AnchorDeltaY);
        //Write(' cubic');
        //Write(' ', EdgeRecord.ControlDeltaX, ' ', EdgeRecord.ControlDeltaY);
        //Write(' ', EdgeRecord.AnchorDeltaX, ' ', EdgeRecord.AnchorDeltaY);
        //Write(' twips (');
        //Write(' ', FloatToStr(Cur.X / 20, 2), ' ', FloatToStr(Cur.Y / 20, 2));
        //Write(')');
      end else begin
        Cur := Cur + Vec2i(EdgeRecord.DeltaX,
                           EdgeRecord.DeltaY);
        //Write(' line');
        //Write(' ', EdgeRecord.DeltaX, ' ', EdgeRecord.DeltaY, ' twips (', FloatToStr(Cur.X / 20, 2), ' ', FloatToStr(Cur.Y / 20, 2), ')');
      end;
      //Writeln;
    end;
  end;
  if Bits < 8 then
    Inc(Data);

  Exit(True);
end;

function DumpFilters(Data, DataEnd: Pointer; out N: Byte; out NextData: Pointer): Boolean;
var
  I: PtrInt;
  F: TSwfFILTER;
begin
  // TODO check limits
  N := PByte(Data)^;
  Inc(Data);
  Writeln('  Filters: ' + IntToStr(N));
  for I := 0 to PtrInt(N) - 1 do begin
    if not SwfFILTER(Data, DataEnd, F, Data) then begin
      Writeln('ERROR bad filter ' + IntToStr(F.FilterID));
      Exit(False);
    end;
    Writeln('    FILTER ' + IntToStr(F.FilterID));
  end;
  NextData := Data;
  Exit(True);
end;

function DumpButton(Data, DataEnd: Pointer; Version: LongInt): Boolean;
label
  LActions;
var
  ButtonRecord: TSwfButtonRecord;
  Hit, Down, Over, Up, Atoms: LongInt;
begin
  if Version = 1 then begin
    Writeln('DefineButton Begin ', AsUInt16(Data));
  end else
    Writeln('DefineButton2 Begin ', AsUInt16(Data));
  Inc(Data, 2);
  Writeln('  Flags: ', PByte(Data)^);
  Inc(Data);
  Writeln('  ActionOffset: ', AsUInt16(Data));
  Inc(Data, 2);
  Hit := 0;
  Down := 0;
  Over := 0;
  Up := 0;
  Atoms := 0;
  while Data < DataEnd do begin // BUTTONRECORDs
    if PByte(Data)^ = 0 then begin // CharacterEndFlag
      if Hit = 0 then
        Writeln('WARNING no hit');
      if Down = 0 then
        Writeln('WARNING no down');
      if Over = 0 then
        Writeln('WARNING no over');
      if Up = 0 then
        Writeln('WARNING no up');
      Inc(Data);
      goto LActions;
    end;
    if not SwfButtonRecord_Begin(Data, DataEnd, Version, ButtonRecord, Data) then begin
      Writeln('ERROR bad ButtonRecord');
      Exit(False);
    end;
    if (Version = 2) and ButtonRecord.ButtonHasFilterList then begin
      if not DumpFilters(Data, DataEnd, ButtonRecord.NumberOfFilters, Data) then
        Exit(False);
    end;
    if not SwfButtonRecord_End(Data, DataEnd, Version, ButtonRecord, Data) then begin
      Writeln('ERROR bad ButtonRecord ending');
      Exit(False);
    end;
    Inc(Atoms);
    Writeln('  ButtonRecord ' + IntToStr(ButtonRecord.CharacterId) + ' hit=' + IntToStr(Ord(ButtonRecord.ButtonStateHitTest)) + ' down=' + IntToStr(Ord(ButtonRecord.ButtonStateDown)) + ' over=' + IntToStr(Ord(ButtonRecord.ButtonStateOver)) + ' up=' + IntToStr(Ord(ButtonRecord.ButtonStateUp)));
    // TODO fill B^.C
    if ButtonRecord.ButtonStateHitTest then begin
      Inc(Hit);
    end;
    if ButtonRecord.ButtonStateDown then begin
      Inc(Down);
    end;
    if ButtonRecord.ButtonStateOver then begin
      Inc(Over);
    end;
    if ButtonRecord.ButtonStateUp then begin
      Inc(Up);
    end;
    Writeln('    Flags:       ' + IntToStr(ButtonRecord.Flags));
    Writeln('    PlaceMatrix: ' + MatrixToString(ButtonRecord.PlaceMatrix));
    Writeln('    PlaceDepth:  ' + IntToStr(ButtonRecord.PlaceDepth));
    Writeln('  Done ButtonRecord ' + IntToStr(ButtonRecord.CharacterId));
  end;
  Writeln('ERROR unexpected end of button');

LActions:
  Writeln('    ButtonRecords: ' + IntToStr(Atoms) + ' (hit=' + IntToStr(Hit) + ' down=' + IntToStr(Down) + ' over=' + IntToStr(Over) + ' up=' + IntToStr(Up) + ')');
  if PByte(Data)^ <> 0 then begin
    Writeln('WARNING Actions are not supported for buttons yet');
  end;
  //NextData := Data;
  if Version = 1 then begin
    Writeln('DefineButton End');
  end else
    Writeln('DefineButton2 End');
  Exit(True);
end;

function ReadTags(Data, DataEnd: Pointer): Boolean;
var
  ShapeBounds, EdgeBounds: TSwfRECT;
  TagBody, TagEnd: Pointer;
  EOS: PAnsiChar;
  S: AnsiString;
  Matrix: TSwfMATRIX;
  Cxform: TSwfCXFORM;
  PlaceObject2: TSwfPlaceObject2;
  SceneCount, FrameLabelCount: UInt32;
  Offset, FrameNum: UInt32;
  I: LongInt;
  Flags: Byte;
begin
  while Data + 2 <= DataEnd do begin
    case SwfTag(Data, DataEnd, TagBody, TagEnd) of
      -1: Exit(Error(1, 'unexpected end'));
      TAG_PlaceObject: begin
        Writeln('PlaceObject ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Writeln('  CharacterId: ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        Writeln('  Depth:       ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        if not SwfMATRIX(TagBody, TagEnd, Matrix, TagBody) then
          Exit(Error(1, 'bad MATRIX'));
        Writeln('  Matrix: ' + MatrixToString(Matrix));
        if TagBody < TagEnd then begin
          if not SwfCXFORM(TagBody, TagEnd, Cxform, TagBody) then
            Exit(Error(1, 'bad CXFORM'));
          Writeln('  ColorTransform: ' + CxformToString(Cxform));
        end;
      end;
      TAG_PlaceObject2: begin
        Writeln('PlaceObject2 ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        if not SwfPlaceObject2(TagBody, TagEnd, PlaceObject2, TagBody) then
          Exit(Error(1, 'bad PlaceObject2'));
        Writeln('  Flags: ' +
                   'PlaceFlagHasClipActions=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasClipActions)) + ' ' +
                   'PlaceFlagHasClipDepth=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasClipDepth)) + ' ' +
                   'PlaceFlagHasName=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasName)) + ' ' +
                   'PlaceFlagHasRatio=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasRatio)) + ' ' +
                   'PlaceFlagHasColorTransform=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasColorTransform)) + ' ' +
                   'PlaceFlagHasMatrix=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasMatrix)) + ' ' +
                   'PlaceFlagHasCharacter=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasCharacter)) + ' ' +
                   'PlaceFlagHasMove=' + IntToStr(Ord(PlaceObject2.PlaceFlagHasMove)));
        Writeln('  Depth: ' + IntToStr(PlaceObject2.Depth));
        if PlaceObject2.PlaceFlagHasCharacter then
          Writeln('  CharacterId: ' + IntToStr(PlaceObject2.CharacterId));
        if PlaceObject2.PlaceFlagHasMatrix then
          Writeln('  Matrix: ' + MatrixToString(PlaceObject2.Matrix));
        if PlaceObject2.PlaceFlagHasColorTransform then
          Writeln('  ColorTransform: ' + CxformToString(PlaceObject2.ColorTransform));
        if PlaceObject2.PlaceFlagHasRatio then
          Writeln('  Ratio: ' + IntToStr(PlaceObject2.Ratio));
        if PlaceObject2.PlaceFlagHasName then begin
          SetString(S, PlaceObject2.Name, PlaceObject2.NameEnd - PlaceObject2.Name);
          Writeln('  Name: ', S);
        end;
        if PlaceObject2.PlaceFlagHasClipDepth then
          Writeln('  Ratio: ' + IntToStr(PlaceObject2.ClipDepth));
        if PlaceObject2.PlaceFlagHasClipActions then
          Writeln('  ClipActions: TODO');
      end;
      TAG_PlaceObject3: begin
        Writeln('TAG_PlaceObject3');
      end;
      TAG_RemoveObject: begin
        Writeln('TAG_RemoveObject');
      end;
      TAG_RemoveObject2: begin
        Writeln('TAG_RemoveObject2');
      end;
      TAG_ShowFrame: begin
        Writeln('ShowFrame ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
      end;
      TAG_SetBackgroundColor: begin
        if TagEnd - TagBody >= 3 then begin
          Writeln('SetBackgroundColor: ', ColorToString(UInt32(TagBody^) or MASK_ALPHA));
        end else begin
          Writeln('WARNING: SetBackgroundColor needs at least 3 bytes');
        end;
      end;
      TAG_FrameLabel: begin
        if not SwfSTRING(TagBody, TagEnd, EOS) then begin
          Writeln('WARNING: no FrameLabel string');
          Data := TagEnd;
          continue;
        end;
        SetString(S, TagBody, EOS - TagBody);
        Writeln('FrameLabel "', S, '"');
      end;
      TAG_Protect: begin
        Writeln('TAG_Protect');
      end;
      TAG_End: begin
        Writeln('End ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Exit(True);
      end;
      TAG_ExportAssets: begin
        Writeln('TAG_ExportAssets');
      end;
      TAG_ImportAssets: begin
        Writeln('TAG_ImportAssets');
      end;
      TAG_EnableDebugger: begin
        Writeln('TAG_EnableDebugger');
      end;
      TAG_EnableDebugger2: begin
        Writeln('TAG_EnableDebugger2');
      end;
      TAG_ScriptLimits: begin
        Writeln('TAG_ScriptLimits');
      end;
      TAG_SetTabIndex: begin
        Writeln('TAG_SetTabIndex');
      end;
      TAG_FileAttributes: begin
        Writeln('FileAttributes:'
          + ' Reserved=' + IntToStr((PByte(TagBody)^ shr 7) and 1)
          + ' UseDirectBlit=' + IntToStr((PByte(TagBody)^ shr 6) and 1)
          + ' UseGPU=' + IntToStr((PByte(TagBody)^ shr 5) and 1)
          + ' HasMetadata=' + IntToStr((PByte(TagBody)^ shr 4) and 1)
          + ' ActionScript3=' + IntToStr((PByte(TagBody)^ shr 3) and 1)
          + ' Reserved=' + IntToStr((PByte(TagBody)^ shr 1) and 3)
          + ' UseNetwork=' + IntToStr((PByte(TagBody)^ shr 0) and 1)
          + ' Reserved=' + IntToStr(PByte(TagBody)[1]) + ' ' + IntToStr(PByte(TagBody)[2]) + ' ' + IntToStr(PByte(TagBody)[3]) + ' ');
      end;
      TAG_ImportAssets2: begin
        Writeln('TAG_ImportAssets2');
      end;
      TAG_SymbolClass: begin
        Writeln('TAG_SymbolClass');
      end;
      TAG_Metadata: begin // ignore
        if not SwfSTRING(TagBody, TagEnd, EOS) then begin
          Writeln('WARNING: Metadata is not null-terminated');
          EOS := TagEnd;
        end;
        SetString(S, TagBody, TagEnd - TagBody);
        Writeln('Metadata: ', S);
      end;
      TAG_DefineScalingGrid: begin
        Writeln('TAG_DefineScalingGrid');
      end;
      TAG_DefineSceneAndFrameLabelData: begin
        Writeln('DefineSceneAndFrameLabelData ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        SceneCount := SwfEncodedU32(TagBody, TagEnd, TagBody);
        Writeln('  Scenes (', SceneCount, '):');
        for I := 0 to SceneCount - 1 do begin
          Offset := SwfEncodedU32(TagBody, TagEnd, TagBody);
          if not SwfSTRING(TagBody, TagEnd, EOS) then
            Exit(Error(1, 'infinite string'));
          SetString(S, TagBody, EOS - TagBody);
          Writeln('    ', I, ': ', Offset, ' "', S, '"');
          TagBody := EOS;
        end;
        FrameLabelCount := SwfEncodedU32(TagBody, TagEnd, TagBody);
        Writeln('  FrameLabels (', FrameLabelCount, '):');
        for I := 0 to FrameLabelCount - 1 do begin
          FrameNum := SwfEncodedU32(TagBody, TagEnd, TagBody);
          if not SwfSTRING(TagBody, TagEnd, EOS) then
            Exit(Error(1, 'infinite string'));
          SetString(S, TagBody, EOS - TagBody);
          Writeln('    ', I, ': ', FrameNum, ' "', S, '"');
          TagBody := EOS;
        end;
      end;
      TAG_DoAction: begin
        Writeln('TAG_DoAction');
      end;
      TAG_DoInitAction: begin
        Writeln('TAG_DoInitAction');
      end;
      TAG_DoABC: begin
        Writeln('TAG_DoABC');
      end;
      TAG_DefineShape: begin
        Shape := 1;
        Writeln('DefineShape ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Writeln('  ShapeId: ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        if not SwfRECT(TagBody, TagEnd, ShapeBounds, TagBody) then
          Exit(Error(1, 'bad ShapeBounds'));
        Writeln('  ShapeBounds: ', RECTToString(ShapeBounds));
        if not DumpSHAPEWITHSTYLE(TagBody, TagEnd, TagBody) then
          Exit(False);
      end;
      TAG_DefineShape2: begin
        Shape := 2;
        Writeln('DefineShape2 ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Writeln('  ShapeId: ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        if not SwfRECT(TagBody, TagEnd, ShapeBounds, TagBody) then
          Exit(Error(1, 'bad ShapeBounds'));
        Writeln('  ShapeBounds: ', RECTToString(ShapeBounds));
        if not DumpSHAPEWITHSTYLE(TagBody, TagEnd, TagBody) then
          Exit(False);
      end;
      TAG_DefineShape3: begin
        Shape := 3;
        Writeln('DefineShape3 ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Writeln('  ShapeId: ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        if not SwfRECT(TagBody, TagEnd, ShapeBounds, TagBody) then
          Exit(Error(1, 'bad ShapeBounds'));
        Writeln('  ShapeBounds: ', RECTToString(ShapeBounds));
        if not DumpSHAPEWITHSTYLE(TagBody, TagEnd, TagBody) then
          Exit(False);
      end;
      TAG_DefineShape4: begin
        Shape := 4;
        Writeln('DefineShape4 ', GetPos(Data, 8), '-', GetPos(TagEnd, 8));
        Writeln('  ShapeId: ', AsUInt16(TagBody));
        Inc(TagBody, 2);
        if not SwfRECT(TagBody, TagEnd, ShapeBounds, TagBody) then
          Exit(Error(1, 'bad ShapeBounds'));
        Writeln('  ShapeBounds: ', RECTToString(ShapeBounds));
        if not SwfRECT(TagBody, TagEnd, EdgeBounds, TagBody) then
          Exit(Error(1, 'bad EdgeBounds'));
        Writeln('  EdgeBounds: ', RECTToString(EdgeBounds));
        Flags := PByte(TagBody)^;
        Writeln('  Flags: UsesFillWindingRule=' + IntToStr((Flags shr 2) and 1) + ' UsesNonScalingStrokes=' + IntToStr((Flags shr 1) and 1) + ' UsesScalingStrokes=' + IntToStr((Flags shr 0) and 1));
        Inc(TagBody);
        if not DumpSHAPEWITHSTYLE(TagBody, TagEnd, TagBody) then
          Exit(False);
      end;
      TAG_DefineBits: begin
        Writeln('TAG_DefineBits');
      end;
      TAG_JPEGTables: begin
        Writeln('TAG_JPEGTables');
      end;
      TAG_DefineBitsJPEG2: begin
        Writeln('TAG_DefineBitsJPEG2');
      end;
      TAG_DefineBitsJPEG3: begin
        Writeln('TAG_DefineBitsJPEG3');
      end;
      TAG_DefineBitsLossless: begin
        Writeln('TAG_DefineBitsLossless');
      end;
      TAG_DefineBitsLossless2: begin
        Writeln('TAG_DefineBitsLossless2');
      end;
      TAG_DefineBitsJPEG4: begin
        Writeln('TAG_DefineBitsJPEG4');
      end;
      TAG_DefineMorphShape: begin
        Writeln('TAG_DefineMorphShape');
      end;
      TAG_DefineMorphShape2: begin
        Writeln('TAG_DefineMorphShape2');
      end;
      TAG_DefineFont: begin
        Writeln('TAG_DefineFont');
      end;
      TAG_DefineFontInfo: begin
        Writeln('TAG_DefineFontInfo');
      end;
      TAG_DefineFontInfo2: begin
        Writeln('TAG_DefineFontInfo2');
      end;
      TAG_DefineFont2: begin
        Writeln('TAG_DefineFont2');
      end;
      TAG_DefineFont3: begin
        Writeln('TAG_DefineFont3');
      end;
      TAG_DefineFontAlignZones: begin
        Writeln('TAG_DefineFontAlignZones');
      end;
      TAG_DefineFontName: begin
        Writeln('TAG_DefineFontName');
      end;
      TAG_DefineText: begin
        Writeln('TAG_DefineText');
      end;
      TAG_DefineText2: begin
        Writeln('TAG_DefineText2');
      end;
      TAG_DefineEditText: begin
        Writeln('TAG_DefineEditText');
      end;
      TAG_CSMTextSettings: begin
        Writeln('TAG_CSMTextSettings');
      end;
      TAG_DefineFont4: begin
        Writeln('TAG_DefineFont4');
      end;
      TAG_DefineSound: begin
        Writeln('TAG_DefineSound');
      end;
      TAG_StartSound: begin
        Writeln('TAG_StartSound');
      end;
      TAG_StartSound2: begin
        Writeln('TAG_StartSound2');
      end;
      TAG_SoundStreamHead: begin
        Writeln('TAG_SoundStreamHead');
      end;
      TAG_SoundStreamHead2: begin
        Writeln('TAG_SoundStreamHead2');
      end;
      TAG_SoundStreamBlock: begin
        Writeln('TAG_SoundStreamBlock');
      end;
      TAG_DefineButton: begin
        Writeln('TAG_DefineButton');
      end;
      TAG_DefineButton2: begin
        DumpButton(TagBody, TagEnd, 2);
      end;
      TAG_DefineButtonCxform: begin
        Writeln('TAG_DefineButtonCxform');
      end;
      TAG_DefineButtonSound: begin
        Writeln('TAG_DefineButtonSound');
      end;
      TAG_DefineSprite: begin
        Writeln('DefineSprite BEGIN');
        Writeln('SpriteID: ', AsUInt16(TagBody));
        Writeln('FrameCount: ', AsUInt16(TagBody + 2));
        if not ReadTags(TagBody + 4, TagEnd) then begin
          Exit(Error(1, 'bad DefineSprite'));
        end;
        Writeln('DefineSprite END');
      end;
      TAG_DefineVideoStream: begin
        Writeln('TAG_DefineVideoStream');
      end;
      TAG_VideoFrame: begin
        Writeln('TAG_VideoFrame');
      end;
      TAG_EnableTelemetry: begin
        Writeln('TAG_EnableTelemetry');
      end;
      TAG_DefineBinaryData: begin
        Writeln('TAG_DefineBinaryData');
      end;
    else
      Writeln('Error: unknown tag ' + IntToStr(SwfTag(Data, DataEnd, TagBody, TagEnd)));
    end;
    Data := TagEnd;
  end;
  Exit(Error(1, 'no End tag'));
end;

function DumpUncompressed(Data, DataEnd: Pointer): Boolean;
var
  FrameSize: TSwfRECT;
  FrameRate: TSwfFIXED8;
  FrameCount: UInt16;
begin
  if not SwfRECT(Data, DataEnd, FrameSize, Data) then
    Exit(Error(1, 'could not read FrameSize'));

  Write('FrameSize:  ', RECTToString(FrameSize));
  if (FrameSize.Xmin <> 0) or (FrameSize.Ymin <> 0) then
    Write(' (WARNING: both Xmin and Ymin must be 0)');
  Writeln;

  FrameRate := SwfFIXED8(Data);
  Inc(Data, 2);
  Writeln('FrameRate:  ', FIXED8ToString(FrameRate));

  FrameCount := AsUInt16(Data);
  Inc(Data, 2);
  Writeln('FrameCount: ', IntToStr(FrameCount));

  Exit(ReadTags(Data, DataEnd));
end;

function DumpSwf(Data, DataEnd: Pointer): Boolean;
var
  Header: TSwfHeader;
  Body: Pointer;
begin
  if not SwfHeader(Data, DataEnd, Header, Body) then
    Exit(Error(1, 'not SWF'));
  Writeln(PAnsiChar(Data)^, PAnsiChar(Data + 1)^, PAnsiChar(Data + 2)^, ' v', Header.Version, ', ', Header.Size);
  if Header.Version > 19 then
    Exit(Error(1, 'too high version'));

  case Header.Compression of
    SWF_COMPRESS_NO: begin
      if Header.Size <> DataEnd - Data then
        Exit(Error(1, 'bad size'));
      Uncompressed := Body;
      UncompressedEnd := DataEnd;
      if not DumpUncompressed(Body, DataEnd) then
        Exit(False);
    end;
    SWF_COMPRESS_ZLIB: begin
      Uncompressed := GetMem(Header.Size);
      if Uncompressed = nil then
        Exit(Error(1, 'no memory'));
      if not ZLibDecompressAll(Body, DataEnd, Uncompressed, Uncompressed + Header.Size, UncompressedEnd) then
        Exit(Error(1, 'corrupted'));
      if not DumpUncompressed(Uncompressed, UncompressedEnd) then
        Exit(False);
    end;
    SWF_COMPRESS_LZMA:
      Exit(Error(1, 'LZMA underdeveloped')); // LZMA
  else
    Exit(Error(1, 'unknown compression'));
  end;

  Exit(True);
end;

var
  Data: Pointer;
  DataLen: SizeUint;
begin
  if ParamCount > 0 then begin
    FILENAME := ParamStr(1);
  end;
  if not ReadFileContent(FILENAME, 0, PAnsiChar(Data), DataLen) then begin
    Writeln(stderr, 'Could not open ', FILENAME);
    Halt(1);
  end;
  if not DumpSwf(Data, Data + DataLen) then begin
    Halt(1);
  end;
  FreeMem(Data);
end.
