unit djsonserializer;

//
//  Serializes data as json.
//

{$MODE FPC}
{$MODESWITCH RESULT}

interface

uses
  dstrnum, // TODO get rid of dstrnum
  dparser,
  dansiview,
  dserializer,
  dstream,
  dstream_json,
  dtinyjson,
  dtinyjson_utils;

type
TJsonSerializer = object(TSerializer)
private
  FData: TData;
  FSpacesCount: LongInt;
  FSpaces: AnsiString;
  FNeedComma: Boolean;
  function FailedWrite: Boolean;
  function CommaIfNeeded: Boolean;
  function NewLine: Boolean;
  function WriteStr(S: PAnsiChar): Boolean;
  function WriteStr(S: PAnsiChar; L: SizeUInt): Boolean;
  property Stream: PStream read FData.FStream;
public
  constructor Init(Data: TData);
  destructor Done; virtual;

  procedure Attr(Name: PAnsiChar; var Value: Int32; Def: Int32); virtual; overload;
end;

TJsonDeserializer = object(TDeserializer)
private
  FData: TData;
  FTiny: TTinyJsonState;
  FName: TAnsiView;
  FMem: Pointer;
  FSize: SizeUInt;
  property Stream: PStream read FData.FStream;
public
  constructor Init(Data: TData);
  destructor Done; virtual;

  function Next: Boolean; virtual;
  function Name: TAnsiView; virtual;

  procedure Attr(var Value: Int32; Def: Int32); virtual; overload;
end;

implementation

function TJsonSerializer.FailedWrite: Boolean;
begin
  Exit(Error(2, 'failed write'));
end;

function TJsonSerializer.CommaIfNeeded: Boolean;
begin
  if FNeedComma then
    Exit(WriteStr(','));
  Exit(True);
end;

function TJsonSerializer.NewLine: Boolean;
begin
  // TODO comma
  // TODO spaces
  Exit(WriteStr(#13#10));
end;

function TJsonSerializer.WriteStr(S: PAnsiChar): Boolean;
begin
  Exit(Stream^.WriteBuffer(S^, StrLen(S)));
end;

function TJsonSerializer.WriteStr(S: PAnsiChar; L: SizeUInt): Boolean;
begin
  Exit(Stream^.WriteBuffer(S^, L));
end;

constructor TJsonSerializer.Init(Data: TData);
var
  I: LongInt;
begin
  inherited Init;
  FData := Data;
  if not Data.ReadyWritable then begin
    Error(1, 'not opened');
    Exit;
  end;
  SetLength(FSpaces, 32);
  for I := 1 to High(FSpaces) do
    FSpaces[I] := ' ';
  if not WriteStr('{') then
    FailedWrite;
  FNeedComma := False;
end;

destructor TJsonSerializer.Done;
begin
  if (not NewLine)
  or (not WriteStr('}')) then
    FailedWrite;
  FData.Done;
  inherited Done;
end;

procedure TJsonSerializer.Attr(Name: PAnsiChar; var Value: Int32; Def: Int32);
begin
  if Value <> Def then begin
    if (not CommaIfNeeded)
    or (not NewLine)
    or (not WriteStr('"'))
    or (not WriteEscapedJsonString(Stream, Name, Name + StrLen(Name)))
    or (not WriteStr('": '))
    or (not WriteStr(PAnsiChar(IntToStr(Value)))) then begin
      FailedWrite;
      Exit;
    end;
    FNeedComma := True;
  end;
end;

constructor TJsonDeserializer.Init(Data: TData);
begin
  inherited Init;
  FData := Data;
  if not FData.ReadyReadable then begin
    Error(1, 'not opened');
    Exit;
  end;
  if not Stream^.ReadToMemory(FMem, FSize) then begin
    Error(1, 'failed read');
    Exit;
  end;
  TinyJsonInit(FTiny, FMem, FMem + FSize);
  while True do begin
    case TinyJsonNext(FTiny) of
      TINYJSON_ERROR: begin
        Error(1, 'bad start');
        Exit;
      end;
      TINYJSON_END: begin
        Error(1, 'unexpected end');
        Exit;
      end;
      TINYJSON_OBJECT_BEGIN: begin
        // ok
        Exit;
      end;
      TINYJSON_OBJECT_END,
      TINYJSON_ARRAY_BEGIN,
      TINYJSON_ARRAY_END,
      TINYJSON_KEY,
      TINYJSON_STRING,
      TINYJSON_NUMBER,
      TINYJSON_FALSE,
      TINYJSON_TRUE,
      TINYJSON_NULL: begin
        Error(1, 'not object');
        Exit;
      end;
    end;
  end;
end;

destructor TJsonDeserializer.Done;
begin
  FData.Done;
  inherited Done;
end;

function TJsonDeserializer.Next: Boolean;
begin
  if TinyJsonNextKey(FTiny) then begin
    FName := AnsiView(FTiny.Key, FTiny.KeyLength);
    Exit(True);
  end else
    Exit(False);
  {while True do begin
    case TinyJsonNext(FTiny) of
      TINYJSON_ERROR: begin
        Error(1, 'bad json');
        Writeln(stderr, FTiny.Line, ' ', FTiny.Pos, ' ', FTiny.ErrorMsg);
        Exit(False);
      end;
      TINYJSON_END: begin
        Exit(False);
      end;
      TINYJSON_KEY: begin
        FName := AnsiView(FTiny.Key, FTiny.KeyLength);
        // TODO read member type here
        Exit(True);
      end;
      TINYJSON_OBJECT_BEGIN,
      TINYJSON_OBJECT_END,
      TINYJSON_ARRAY_BEGIN,
      TINYJSON_ARRAY_END,
      TINYJSON_STRING,
      TINYJSON_NUMBER,
      TINYJSON_FALSE,
      TINYJSON_TRUE,
      TINYJSON_NULL: begin
        Error(1, 'bad parsing');
        Exit(False);
      end;
    end;
  end;}
end;

function TJsonDeserializer.Name: TAnsiView;
begin
  Result := FName;
end;

procedure TJsonDeserializer.Attr(var Value: Int32; Def: Int32);
var
  S: PAnsiChar;
begin
  case TinyJsonNext(FTiny) of
    TINYJSON_NUMBER: begin
      S := FTiny.Value;
      if not ParseDecimalI32(S, FTiny.ValueEnd, Value) then begin
        Value := Def;
      end;
    end;
  else
    Value := Def;
  end;
end;

end.
