context: #region License
  // Copyright (c) 2007 James Newton-King
  //
  // Permission is hereby granted, free of charge, to any person
  // obtaining a copy of this software and associated documentation
  // files (the "Software"), to deal in the Software without
  // restriction, including without limitation the rights to use,
  // copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the
  // Software is furnished to do so, subject to the following
  // conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  // OTHER DEALINGS IN THE SOFTWARE.
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Text;
  using System.IO;
  using System.Xml;
  using System.Globalization;
  using Newtonsoft.Json.Utilities;
  
  namespace Newtonsoft.Json
  {
    /// <summary>
    /// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data.
    /// </summary>
    public class JsonTextReader : JsonReader, IJsonLineInfo
    {
      private enum ReadType
      {
        Read,
        ReadAsBytes,
        ReadAsDecimal,
  #if !NET20
        ReadAsDateTimeOffset
  #endif
      }
  
      private readonly TextReader _reader;
      private readonly StringBuffer _buffer;
      private char? _lastChar;
      private int _currentLinePosition;
      private int _currentLineNumber;
      private bool _end;
      private ReadType _readType;
      private CultureInfo _culture;
  
      /// <summary>
      /// Gets or sets the culture used when reading JSON. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
      /// </summary>
      public CultureInfo Culture
      {
        get { return _culture ?? CultureInfo.CurrentCulture; }
        set { _culture = value; }
      }
  
      /// <summary>
      /// Initializes a new instance of the <see cref="JsonReader"/> class with the specified <see cref="TextReader"/>.
      /// </summary>
      /// <param name="reader">The <c>TextReader</c> containing the XML data to read.</param>
      public JsonTextReader(TextReader reader)
      {
        if (reader == null)
          throw new ArgumentNullException("reader");
  
        _reader = reader;
        _buffer = new StringBuffer(4096);
        _currentLineNumber = 1;
      }
  
      private void ParseString(char quote)
      {
        ReadStringIntoBuffer(quote);
  
        if (_readType == ReadType.ReadAsBytes)
        {
          byte[] data;
          if (_buffer.Position == 0)
          {
            data = new byte[0];
          }
          else
          {
            data = Convert.FromBase64CharArray(_buffer.GetInternalBuffer(), 0, _buffer.Position);
            _buffer.Position = 0;
          }
  
          SetToken(JsonToken.Bytes, data);
        }
        else
        {
          string text = _buffer.ToString();
          _buffer.Position = 0;
  
          if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal))
          {
            ParseDate(text);
          }
          else
          {
            SetToken(JsonToken.String, text);
            QuoteChar = quote;
          } 
        }
      }
  
      private void ReadStringIntoBuffer(char quote)
      {
        while (true)
        {
          char currentChar = MoveNext();
  
          switch (currentChar)
          {
            case '\0':
              if (_end)
                throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition);
  
              _buffer.Append('\0');
              break;
            case '\\':
              if ((currentChar = MoveNext()) != '\0' || !_end)
              {
                switch (currentChar)
                {
                  case 'b':
                    _buffer.Append('\b');
                    break;
                  case 't':
                    _buffer.Append('\t');
                    break;
                  case 'n':
                    _buffer.Append('\n');
                    break;
                  case 'f':
                    _buffer.Append('\f');
                    break;
                  case 'r':
                    _buffer.Append('\r');
                    break;
                  case '\\':
                    _buffer.Append('\\');
                    break;
                  case '"':
                  case '\'':
                  case '/':
                    _buffer.Append(currentChar);
                    break;
                  case 'u':
                    char[] hexValues = new char[4];
                    for (int i = 0; i < hexValues.Length; i++)
                    {
                      if ((currentChar = MoveNext()) != '\0' || !_end)
                        hexValues[i] = currentChar;
                      else
                        throw CreateJsonReaderException("Unexpected end while parsing unicode character. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
                    }
  
                    char hexChar = Convert.ToChar(int.Parse(new string(hexValues), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo));
                    _buffer.Append(hexChar);
                    break;
                  default:
                    throw CreateJsonReaderException("Bad JSON escape sequence: {0}. Line {1}, position {2}.", @"\" + currentChar, _currentLineNumber, _currentLinePosition);
                }
              }
              else
              {
                throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition);
              }
              break;
            case '"':
            case '\'':
              if (currentChar == quote)
              {
                return;
              }
              else
              {
                _buffer.Append(currentChar);
              }
              break;
            default:
              _buffer.Append(currentChar);
              break;
          }
        }
      }
  
      private JsonReaderException CreateJsonReaderException(string format, params object[] args)
      {
        string message = format.FormatWith(CultureInfo.InvariantCulture, args);
        return new JsonReaderException(message, null, _currentLineNumber, _currentLinePosition);
      }
  
      private TimeSpan ReadOffset(string offsetText)
      {
        bool negative = (offsetText[0] == '-');
  
        int hours = int.Parse(offsetText.Substring(1, 2), NumberStyles.Integer, CultureInfo.InvariantCulture);
        int minutes = 0;
        if (offsetText.Length >= 5)
          minutes = int.Parse(offsetText.Substring(3, 2), NumberStyles.Integer, CultureInfo.InvariantCulture);
  
        TimeSpan offset = TimeSpan.FromHours(hours) + TimeSpan.FromMinutes(minutes);
        if (negative)
          offset = offset.Negate();
        
        return offset;
      }
  
      private void ParseDate(string text)
      {
        string value = text.Substring(6, text.Length - 8);
        DateTimeKind kind = DateTimeKind.Utc;
  
        int index = value.IndexOf('+', 1);
  
        if (index == -1)
          index = value.IndexOf('-', 1);
  
        TimeSpan offset = TimeSpan.Zero;
  
        if (index != -1)
        {
          kind = DateTimeKind.Local;
          offset = ReadOffset(value.Substring(index));
          value = value.Substring(0, index);
        }
  
        long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
  
        DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks);
  
  #if !NET20
        if (_readType == ReadType.ReadAsDateTimeOffset)
        {
          SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset));
        }
        else
  #endif
        {
          DateTime dateTime;
  
          switch (kind)
          {
            case DateTimeKind.Unspecified:
              dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified);
              break;
            case DateTimeKind.Local:
              dateTime = utcDateTime.ToLocalTime();
              break;
            default:
              dateTime = utcDateTime;
              break;
          }
  
          SetToken(JsonToken.Date, dateTime);
        }
      }
  
      private const int LineFeedValue = StringUtils.LineFeed;
      private const int CarriageReturnValue = StringUtils.CarriageReturn;
  
      private char MoveNext()
      {
        int value = _reader.Read();
  
        switch (value)
        {
          case -1:
            _end = true;
            return '\0';
          case CarriageReturnValue:
            if (_reader.Peek() == LineFeedValue)
              _reader.Read();
  
            _currentLineNumber++;
            _currentLinePosition = 0;
            break;
          case LineFeedValue:
            _currentLineNumber++;
            _currentLinePosition = 0;
            break;
          default:
            _currentLinePosition++;
            break;
        }
  
        return (char)value;
      }
  
      private bool HasNext()
      {
        return (_reader.Peek() != -1);
      }
  
      private int PeekNext()
      {
        return _reader.Peek();
      }
  
      /// <summary>
      /// Reads the next JSON token from the stream.
      /// </summary>
      /// <returns>
      /// true if the next token was read successfully; false if there are no more tokens to read.
      /// </returns>
      public override bool Read()
      {
        _readType = ReadType.Read;
        return ReadInternal();
      }
  
      /// <summary>
      /// Reads the next JSON token from the stream as a <see cref="T:Byte[]"/>.
      /// </summary>
      /// <returns>
      /// A <see cref="T:Byte[]"/> or a null reference if the next JSON token is null.
      /// </returns>
      public override byte[] ReadAsBytes()
      {
        _readType = ReadType.ReadAsBytes;
  
          do
          {
        if (!ReadInternal())
          throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
          } while (TokenType == JsonToken.Comment);
  
        if (TokenType == JsonToken.Null)
          return null;
        if (TokenType == JsonToken.Bytes)
          return (byte[]) Value;
        if (TokenType == JsonToken.StartArray)
        {
          List<byte> data = new List<byte>();
  
          while (ReadInternal())
          {
            switch (TokenType)
            {
              case JsonToken.Integer:
                data.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture));
                break;
              case JsonToken.EndArray:
                byte[] d = data.ToArray();
                SetToken(JsonToken.Bytes, d);
                return d;
              case JsonToken.Comment:
                // skip
                break;
              default:
                throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
            }
          }
  
          throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
        }
  
  
        throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
      }
  
      /// <summary>
      /// Reads the next JSON token from the stream as a <see cref="Nullable{Decimal}"/>.
      /// </summary>
      /// <returns>A <see cref="Nullable{Decimal}"/>.</returns>
      public override decimal? ReadAsDecimal()
      {
        _readType = ReadType.ReadAsDecimal;
        
        do
        {
        if (!ReadInternal())
          throw CreateJsonReaderException("Unexpected end when reading decimal: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
        } while (TokenType == JsonToken.Comment);
  
        if (TokenType == JsonToken.Null)
          return null;
        if (TokenType == JsonToken.Float)
          return (decimal?)Value;
  
        decimal d;
        if (TokenType == JsonToken.String && decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d))
        {
          SetToken(JsonToken.Float, d);
          return d;
        }
  
        throw CreateJsonReaderException("Unexpected token when reading decimal: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
      }
  
  #if !NET20
      /// <summary>
      /// Reads the next JSON token from the stream as a <see cref="Nullable{DateTimeOffset}"/>.
      /// </summary>
      /// <returns>A <see cref="DateTimeOffset"/>.</returns>
      public override DateTimeOffset? ReadAsDateTimeOffset()
      {
        _readType = ReadType.ReadAsDateTimeOffset;
  
        do
        {
        if (!ReadInternal())
          throw CreateJsonReaderException("Unexpected end when reading date: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
        } while (TokenType == JsonToken.Comment);
  
        if (TokenType == JsonToken.Null)
          return null;
        if (TokenType == JsonToken.Date)
          return (DateTimeOffset)Value;
  
        DateTimeOffset dt;
        if (TokenType == JsonToken.String && DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.None, out dt))
        {
          SetToken(JsonToken.Date, dt);
          return dt;
        }
  
        throw CreateJsonReaderException("Unexpected token when reading date: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition);
      }
  #endif
  
      private bool ReadInternal()
      {
        while (true)
        {
          char currentChar;
          if (_lastChar != null)
          {
            currentChar = _lastChar.Value;
            _lastChar = null;
          }
          else
          {
            currentChar = MoveNext();
          }
  
          if (currentChar == '\0' && _end)
            return false;
  
          switch (CurrentState)
          {
            case State.Start:
            case State.Property:
            case State.Array:
            case State.ArrayStart:
            case State.Constructor:
            case State.ConstructorStart:
              return ParseValue(currentChar);
            case State.Complete:
              break;
            case State.Object:
            case State.ObjectStart:
              return ParseObject(currentChar);
            case State.PostValue:
              // returns true if it hits
              // end of object or array
              if (ParsePostValue(currentChar))
                return true;
              break;
            case State.Closed:
              break;
            case State.Error:
              break;
            default:
              throw CreateJsonReaderException("Unexpected state: {0}. Line {1}, position {2}.", CurrentState, _currentLineNumber, _currentLinePosition);
          }
        }
      }
  
      private bool ParsePostValue(char currentChar)
      {
        do
        {
          switch (currentChar)
          {
            case '}':
              SetToken(JsonToken.EndObject);
              return true;
            case ']':
              SetToken(JsonToken.EndArray);
              return true;
            case ')':
              SetToken(JsonToken.EndConstructor);
              return true;
            case '/':
              ParseComment();
              return true;
            case ',':
              // finished parsing
              SetStateBasedOnCurrent();
              return false;
            case ' ':
            case StringUtils.Tab:
            case StringUtils.LineFeed:
            case StringUtils.CarriageReturn:
              // eat
              break;
            default:
              if (char.IsWhiteSpace(currentChar))
              {
                // eat
              }
              else
              {
                throw CreateJsonReaderException("After parsing a value an unexpected character was encountered: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
              }
              break;
          }
        } while ((currentChar = MoveNext()) != '\0' || !_end);
  
        return false;
      }
  
      private bool ParseObject(char currentChar)
      {
        do
        {
          switch (currentChar)
          {
            case '}':
              SetToken(JsonToken.EndObject);
              return true;
            case '/':
              ParseComment();
              return true;
            case ' ':
            case StringUtils.Tab:
            case StringUtils.LineFeed:
            case StringUtils.CarriageReturn:
              // eat
              break;
            default:
              if (char.IsWhiteSpace(currentChar))
              {
                // eat
              }
              else
              {
                return ParseProperty(currentChar);
              }
              break;
          }
        } while ((currentChar = MoveNext()) != '\0' || !_end);
  
        return false;
      }
  
      private bool ParseProperty(char firstChar)
      {
        char currentChar = firstChar;
        char quoteChar;
  
        if (ValidIdentifierChar(currentChar))
        {
          quoteChar = '\0';
          currentChar = ParseUnquotedProperty(currentChar);
        }
        else if (currentChar == '"' || currentChar == '\'')
        {
          quoteChar = currentChar;
          ReadStringIntoBuffer(quoteChar);
          currentChar = MoveNext();
        }
        else
        {
          throw CreateJsonReaderException("Invalid property identifier character: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
        }
  
        if (currentChar != ':')
        {
  
gt:         currentChar = MoveNext();