unit Bcl.Json.Reader;

{$I Bcl.Inc}
{$INLINE ON}{$R-}{$Q-}

interface

uses
  Classes, SysUtils;

type
  TCharArrayND = Array [0 .. 65536] of char; // will go faster than a dyn array.
  PCharArrayND = ^TCharArrayND;

  TJsonStreamReader = class // todo: this might be later unified with TXmlStreamReader
  public
    type
      EInvalidJsonInput = class(Exception)
      public
        constructor Create;
      end;
      EInternalError = class(Exception)
      public
        constructor Create;
      end;
      EEndOfInputReached = class(Exception)
      public
        constructor Create;
      end;
  private
  const
    StreamBuffSize = 4192;
  var
    FStream: TStream;
    FEncoding: TEncoding;
    UsingUTF16: boolean;
    BigEndian: boolean;
    Buffer: PCharArrayND;
    BuffAllocSize: integer;
    BuffPos: integer;
    BuffLen: integer;
    StreamBuff: TBytes;
    StreamBuffLen: integer;
    EofReached: boolean;
    procedure DetectEncoding;
    procedure FillBuffer;
    procedure AddToBuffer(const a: array of byte);
    function IsContinuation(K: integer): boolean; inline;
    function IsStartOfContinuation(K: integer): boolean; inline;
  public
    constructor Create(const aStream: TStream);
    destructor Destroy; override;
    function NextChar: char; inline;
    function NextCharNotEOF: char; inline;
    function PeekChar: char; inline;
    procedure Backup(const c: char);
    procedure MoveNext(const Count: integer = 1); inline;
    function NextString(const len: integer): string;
    function Eof: boolean; inline;
    function CharsInBuffer: integer; inline;
  end;

  TJsonToken = (BeginObject, EndObject, BeginArray, EndArray,
    Name, Boolean, Null, Text, Number, EOF);

  TJsonReader = class
  strict private
    // Json parsing state machine algorithm based on Gson which api interface
    // is the most similar to TJsonReader
    type
      TJsonState = (None, BeginObject, EndObject, BeginArray, EndArray, True, False,
        Null, DoubleQuoted, Buffered, DoubleQuotedName, Int64, Number, EOF);
      TNumberState = (None, Sign, Digit, Decimal, Fraction, ExpE, ExpSign, ExpDigit);
      TJsonScope = (EmptyDocument, EmptyArray, EmptyObject, NonEmptyDocument,
        NonEmptyArray, NonEmptyObject, DanglingName);
    const
      JsonStateNames: array[TJsonState] of string =
        ('None', 'BeginObject', 'EndObject', 'BeginArray', 'EndArray', 'True', 'False',
        'Null', 'DoubleQuoted', 'Buffered', 'DoubleQuotedName', 'Int64', 'Number', 'EOF');
  public
    type
      EInvalidStateException = class(Exception)
      public
        constructor Create(const Expected, Actual: TJsonState);
      end;
      EUnterminatedArray = class(Exception)
      public
        constructor Create;
      end;
      EUnterminatedObject = class(Exception)
      public
        constructor Create;
      end;
      ENameExpected = class(Exception)
      public
        constructor Create;
      end;
      EColonExpected = class(Exception)
      public
        constructor Create;
      end;
      EReaderClosed = class(Exception)
      public
        constructor Create;
      end;
      EMultipleRootNotAllowed = class(Exception)
      public
        constructor Create;
      end;
      EExpectedValue = class(Exception)
      public
        constructor Create;
      end;
      EObjectOrArrayExpected = class(Exception)
      public
        constructor Create;
      end;
      ETooManyDepthLevels = class(Exception)
      public
        constructor Create;
      end;
      EInvalidEscaped = class(Exception)
      public
        constructor Create;
      end;
    const
      MaxNumberBuffer = 255;
      MaxStackSize = 255;
  private
    FReader: TJsonStreamReader;
    FStack: array[0..MaxStackSize] of TJsonScope;
    FStackSize: integer;
    FPeeked: TJsonState;
    FPeekedInt64: Int64;
    FPeekedNumber: array[0..MaxNumberBuffer] of Char;
    FPeekedString: string;
    FFormatSettings: TFormatSettings;
    function NextPeek: TJsonState; inline;
    procedure CheckState(const State: TJsonState); inline;
    procedure SkipChar; inline;
    function IsLiteral(C: Char): boolean;
    function IsDigit(C: Char): boolean; inline;
    function DoPeek: TJsonState;
    procedure PushScope(const Scope: TJsonScope);
    function NextNonWhitespace: Char;
    function PeekKeyword: TJsonState;
    function PeekNumber: TJsonState;
    function InternalReadQuoted(const BuildString: boolean): string;
    function ReadQuoted: string;
    function ReadEscaped: Char;
    procedure SkipQuoted;
  private
    function SkipWhitespaceUntilEnd: boolean;
    procedure CopyBufferToString(const LocalTagPos: integer; var TagName: string; StartBuffPos: integer; Buff: PCharArrayND); inline;
    procedure PositionAtCurrentPos(var StartBuffPos: integer; var Buff: PCharArrayND;
      var StillInBuff: integer); inline;
  public
    constructor Create(const AStream: TStream);
    destructor Destroy; override;
    procedure ReadBeginArray;
    procedure ReadEndArray;
    procedure ReadBeginObject;
    procedure ReadEndObject;
    function ReadName: string;
    function ReadString: string;
    function ReadBoolean: boolean;
    function ReadDouble: double;
    function ReadInt64: Int64;
    function ReadInteger: integer;
    procedure SkipValue;
    procedure ReadNull;
    function HasNext: boolean;
    function Peek: TJsonToken;
    function IsNull: boolean;
    function Eof: boolean;
    function IsFloat: Boolean;
    function IsInt64: Boolean;
  end;

implementation

uses
  Bcl.Utils;

const
  Wspace: Set of byte = [$20, $A, $D, $9, $C];

type
  TEncoding_UTF8 = class(TUTF8Encoding)
  public
    function GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
  end;

  TEncoding_Unicode = class(TUnicodeEncoding)
  public
    function GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
  end;

  TEncoding_BigEndianUnicode = class(TBigEndianUnicodeEncoding)
  public
    function GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
  end;

{ TJsonReader }

constructor TJsonReader.Create(const aStream: TStream);
begin
  inherited Create;
  TBclUtils.InitFormatSettings(FFormatSettings);
  FFormatSettings.DecimalSeparator := '.';
  FReader := TJsonStreamReader.Create(aStream);
  if FReader.Eof then
    FPeeked := TJsonState.Eof
  else
    FPeeked := TJsonState.None;
  FStack[0] := TJsonScope.EmptyDocument;
  FStackSize := 1;
end;

destructor TJsonReader.Destroy;
begin
  FReader.Free;
  inherited;
end;

function TJsonReader.Eof: boolean;
begin
  Result := (Peek = TJsonToken.EOF);
end;

function TJsonReader.Peek: TJsonToken;
begin
  case NextPeek of
    TJsonState.BeginObject:
      Result := TJsonToken.BeginObject;

    TJsonState.EndObject:
      Result := TJsonToken.EndObject;

    TJsonState.BeginArray:
      Result := TJsonToken.BeginArray;

    TJsonState.EndArray:
      Result := TJsonToken.EndArray;

    TJsonState.DoubleQuotedName:
      Result := TJsonToken.Name;

    TJsonState.True,
    TJsonState.False:
      Result := TJsonToken.Boolean;

    TJsonState.Null:
      Result := TJsonToken.Null;

    TJsonState.DoubleQuoted,
    TJsonState.Buffered:
      Result := TJsonToken.Text;

    TJsonState.Int64,
    TJsonState.Number:
      Result := TJsonToken.Number;

    TJsonState.EOF:
      Result := TJsonToken.EOF;
  else
    Assert(false);
    Result := TJsonToken.EOF; // to avoid warnings, this should never happen
  end;
end;

function TJsonReader.PeekKeyword: TJsonState;
begin
  // don't need to create looping logic just for three small keywords
  case FReader.PeekChar of
    't', 'T':
      begin
        FReader.MoveNext;
        case FReader.NextChar of
          'r', 'R': case FReader.NextChar of
            'u', 'U': case FReader.NextChar of
              'e', 'E':
                if FReader.EOF or not IsLiteral(FReader.PeekChar) then
                  Exit(TJsonState.True);
            end;
          end;
        end;
      end;
    'f', 'F':
      begin
        FReader.MoveNext;
        case FReader.NextChar of
          'a', 'A': case FReader.NextChar of
            'l', 'L': case FReader.NextChar of
              's', 'S': case FReader.NextChar of
                'e', 'E':
                  if FReader.EOF or not IsLiteral(FReader.PeekChar) then
                    Exit(TJsonState.False);
              end;
            end;
          end;
        end;
      end;
    'n', 'N':
      begin
        FReader.MoveNext;
        case FReader.NextChar of
          'u', 'U': case FReader.NextChar of
            'l', 'L': case FReader.NextChar of
              'l', 'L':
                if FReader.EOF or not IsLiteral(FReader.PeekChar) then
                  Exit(TJsonState.Null);
            end;
          end;
        end;
      end;
  else
    Exit(TJsonState.None);
  end;
  raise EExpectedValue.Create;
end;

function TJsonReader.PeekNumber: TJsonState;
const
  MinIncompleteInteger = Low(Int64) div 10;
var
  Last: TNumberState;
  Negative: boolean;
  FitsInInt64: boolean;
  Value: Int64;
  NewValue: Int64;
  C: Char;
  BufIndex: integer;
begin
  C := FReader.PeekChar;
  if (C <> '-') and not IsDigit(C) then
    Exit(TJsonState.None);

  Negative := false;
  FitsInInt64 := true;
  Last := TNumberState.None;
  BufIndex := 0;
  Value := -1; // just a value different than zero
  repeat
    if BufIndex >= MaxNumberBuffer then
      raise EExpectedValue.Create;
    if FReader.Eof then
      break;
    C := FReader.NextChar;
    FPeekedNumber[BufIndex] := C;
    Inc(BufIndex);
    case C of
      '-':
        if Last = TNumberState.None then
        begin
          Negative := true;
          Last := TNumberState.Sign;
          Continue;
        end
        else
        if Last = TNumberState.ExpE then
        begin
          Last := TNumberState.ExpSign;
          Continue;
        end
        else
          raise EExpectedValue.Create;
      '+':
        if Last = TNumberState.ExpE then
        begin
          Last := TNumberState.ExpSign;
          Continue;
        end
        else
          raise EExpectedValue.Create;
      'e', 'E':
        if Last in [TNumberState.Digit, TNumberState.Fraction] then
        begin
          Last := TNumberState.ExpE;
          Continue;
        end
        else
          raise EExpectedValue.Create;
      '.':
        if Last = TNumberState.Digit then
        begin
          Last := TNumberState.Decimal;
          Continue;
        end
        else
          raise EExpectedValue.Create;
    else
      if not IsDigit(C) then
        if not IsLiteral(C) then
        begin
          FReader.Backup(C);
          Dec(BufIndex);
          Break;
        end
        else
          raise EExpectedValue.Create;

      if Last in [TNumberState.Sign, TNumberState.None] then
      begin
        Value := -(Ord(C) - 48);
        Last := TNumberState.Digit
      end
      else
      if Last = TNumberState.Digit then
      begin
        if Value = 0 then
          raise EExpectedValue.Create; // numbers cannot have leading zeros
        NewValue := Value * 10 - (Ord(C) - 48);
        FitsInInt64 := FitsInInt64 and (
            (Value > MinIncompleteInteger)
             or ((Value = MinIncompleteInteger) and (NewValue < Value))
          );
        Value := NewValue;
      end
      else
      if Last = TNumberState.Decimal then
        Last := TNumberState.Fraction
      else
      if Last in [TNumberState.ExpE, TNumberState.ExpSign] then
        Last := TNumberState.ExpDigit;
    end;
  until false;

  if (Last = TNumberState.Digit) and FitsInInt64 and ((Value <> Low(Int64)) or Negative) then
  begin
    if Negative then
      FPeekedInt64 := Value
    else
      FPeekedInt64 := -Value;
    Exit(TJsonState.Int64);
  end
  else
  if Last in [TNumberState.Digit, TNumberState.Fraction, TNumberState.ExpDigit] then
  begin
    FPeekedNumber[BufIndex] := #0;
    Exit(TJsonState.Number);
  end
  else
    raise EExpectedValue.Create;
end;

procedure TJsonReader.PositionAtCurrentPos(var StartBuffPos: integer;
  var Buff: PCharArrayND; var StillInBuff: integer);
begin
  StillInBuff := FReader.CharsInBuffer;
  if StillInBuff <= 0 then
    raise TJsonStreamReader.EEndOfInputReached.Create;
  Buff := FReader.Buffer;
  StartBuffPos := FReader.BuffPos;
end;

procedure TJsonReader.PushScope(const Scope: TJsonScope);
begin
  if FStackSize > MaxStackSize then
    raise ETooManyDepthLevels.Create;
  FStack[FStackSize] := Scope;
  Inc(FStackSize);
end;

procedure TJsonReader.CopyBufferToString(const LocalTagPos: integer; var TagName: string; StartBuffPos: integer; Buff: PCharArrayND);
var
  LV: integer;
begin
  LV := Length(TagName);
  if LocalTagPos <= LV then
    exit;

  SetLength(TagName, LocalTagPos);
  System.Move(Buff[StartBuffPos], TagName[1 + LV], (LocalTagPos - LV) * SizeOf(char));
end;

function TJsonReader.HasNext: boolean;
begin
  Result := not (NextPeek in [TJsonState.EndObject, TJsonState.EndArray]);
end;

function TJsonReader.IsDigit(C: Char): boolean;
begin
  Result := (C <= #255) and (byte(C) in [Ord('0')..Ord('9')]);
end;

function TJsonReader.IsFloat: Boolean;
begin
  Result := (NextPeek = TJsonState.Number);
end;

function TJsonReader.IsInt64: Boolean;
begin
  Result := (NextPeek = TJsonState.Int64);
end;

function TJsonReader.IsLiteral(C: Char): boolean;
begin
  {$IFDEF NEXTGEN} {$WARNINGS OFF} {$ENDIF}
  Result := not CharInSet(C, ['/', '\', ';', '#', '{', '}', '[', ']', ':', ',', ' ', #13, #10, #12, #9]);
  {$IFDEF NEXTGEN} {$WARNINGS ON} {$ENDIF}
end;

function TJsonReader.IsNull: boolean;
begin
  Result := (Peek = TJsonToken.Null);
end;

function TJsonReader.NextNonWhitespace: Char;
var
  StartBuffPos, StillInBuff: integer;
  Buff: PCharArrayND;
begin
  PositionAtCurrentPos(StartBuffPos, Buff, StillInBuff);
  repeat
    if (Buff[StartBuffPos] > #32) or not(byte(Buff[StartBuffPos]) in Wspace) then
    begin;
      FReader.BuffPos := StartBuffPos;
      Exit(Buff[StartBuffPos]);
    end;

    Dec(StillInBuff);
    Inc(StartBuffPos);
    if StillInBuff <= 0 then
    begin
      FReader.BuffPos := StartBuffPos;
      PositionAtCurrentPos(StartBuffPos, Buff, StillInBuff);
      Continue;
    end;
  until False;
end;

function TJsonReader.NextPeek: TJsonState;
begin
  if FPeeked = TJsonState.None then
    FPeeked := DoPeek;
  Result := FPeeked;
end;

procedure TJsonReader.SkipQuoted;
begin
  InternalReadQuoted(false);
end;

procedure TJsonReader.SkipValue;
var
  Count: integer;
begin
  Count := 0;
  repeat
    case NextPeek of
      TJsonState.BeginArray:
        begin
          PushScope(TJsonScope.EmptyArray);
          Inc(Count);
        end;
      TJsonState.BeginObject:
        begin
          PushScope(TJsonScope.EmptyObject);
          Inc(Count);
        end;
      TJsonState.EndArray, TJsonState.EndObject:
        begin
          Dec(FStackSize);
          Dec(Count);
        end;
      TJsonState.DoubleQuoted, TJsonState.DoubleQuotedName:
        SkipQuoted;
    end;
    FPeeked := TJsonState.None;
  until Count <= 0;
end;

procedure TJsonReader.SkipChar;
begin
  FReader.MoveNext;
end;

function TJsonReader.DoPeek: TJsonState;
var
  FPeekStack: TJsonScope;
  C: Char;
begin
  FPeekStack := FStack[FStackSize - 1];
  if FPeekStack = TJsonScope.EmptyArray then
    FStack[FStackSize - 1] := TJsonScope.NonEmptyArray
  else
  if FPeekStack = TJsonScope.NonEmptyArray then
  begin
    C := NextNonWhitespace;
    case C of
      ']':
        begin
          SkipChar;
          FPeeked := TJsonState.EndArray;
          Exit(FPeeked);
        end;
      ',':
         SkipChar;
    else
      raise EUnterminatedArray.Create;
    end;
  end
  else
  if FPeekStack in [TJsonScope.EmptyObject, TJsonScope.NonEmptyObject] then
  begin
    FStack[FStackSize - 1] := TJsonScope.DanglingName;
    if FPeekStack = TJsonScope.NonEmptyObject then
    begin
      C := NextNonWhitespace;
      case C of
        '}':
          begin
            SkipChar;
            FPeeked := TJsonState.EndObject;
            Exit(FPeeked);
          end;
        ',': SkipChar;
      else
        raise EUnterminatedObject.Create;
      end;
    end;
    C := NextNonWhitespace;
    case C of
      '"':
        begin
          SkipChar;
          FPeeked := TJsonState.DoubleQuotedName;
          Exit(FPeeked);
        end;
      '}':
        if FPeekStack <> TJsonScope.NonEmptyObject then
        begin
          SkipChar;
          FPeeked := TJsonState.EndObject;
          Exit(FPeeked);
        end else
          raise ENameExpected.Create;
    else
      raise ENameExpected.Create;
    end;
  end
  else
  if FPeekStack = TJsonScope.DanglingName then
  begin
    FStack[FStackSize - 1] := TJsonScope.NonEmptyObject;
    C := NextNonWhitespace;
    if C = ':' then
      SkipChar
    else
      raise EColonExpected.Create;
  end
  else
  if FPeekStack = TJsonScope.EmptyDocument then
    FStack[FStackSize - 1] := TJsonScope.NonEmptyDocument
  else
  if FPeekStack = TJsonScope.NonEmptyDocument then
  begin
    if SkipWhitespaceUntilEnd then
    begin
      FPeeked := TJsonState.EOF;
      Exit(FPeeked);
    end else
      raise EMultipleRootNotAllowed.Create;
  end;

  C := NextNonWhitespace;
  case C of
    ']':
      if FPeekStack = TJsonScope.EmptyArray then
      begin
        SkipChar;
        FPeeked := TJsonState.EndArray;
        Exit(FPeeked);
      end else
        raise EExpectedValue.Create;
    '"':
      begin
        SkipChar;
        FPeeked := TJsonState.DoubleQuoted;
        Exit(FPeeked);
      end;
    '[':
      begin
        SkipChar;
        FPeeked := TJsonState.BeginArray;
        Exit(FPeeked);
      end;
    '{':
      begin
        SkipChar;
        FPeeked := TJsonState.BeginObject;
        Exit(FPeeked);
      end;
  end;

  Result := PeekKeyword;
  if Result <> TJsonState.None then
    Exit;
  Result := PeekNumber;
  if Result <> TJsonState.None then
    Exit;
  raise EExpectedValue.Create;
end;

function IsWhiteSpace(const c: char): boolean; inline;
begin
  if (c > #32) then
    exit(false);
  Result := byte(c) in Wspace;
end;

function IsWhiteSpaceOrEndTag(const c: char): boolean; inline;
begin
  if (c = '>') then
    exit(true);
  exit(IsWhiteSpace(c));
end;

function TJsonReader.SkipWhitespaceUntilEnd: boolean;
var
  StartBuffPos, StillInBuff: integer;
  Buff: PCharArrayND;
begin
  StillInBuff := FReader.CharsInBuffer;
  if StillInBuff <= 0 then
    Exit(true);
  Buff := FReader.Buffer;
  StartBuffPos := FReader.BuffPos;

  repeat
    if (Buff[StartBuffPos] > #32) or not(byte(Buff[StartBuffPos]) in Wspace) then
    begin;
      FReader.BuffPos := StartBuffPos;
      Exit(false);
    end;

    Dec(StillInBuff);
    Inc(StartBuffPos);
    if StillInBuff <= 0 then
    begin
      FReader.BuffPos := StartBuffPos;
      StillInBuff := FReader.CharsInBuffer;
      if StillInBuff <= 0 then
        Exit(true);
      Buff := FReader.Buffer;
      StartBuffPos := FReader.BuffPos;
    end;
  until False;
end;

procedure TJsonReader.CheckState(const State: TJsonState);
begin
  if NextPeek <> State then
    raise EInvalidStateException.Create(State, NextPeek);
end;

procedure TJsonReader.ReadBeginArray;
begin
  CheckState(TJsonState.BeginArray);
  PushScope(TJsonScope.EmptyArray);
  FPeeked := TJsonState.None;
end;

procedure TJsonReader.ReadEndArray;
begin
  CheckState(TJsonState.EndArray);
  Dec(FStackSize);
  FPeeked := TJsonState.None;
end;


function TJsonReader.ReadBoolean: boolean;
begin
  case NextPeek of
    TJsonState.True:
      Result := true;
    TJsonState.False:
      Result := false;
  else
    raise EInvalidStateException.Create(TJsonState.True, NextPeek); // todo: false
  end;
  FPeeked := TJsonState.None;
end;

function TJsonReader.ReadDouble: double;
begin
  case NextPeek of
    TJsonState.Int64:
      begin
        FPeeked := TJsonState.None;
        Exit(FPeekedInt64);
      end;
    TJsonState.Number:
      begin
        if TryStrToFloat(PChar(@FPeekedNumber[0]), Result, FFormatSettings) then
        begin
          FPeeked := TJsonState.None;
          Exit;
        end else
          FPeekedString := PChar(@FPeekedNumber[0]);
      end;
    TJsonState.DoubleQuoted:
      FPeekedString := ReadQuoted;
    TJsonState.Buffered: ;
  else
    raise EInvalidStateException.Create(TJsonState.Number, NextPeek); // todo
  end;

  FPeeked := TJsonState.Buffered;
  Result := StrToFloat(FPeekedString, FFormatSettings);
  FPeekedString := '';
  FPeeked := TJsonState.None;
end;

function TJsonReader.ReadEscaped: Char;
var
  CharValue: UInt32;
  I: integer;
  C1: Char;
  B: byte;
begin
  Result := FReader.NextChar;
  case Result of
    'n': Result := #10;
    'r': Result := #13;
    't': Result := #9;
    'b': Result := #8;
    'f': Result := #12;
    'u':
      begin
        CharValue := 0;
        for I := 3 downto 0 do
        begin
          C1 := FReader.PeekChar;
          if (C1 > #255) then
            raise EInvalidEscaped.Create;
          FReader.MoveNext;
          B := Byte(C1);
          CharValue := CharValue shl 4;
          if B in [ord('0') .. ord('9')] then
            CharValue := CharValue + (UInt32(B) - ord('0'))
          else
          if B in [ord('a') .. ord('f')] then
            CharValue := CharValue + (UInt32(B) - ord('a')) + 10
          else
          if B in [ord('A') .. ord('F')] then
            CharValue := CharValue + (UInt32(B) - ord('A')) + 10
          else
            raise EInvalidEscaped.Create;
        end;
        Result := Char(CharValue);
      end;
  end;
  // keep ", \, /
end;

function TJsonReader.ReadInt64: Int64;
var
  AsDouble: double;
begin
  case NextPeek of
    TJsonState.Int64:
      begin
        FPeeked := TJsonState.None;
        Exit(FPeekedInt64);
      end;
    TJsonState.Number:
      begin
        if TryStrToInt64(PChar(@FPeekedNumber[0]), Result) then
        begin
          FPeeked := TJsonState.None;
          Exit;
        end else
          FPeekedString := PChar(@FPeekedNumber[0]);
      end;
    TJsonState.DoubleQuoted:
      begin
        FPeekedString := ReadQuoted;
        if TryStrToInt64(FPeekedString, Result) then
        begin
          FPeeked := TJsonState.None;
          Exit;
        end;
      end;
    TJsonState.Buffered: ;
  else
    raise EInvalidStateException.Create(TJsonState.Int64, NextPeek); // todo
  end;

  FPeeked := TJsonState.Buffered;
  AsDouble := StrToFloat(FPeekedString, FFormatSettings);
  Result := Round(AsDouble);
  if AsDouble <> Result then
    raise EInvalidStateException.Create(TJsonState.Int64, NextPeek);
  FPeekedString := '';
  FPeeked := TJsonState.None;
end;

function TJsonReader.ReadInteger: integer;
var
  AsDouble: double;
begin
  case NextPeek of
    TJsonState.Int64:
      begin
        Result := Integer(FPeekedInt64);
        if Result <> FPeekedInt64 then
          raise EInvalidStateException.Create(TJsonState.Int64, NextPeek);
        FPeeked := TJsonState.None;
        Exit;
      end;
    TJsonState.Number:
      begin
        if TryStrToInt(PChar(@FPeekedNumber[0]), Result) then
        begin
          FPeeked := TJsonState.None;
          Exit;
        end else
          FPeekedString := PChar(@FPeekedNumber[0]);
      end;
    TJsonState.DoubleQuoted:
      begin
        FPeekedString := ReadQuoted;
        if TryStrToInt(FPeekedString, Result) then
        begin
          FPeeked := TJsonState.None;
          Exit;
        end;
      end;
    TJsonState.Buffered: ;
  else
    raise EInvalidStateException.Create(TJsonState.Int64, NextPeek); // todo
  end;

  FPeeked := TJsonState.Buffered;
  AsDouble := StrToFloat(FPeekedString, FFormatSettings);
  Result := Round(AsDouble);
  if AsDouble <> Result then
    raise EInvalidStateException.Create(TJsonState.Int64, NextPeek);
  FPeekedString := '';
  FPeeked := TJsonState.None;
end;

function TJsonReader.ReadName: string;
begin
  CheckState(TJsonState.DoubleQuotedName);
  FPeeked := TJsonState.None;
  Result := ReadQuoted;
end;

procedure TJsonReader.ReadNull;
begin
  CheckState(TJsonState.Null);
  FPeeked := TJsonState.None;
end;

procedure TJsonReader.ReadBeginObject;
begin
  CheckState(TJsonState.BeginObject);
  PushScope(TJsonScope.EmptyObject);
  FPeeked := TJsonState.None;
end;

procedure TJsonReader.ReadEndObject;
begin
  CheckState(TJsonState.EndObject);
  Dec(FStackSize);
  FPeeked := TJsonState.None;
end;

function TJsonReader.ReadQuoted: string;
begin
  Result := InternalReadQuoted(true);
end;

function TJsonReader.InternalReadQuoted(const BuildString: boolean): string;
var
  c: char;
  StillInBuff: integer;
  Buff: PCharArrayND;
  LocalTagPos: integer;
  StartBuffPos: integer;
  BuffPos: integer;
begin
  Result := '';
  LocalTagPos := 0;
  repeat
    PositionAtCurrentPos(StartBuffPos, Buff, StillInBuff);
    BuffPos := StartBuffPos;
    while StillInBuff > 0 do
    begin
      c := Buff[BuffPos];
      Inc(BuffPos);
      Dec(StillInBuff);
      Inc(LocalTagPos);
      if (c = '"') then
      begin
        FReader.BuffPos := BuffPos;
        if BuildString then
          CopyBufferToString(LocalTagPos - 1, Result, StartBuffPos, Buff);
        Exit;
      end
      else
      if c = '\' then
      begin
        FReader.BuffPos := BuffPos;
        if BuildString then
        begin
          CopyBufferToString(LocalTagPos - 1, Result, StartBuffPos, Buff);
          Result := Result + ReadEscaped;
        end else
          ReadEscaped;
        PositionAtCurrentPos(StartBuffPos, Buff, StillInBuff);
        BuffPos := StartBuffPos;
        LocalTagPos := Length(Result);
      end;
    end;
    if BuildString then
      CopyBufferToString(LocalTagPos, Result, StartBuffPos, Buff);
    FReader.BuffPos := BuffPos;
  until false;
end;

function TJsonReader.ReadString: string;
begin
  case NextPeek of
    TJsonState.DoubleQuoted:
      Result := ReadQuoted;
    TJsonState.Int64:
      Result := IntToStr(FPeekedInt64);
    TJsonState.Number:
      Result := PChar(@FPeekedNumber[0]);
    TJsonState.Buffered:
      Result := FPeekedString;
  else
    raise EInvalidStateException.Create(TJsonState.DoubleQuoted, NextPeek); // todo: int64 or number
  end;
  FPeeked := TJsonState.None;
end;

{ TJsonStreamReader }

function TJsonStreamReader.CharsInBuffer: integer;
begin
  Result := BuffLen - BuffPos;
  if Result <= 0 then
  begin
    FillBuffer;
    Result := BuffLen - BuffPos;
  end;
end;

constructor TJsonStreamReader.Create(const aStream: TStream);
begin
  FStream := aStream;
  SetLength(StreamBuff, StreamBuffSize);
  DetectEncoding;
  BuffAllocSize := FEncoding.GetMaxCharCount(StreamBuffSize) + 1;
  GetMem(Buffer, BuffAllocSize * SizeOf(char));
end;

destructor TJsonStreamReader.Destroy;
begin
  FreeMem(Buffer);
  FreeAndNil(FEncoding);
  inherited;
end;

procedure TJsonStreamReader.DetectEncoding;
var
  b: array [0..1] of byte;
  BytesRead: integer;
begin
  BytesRead := FStream.Read(b[0], Length(b));
  if BytesRead = 0 then
  begin
    // Do not raise error for empty stream. An empty stream could just provide
    // an EOF token when reading Json
    EofReached := true;
    FEncoding := TEncoding_UTF8.Create;
    Exit;
  end;

  if BytesRead < Length(b) then
    raise EInvalidJsonInput.Create;

  { from RFC-4627:

    JSON text SHALL be encoded in Unicode.  The default encoding is
    UTF-8.

    Since the first two characters of a JSON text will always be ASCII
    characters [RFC0020], it is possible to determine whether an octet
    stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
    at the pattern of nulls in the first four octets.

           00 00 00 xx  UTF-32BE
           00 xx 00 xx  UTF-16BE
           xx 00 00 00  UTF-32LE
           xx 00 xx 00  UTF-16LE
           xx xx xx xx  UTF-8
  }
  // ignore UTF-32 for now
  if b[0] = 0 then
  begin
    FEncoding := TEncoding_BigEndianUnicode.Create;
    UsingUTF16 := true;
    BigEndian := true;
    AddToBuffer(b);
  end
  else
  if b[1] = 0 then
  begin
    FEncoding := TEncoding_Unicode.Create;
    UsingUTF16 := true;
  end
  else
    FEncoding := TEncoding_UTF8.Create;
  AddToBuffer(b);
end;

procedure TJsonStreamReader.AddToBuffer(const a: Array of byte);
begin
  if StreamBuffLen + Length(a) >= StreamBuffSize then
    raise EInternalError.Create;

  System.Move(a[0], StreamBuff[StreamBuffLen], Length(a));
  Inc(StreamBuffLen, Length(a));
end;

function TJsonStreamReader.IsContinuation(K: integer): boolean;
begin
  if (K < 0) then
    exit(false);
  begin
    if (StreamBuff[K] and $C0) = $80 then
      exit(true)
    else
      exit(false);
  end;

end;

function TJsonStreamReader.IsStartOfContinuation(K: integer): boolean;
begin
  if (K < 0) then
    exit(false);
  begin
    if (StreamBuff[K] and $C0) = $C0 then
      exit(true)
    else
      exit(false);
  end;

end;

procedure TJsonStreamReader.FillBuffer;
var
  SRead, ERead: integer;
  RBuffLen: integer;
begin
  if EofReached then
    exit;

  Assert(StreamBuffLen < StreamBuffSize);
  SRead := FStream.Read(StreamBuff[StreamBuffLen], StreamBuffSize - StreamBuffLen);
  if (SRead <> StreamBuffSize - StreamBuffLen) then
    EofReached := true;
  Inc(StreamBuffLen, SRead);
  RBuffLen := StreamBuffLen;
  if (not UsingUTF16 and not EofReached) then
  // we don't care about UTF16 surrogates, as we will treat them as different chars. We do have to care about utf8 continuations.
  begin
    while IsContinuation(RBuffLen - 1) do
      Dec(RBuffLen);
    if (IsStartOfContinuation(RBuffLen - 1)) then
      Dec(RBuffLen);
  end;

  if (RBuffLen < 0) then
    raise EInvalidJsonInput.Create;

  if (UsingUTF16) then
  begin
    // Delphi implementation in those ignores bytecount (RBuffLen). We need to ensure last parameter is ok, we can't pass BuffAllocSize as we do in UTF8.
    if RBuffLen mod 2 <> 0 then
      Dec(RBuffLen);
    if RBuffLen > (BuffAllocSize - 1) * 2 then
      RBuffLen := (BuffAllocSize - 1) * 2;

    if BigEndian then
      ERead := TEncoding_BigEndianUnicode(FEncoding).GetCharsPChar(@StreamBuff[0], RBuffLen, @Buffer[1], RBuffLen div 2)
    else
      ERead := TEncoding_Unicode(FEncoding).GetCharsPChar(@StreamBuff[0], RBuffLen, @Buffer[1], RBuffLen div 2);
  end
  else
    ERead := TEncoding_UTF8(FEncoding).GetCharsPChar(@StreamBuff[0], RBuffLen, @Buffer[1], BuffAllocSize - 1);

  if (RBuffLen < StreamBuffLen) then
  begin
    System.Move(StreamBuff[RBuffLen], StreamBuff[0], StreamBuffLen - RBuffLen);
  end;
  Dec(StreamBuffLen, RBuffLen);
  BuffPos := 1;
  BuffLen := ERead + 1;

end;

function TJsonStreamReader.NextChar: char;
begin
  if (Eof) then
    raise EEndOfInputReached.Create;
  Result := Buffer[BuffPos];
  Inc(BuffPos);
end;

function TJsonStreamReader.NextCharNotEOF: char;
begin
  Result := Buffer[BuffPos];
  Inc(BuffPos);
end;

function TJsonStreamReader.PeekChar: char;
begin
  if (Eof) then
    raise EEndOfInputReached.Create;
  Result := Buffer[BuffPos];
end;

procedure TJsonStreamReader.Backup(const c: char);
begin
  Dec(BuffPos);
  if (BuffPos = 0) then
    Buffer[0] := c;
end;

procedure TJsonStreamReader.MoveNext(const Count: integer = 1); // this should be called always after PeekChar, both methods together do a NextChar.
begin
  Inc(BuffPos, Count);
end;

function TJsonStreamReader.NextString(const len: integer): string;
var
  i: integer;
begin
  SetLength(Result, len);
  for i := 1 to len do
  begin
    if (Eof) then
      raise EEndOfInputReached.Create;
    Result[i] := Buffer[BuffPos];
    Inc(BuffPos);
  end;
end;

function TJsonStreamReader.Eof: boolean;
begin
  if (BuffPos >= BuffLen) then
    FillBuffer;
  Result := EofReached and (BuffPos >= BuffLen);
end;

{ TEncoding_BigEndianUnicode }

function TEncoding_BigEndianUnicode.GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
begin
  Result := GetChars(Bytes, ByteCount, Chars, CharCount);
end;

{ TEncoding_Unicode }

function TEncoding_Unicode.GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
begin
  Result := GetChars(Bytes, ByteCount, Chars, CharCount);
end;

{ TEncoding_UTF8 }

function TEncoding_UTF8.GetCharsPChar(Bytes: PByte; ByteCount: integer; Chars: PChar; CharCount: integer): integer;
begin
  Result := GetChars(Bytes, ByteCount, Chars, CharCount);
end;

{ TJsonReader.EInvalidStateException }

constructor TJsonReader.EInvalidStateException.Create(const Expected, Actual: TJsonState);
begin
  inherited CreateFmt('Invalid Json parser state. Expected: %s. Actual: %s',
    [JsonStateNames[Expected], JsonStateNames[Actual]]);
end;

{ TJsonReader.EUnterminatedArray }

constructor TJsonReader.EUnterminatedArray.Create;
begin
  inherited Create('Unterminated array');
end;

{ TJsonReader.EUnterminatedObject }

constructor TJsonReader.EUnterminatedObject.Create;
begin
  inherited Create('Unterminated object');
end;

{ TJsonReader.ENameExpected }

constructor TJsonReader.ENameExpected.Create;
begin
  inherited Create('Name expected');
end;

{ TJsonReader.EColonExpected }

constructor TJsonReader.EColonExpected.Create;
begin
  inherited Create('Colon expected');
end;

{ TJsonReader.EReaderClosed }

constructor TJsonReader.EReaderClosed.Create;
begin
  inherited Create('Reader already closed');
end;

{ TJsonReader.EMultipleRootNotAllowed }

constructor TJsonReader.EMultipleRootNotAllowed.Create;
begin
  inherited Create('Multiple root values not allowed');
end;

{ TJsonReader.EExpectedValue }

constructor TJsonReader.EExpectedValue.Create;
begin
  inherited Create('Value expected but invalid character found');
end;

{ TJsonReader.EObjectOrArrayExpected }

constructor TJsonReader.EObjectOrArrayExpected.Create;
begin
  inherited Create('Object or array expected as top-level value');
end;

{ TJsonReader.ETooManyDepthLevels }

constructor TJsonReader.ETooManyDepthLevels.Create;
begin
  inherited Create('Maximum level of nested structured reached.');
end;

{ TJsonStreamReader.EInvalidJsonInput }

constructor TJsonStreamReader.EInvalidJsonInput.Create;
begin
  inherited Create('Invalid JSON Input');
end;

{ TJsonStreamReader.EInternalError }

constructor TJsonStreamReader.EInternalError.Create;
begin
  inherited Create('JSON stream reader internal error');
end;

{ TJsonStreamReader.EEndOfInputReached }

constructor TJsonStreamReader.EEndOfInputReached.Create;
begin
  inherited Create('End of JSON input reached.');
end;

{ TJsonReader.EInvalidEscaped }

constructor TJsonReader.EInvalidEscaped.Create;
begin
  inherited Create('Invalid escaped sequence');
end;

end.
