/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using Thrift.Transport;
using System.Globalization;

namespace Thrift.Protocol
{
    /// <summary>
    /// JSON protocol implementation for thrift.
    /// <para/>
    /// This is a full-featured protocol supporting Write and Read.
    /// <para/>
    /// Please see the C++ class header for a detailed description of the
    /// protocol's wire format.
    /// <para/>
    /// Adapted from the Java version.
    /// </summary>
    public class TJSONProtocol : TProtocol
    {
        /// <summary>
        /// Factory for JSON protocol objects.
        /// </summary>
        public class Factory : TProtocolFactory
        {
            public TProtocol GetProtocol(TTransport trans)
            {
                return new TJSONProtocol(trans);
            }
        }

        private static byte[] COMMA = new byte[] { (byte)',' };
        private static byte[] COLON = new byte[] { (byte)':' };
        private static byte[] LBRACE = new byte[] { (byte)'{' };
        private static byte[] RBRACE = new byte[] { (byte)'}' };
        private static byte[] LBRACKET = new byte[] { (byte)'[' };
        private static byte[] RBRACKET = new byte[] { (byte)']' };
        private static byte[] QUOTE = new byte[] { (byte)'"' };
        private static byte[] BACKSLASH = new byte[] { (byte)'\\' };

        private byte[] ESCSEQ = new byte[] { (byte)'\\', (byte)'u', (byte)'0', (byte)'0' };

        private const long VERSION = 1;
        private byte[] JSON_CHAR_TABLE = {
    0,  0,  0,  0,  0,  0,  0,  0,(byte)'b',(byte)'t',(byte)'n',  0,(byte)'f',(byte)'r',  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    1,  1,(byte)'"',  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
  };

        private char[] ESCAPE_CHARS = "\"\\/bfnrt".ToCharArray();

        private byte[] ESCAPE_CHAR_VALS = {
    (byte)'"', (byte)'\\', (byte)'/', (byte)'\b', (byte)'\f', (byte)'\n', (byte)'\r', (byte)'\t',
  };

        private const int DEF_STRING_SIZE = 16;

        private static byte[] NAME_BOOL = new byte[] { (byte)'t', (byte)'f' };
        private static byte[] NAME_BYTE = new byte[] { (byte)'i', (byte)'8' };
        private static byte[] NAME_I16 = new byte[] { (byte)'i', (byte)'1', (byte)'6' };
        private static byte[] NAME_I32 = new byte[] { (byte)'i', (byte)'3', (byte)'2' };
        private static byte[] NAME_I64 = new byte[] { (byte)'i', (byte)'6', (byte)'4' };
        private static byte[] NAME_DOUBLE = new byte[] { (byte)'d', (byte)'b', (byte)'l' };
        private static byte[] NAME_STRUCT = new byte[] { (byte)'r', (byte)'e', (byte)'c' };
        private static byte[] NAME_STRING = new byte[] { (byte)'s', (byte)'t', (byte)'r' };
        private static byte[] NAME_MAP = new byte[] { (byte)'m', (byte)'a', (byte)'p' };
        private static byte[] NAME_LIST = new byte[] { (byte)'l', (byte)'s', (byte)'t' };
        private static byte[] NAME_SET = new byte[] { (byte)'s', (byte)'e', (byte)'t' };

        private static byte[] GetTypeNameForTypeID(TType typeID)
        {
            switch (typeID)
            {
                case TType.Bool:
                    return NAME_BOOL;
                case TType.Byte:
                    return NAME_BYTE;
                case TType.I16:
                    return NAME_I16;
                case TType.I32:
                    return NAME_I32;
                case TType.I64:
                    return NAME_I64;
                case TType.Double:
                    return NAME_DOUBLE;
                case TType.String:
                    return NAME_STRING;
                case TType.Struct:
                    return NAME_STRUCT;
                case TType.Map:
                    return NAME_MAP;
                case TType.Set:
                    return NAME_SET;
                case TType.List:
                    return NAME_LIST;
                default:
                    throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED,
                                                 "Unrecognized type");
            }
        }

        private static TType GetTypeIDForTypeName(byte[] name)
        {
            TType result = TType.Stop;
            if (name.Length > 1)
            {
                switch (name[0])
                {
                    case (byte)'d':
                        result = TType.Double;
                        break;
                    case (byte)'i':
                        switch (name[1])
                        {
                            case (byte)'8':
                                result = TType.Byte;
                                break;
                            case (byte)'1':
                                result = TType.I16;
                                break;
                            case (byte)'3':
                                result = TType.I32;
                                break;
                            case (byte)'6':
                                result = TType.I64;
                                break;
                        }
                        break;
                    case (byte)'l':
                        result = TType.List;
                        break;
                    case (byte)'m':
                        result = TType.Map;
                        break;
                    case (byte)'r':
                        result = TType.Struct;
                        break;
                    case (byte)'s':
                        if (name[1] == (byte)'t')
                        {
                            result = TType.String;
                        }
                        else if (name[1] == (byte)'e')
                        {
                            result = TType.Set;
                        }
                        break;
                    case (byte)'t':
                        result = TType.Bool;
                        break;
                }
            }
            if (result == TType.Stop)
            {
                throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED,
                                             "Unrecognized type");
            }
            return result;
        }

        /// <summary>
        /// Base class for tracking JSON contexts that may require
        /// inserting/Reading additional JSON syntax characters
        /// This base context does nothing.
        /// </summary>
        protected class JSONBaseContext
        {
            protected TJSONProtocol proto;

            public JSONBaseContext(TJSONProtocol proto)
            {
                this.proto = proto;
            }

            public virtual void Write() { }

            public virtual void Read() { }

            public virtual bool EscapeNumbers() { return false; }
        }

        /// <summary>
        /// Context for JSON lists. Will insert/Read commas before each item except
        /// for the first one
        /// </summary>
        protected class JSONListContext : JSONBaseContext
        {
            public JSONListContext(TJSONProtocol protocol)
                : base(protocol)
            {

            }

            private bool first = true;

            public override void Write()
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    proto.trans.Write(COMMA);
                }
            }

            public override void Read()
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    proto.ReadJSONSyntaxChar(COMMA);
                }
            }
        }

        /// <summary>
        /// Context for JSON records. Will insert/Read colons before the value portion
        /// of each record pair, and commas before each key except the first. In
        /// addition, will indicate that numbers in the key position need to be
        /// escaped in quotes (since JSON keys must be strings).
        /// </summary>
        protected class JSONPairContext : JSONBaseContext
        {
            public JSONPairContext(TJSONProtocol proto)
                : base(proto)
            {

            }

            private bool first = true;
            private bool colon = true;

            public override void Write()
            {
                if (first)
                {
                    first = false;
                    colon = true;
                }
                else
                {
                    proto.trans.Write(colon ? COLON : COMMA);
                    colon = !colon;
                }
            }

            public override void Read()
            {
                if (first)
                {
                    first = false;
                    colon = true;
                }
                else
                {
                    proto.ReadJSONSyntaxChar(colon ? COLON : COMMA);
                    colon = !colon;
                }
            }

            public override bool EscapeNumbers()
            {
                return colon;
            }
        }

        /// <summary>
        /// Holds up to one byte from the transport
        /// </summary>
        protected class LookaheadReader
        {
            protected TJSONProtocol proto;

            public LookaheadReader(TJSONProtocol proto)
            {
                this.proto = proto;
            }

            private bool hasData;
            private byte[] data = new byte[1];

            /// <summary>
            /// Return and consume the next byte to be Read, either taking it from the
            /// data buffer if present or getting it from the transport otherwise.
            /// </summary>
            public byte Read()
            {
                if (hasData)
                {
                    hasData = false;
                }
                else
                {
                    proto.trans.ReadAll(data, 0, 1);
                }
                return data[0];
            }

            /// <summary>
            /// Return the next byte to be Read without consuming, filling the data
            /// buffer if it has not been filled alReady.
            /// </summary>
            public byte Peek()
            {
                if (!hasData)
                {
                    proto.trans.ReadAll(data, 0, 1);
                }
                hasData = true;
                return data[0];
            }
        }

        // Default encoding
        protected Encoding utf8Encoding = UTF8Encoding.UTF8;

        // Stack of nested contexts that we may be in
        protected Stack<JSONBaseContext> contextStack = new Stack<JSONBaseContext>();

        // Current context that we are in
        protected JSONBaseContext context;

        // Reader that manages a 1-byte buffer
        protected LookaheadReader reader;

        /// <summary>
        /// Push a new JSON context onto the stack.
        /// </summary>
        protected void PushContext(JSONBaseContext c)
        {
            contextStack.Push(context);
            context = c;
        }

        /// <summary>
        /// Pop the last JSON context off the stack
        /// </summary>
        protected void PopContext()
        {
            context = contextStack.Pop();
        }

        /// <summary>
        /// TJSONProtocol Constructor
        /// </summary>
        public TJSONProtocol(TTransport trans)
            : base(trans)
        {
            context = new JSONBaseContext(this);
            reader = new LookaheadReader(this);
        }

        // Temporary buffer used by several methods
        private byte[] tempBuffer = new byte[4];

        /// <summary>
        /// Read a byte that must match b[0]; otherwise an exception is thrown.
        /// Marked protected to avoid synthetic accessor in JSONListContext.Read
        /// and JSONPairContext.Read
        /// </summary>
        protected void ReadJSONSyntaxChar(byte[] b)
        {
            byte ch = reader.Read();
            if (ch != b[0])
            {
                throw new TProtocolException(TProtocolException.INVALID_DATA,
                                             "Unexpected character:" + (char)ch);
            }
        }

        /// <summary>
        /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
        /// corresponding hex value
        /// </summary>
        private static byte HexVal(byte ch)
        {
            if ((ch >= '0') && (ch <= '9'))
            {
                return (byte)((char)ch - '0');
            }
            else if ((ch >= 'a') && (ch <= 'f'))
            {
                ch += 10;
                return (byte)((char)ch - 'a');
            }
            else
            {
                throw new TProtocolException(TProtocolException.INVALID_DATA,
                                             "Expected hex character");
            }
        }

        /// <summary>
        /// Convert a byte containing a hex value to its corresponding hex character
        /// </summary>
        private static byte HexChar(byte val)
        {
            val &= 0x0F;
            if (val < 10)
            {
                return (byte)((char)val + '0');
            }
            else
            {
                val -= 10;
                return (byte)((char)val + 'a');
            }
        }

        /// <summary>
        /// Write the bytes in array buf as a JSON characters, escaping as needed
        /// </summary>
        private void WriteJSONString(byte[] b)
        {
            context.Write();
            trans.Write(QUOTE);
            int len = b.Length;
            for (int i = 0; i < len; i++)
            {
                if ((b[i] & 0x00FF) >= 0x30)
                {
                    if (b[i] == BACKSLASH[0])
                    {
                        trans.Write(BACKSLASH);
                        trans.Write(BACKSLASH);
                    }
                    else
                    {
                        trans.Write(b, i, 1);
                    }
                }
                else
                {
                    tempBuffer[0] = JSON_CHAR_TABLE[b[i]];
                    if (tempBuffer[0] == 1)
                    {
                        trans.Write(b, i, 1);
                    }
                    else if (tempBuffer[0] > 1)
                    {
                        trans.Write(BACKSLASH);
                        trans.Write(tempBuffer, 0, 1);
                    }
                    else
                    {
                        trans.Write(ESCSEQ);
                        tempBuffer[0] = HexChar((byte)(b[i] >> 4));
                        tempBuffer[1] = HexChar(b[i]);
                        trans.Write(tempBuffer, 0, 2);
                    }
                }
            }
            trans.Write(QUOTE);
        }

        /// <summary>
        /// Write out number as a JSON value. If the context dictates so, it will be
        /// wrapped in quotes to output as a JSON string.
        /// </summary>
        private void WriteJSONInteger(long num)
        {
            context.Write();
            string str = num.ToString();

            bool escapeNum = context.EscapeNumbers();
            if (escapeNum)
                trans.Write(QUOTE);

            trans.Write(utf8Encoding.GetBytes(str));

            if (escapeNum)
                trans.Write(QUOTE);
        }

        /// <summary>
        /// Write out a double as a JSON value. If it is NaN or infinity or if the
        /// context dictates escaping, Write out as JSON string.
        /// </summary>
        private void WriteJSONDouble(double num)
        {
            context.Write();
            string str = num.ToString("G17", CultureInfo.InvariantCulture);
            bool special = false;

            switch (str[0])
            {
                case 'N': // NaN
                case 'I': // Infinity
                    special = true;
                    break;
                case '-':
                    if (str[1] == 'I')
                    { // -Infinity
                        special = true;
                    }
                    break;
            }

            bool escapeNum = special || context.EscapeNumbers();

            if (escapeNum)
                trans.Write(QUOTE);

            trans.Write(utf8Encoding.GetBytes(str));

            if (escapeNum)
                trans.Write(QUOTE);
        }
        /// <summary>
        /// Write out contents of byte array b as a JSON string with base-64 encoded
        /// data
        /// </summary>
        private void WriteJSONBase64(byte[] b)
        {
            context.Write();
            trans.Write(QUOTE);

            int len = b.Length;
            int off = 0;

            while (len >= 3)
            {
                // Encode 3 bytes at a time
                TBase64Utils.encode(b, off, 3, tempBuffer, 0);
                trans.Write(tempBuffer, 0, 4);
                off += 3;
                len -= 3;
            }
            if (len > 0)
            {
                // Encode remainder
                TBase64Utils.encode(b, off, len, tempBuffer, 0);
                trans.Write(tempBuffer, 0, len + 1);
            }

            trans.Write(QUOTE);
        }

        private void WriteJSONObjectStart()
        {
            context.Write();
            trans.Write(LBRACE);
            PushContext(new JSONPairContext(this));
        }

        private void WriteJSONObjectEnd()
        {
            PopContext();
            trans.Write(RBRACE);
        }

        private void WriteJSONArrayStart()
        {
            context.Write();
            trans.Write(LBRACKET);
            PushContext(new JSONListContext(this));
        }

        private void WriteJSONArrayEnd()
        {
            PopContext();
            trans.Write(RBRACKET);
        }

        public override void WriteMessageBegin(TMessage message)
        {
            WriteJSONArrayStart();
            WriteJSONInteger(VERSION);

            byte[] b = utf8Encoding.GetBytes(message.Name);
            WriteJSONString(b);

            WriteJSONInteger((long)message.Type);
            WriteJSONInteger(message.SeqID);
        }

        public override void WriteMessageEnd()
        {
            WriteJSONArrayEnd();
        }

        public override void WriteStructBegin(TStruct str)
        {
            WriteJSONObjectStart();
        }

        public override void WriteStructEnd()
        {
            WriteJSONObjectEnd();
        }

        public override void WriteFieldBegin(TField field)
        {
            WriteJSONInteger(field.ID);
            WriteJSONObjectStart();
            WriteJSONString(GetTypeNameForTypeID(field.Type));
        }

        public override void WriteFieldEnd()
        {
            WriteJSONObjectEnd();
        }

        public override void WriteFieldStop() { }

        public override void WriteMapBegin(TMap map)
        {
            WriteJSONArrayStart();
            WriteJSONString(GetTypeNameForTypeID(map.KeyType));
            WriteJSONString(GetTypeNameForTypeID(map.ValueType));
            WriteJSONInteger(map.Count);
            WriteJSONObjectStart();
        }

        public override void WriteMapEnd()
        {
            WriteJSONObjectEnd();
            WriteJSONArrayEnd();
        }

        public override void WriteListBegin(TList list)
        {
            WriteJSONArrayStart();
            WriteJSONString(GetTypeNameForTypeID(list.ElementType));
            WriteJSONInteger(list.Count);
        }

        public override void WriteListEnd()
        {
            WriteJSONArrayEnd();
        }

        public override void WriteSetBegin(TSet set)
        {
            WriteJSONArrayStart();
            WriteJSONString(GetTypeNameForTypeID(set.ElementType));
            WriteJSONInteger(set.Count);
        }

        public override void WriteSetEnd()
        {
            WriteJSONArrayEnd();
        }

        public override void WriteBool(bool b)
        {
            WriteJSONInteger(b ? (long)1 : (long)0);
        }

        public override void WriteByte(sbyte b)
        {
            WriteJSONInteger((long)b);
        }

        public override void WriteI16(short i16)
        {
            WriteJSONInteger((long)i16);
        }

        public override void WriteI32(int i32)
        {
            WriteJSONInteger((long)i32);
        }

        public override void WriteI64(long i64)
        {
            WriteJSONInteger(i64);
        }

        public override void WriteDouble(double dub)
        {
            WriteJSONDouble(dub);
        }

        public override void WriteString(string str)
        {
            byte[] b = utf8Encoding.GetBytes(str);
            WriteJSONString(b);
        }

        public override void WriteBinary(byte[] bin)
        {
            WriteJSONBase64(bin);
        }

        /**
         * Reading methods.
         */

        /// <summary>
        /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the
        /// context if skipContext is true.
        /// </summary>
        private byte[] ReadJSONString(bool skipContext)
        {
            MemoryStream buffer = new MemoryStream();
            List<char> codeunits = new List<char>();


            if (!skipContext)
            {
                context.Read();
            }
            ReadJSONSyntaxChar(QUOTE);
            while (true)
            {
                byte ch = reader.Read();
                if (ch == QUOTE[0])
                {
                    break;
                }

                // escaped?
                if (ch != ESCSEQ[0])
                {
                    buffer.Write(new byte[] { (byte)ch }, 0, 1);
                    continue;
                }

                // distinguish between \uXXXX and \?
                ch = reader.Read();
                if (ch != ESCSEQ[1])  // control chars like \n
                {
                    int off = Array.IndexOf(ESCAPE_CHARS, (char)ch);
                    if (off == -1)
                    {
                        throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                        "Expected control char");
                    }
                    ch = ESCAPE_CHAR_VALS[off];
                    buffer.Write(new byte[] { (byte)ch }, 0, 1);
                    continue;
                }


                // it's \uXXXX
                trans.ReadAll(tempBuffer, 0, 4);
                var wch = (short)((HexVal((byte)tempBuffer[0]) << 12) +
                                  (HexVal((byte)tempBuffer[1]) << 8) +
                                  (HexVal((byte)tempBuffer[2]) << 4) +
                                   HexVal(tempBuffer[3]));
                if (Char.IsHighSurrogate((char)wch))
                {
                    if (codeunits.Count > 0)
                    {
                        throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                        "Expected low surrogate char");
                    }
                    codeunits.Add((char)wch);
                }
                else if (Char.IsLowSurrogate((char)wch))
                {
                    if (codeunits.Count == 0)
                    {
                        throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                        "Expected high surrogate char");
                    }
                    codeunits.Add((char)wch);
                    var tmp = utf8Encoding.GetBytes(codeunits.ToArray());
                    buffer.Write(tmp, 0, tmp.Length);
                    codeunits.Clear();
                }
                else
                {
                    var tmp = utf8Encoding.GetBytes(new char[] { (char)wch });
                    buffer.Write(tmp, 0, tmp.Length);
                }
            }


            if (codeunits.Count > 0)
            {
                throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                "Expected low surrogate char");
            }

            return buffer.ToArray();
        }

        /// <summary>
        /// Return true if the given byte could be a valid part of a JSON number.
        /// </summary>
        private bool IsJSONNumeric(byte b)
        {
            switch (b)
            {
                case (byte)'+':
                case (byte)'-':
                case (byte)'.':
                case (byte)'0':
                case (byte)'1':
                case (byte)'2':
                case (byte)'3':
                case (byte)'4':
                case (byte)'5':
                case (byte)'6':
                case (byte)'7':
                case (byte)'8':
                case (byte)'9':
                case (byte)'E':
                case (byte)'e':
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Read in a sequence of characters that are all valid in JSON numbers. Does
        /// not do a complete regex check to validate that this is actually a number.
        /// </summary>
        private string ReadJSONNumericChars()
        {
            StringBuilder strbld = new StringBuilder();
            while (true)
            {
                byte ch = reader.Peek();
                if (!IsJSONNumeric(ch))
                {
                    break;
                }
                strbld.Append((char)reader.Read());
            }
            return strbld.ToString();
        }

        /// <summary>
        /// Read in a JSON number. If the context dictates, Read in enclosing quotes.
        /// </summary>
        private long ReadJSONInteger()
        {
            context.Read();
            if (context.EscapeNumbers())
            {
                ReadJSONSyntaxChar(QUOTE);
            }

            string str = ReadJSONNumericChars();
            if (context.EscapeNumbers())
            {
                ReadJSONSyntaxChar(QUOTE);
            }

            try
            {
                return Int64.Parse(str);
            }
            catch (FormatException fex)
            {
                throw new TProtocolException(TProtocolException.INVALID_DATA,
                                             "Bad data encounted in numeric data", fex);
            }
        }

        /// <summary>
        /// Read in a JSON double value. Throw if the value is not wrapped in quotes
        /// when expected or if wrapped in quotes when not expected.
        /// </summary>
        private double ReadJSONDouble()
        {
            context.Read();
            if (reader.Peek() == QUOTE[0])
            {
                byte[] arr = ReadJSONString(true);
                double dub = Double.Parse(utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture);

                if (!context.EscapeNumbers() && !Double.IsNaN(dub) && !Double.IsInfinity(dub))
                {
                    // Throw exception -- we should not be in a string in this case
                    throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                 "Numeric data unexpectedly quoted");
                }
                return dub;
            }
            else
            {
                if (context.EscapeNumbers())
                {
                    // This will throw - we should have had a quote if escapeNum == true
                    ReadJSONSyntaxChar(QUOTE);
                }
                try
                {
                    return Double.Parse(ReadJSONNumericChars(), CultureInfo.InvariantCulture);
                }
                catch (FormatException fex)
                {
                    throw new TProtocolException(TProtocolException.INVALID_DATA,
                                                 "Bad data encounted in numeric data", fex);
                }
            }
        }

        /// <summary>
        /// Read in a JSON string containing base-64 encoded data and decode it.
        /// </summary>
        private byte[] ReadJSONBase64()
        {
            byte[] b = ReadJSONString(false);
            int len = b.Length;
            int off = 0;
            int size = 0;
            // reduce len to ignore fill bytes
            while ((len > 0) && (b[len - 1] == '='))
            {
                --len;
            }
            // read & decode full byte triplets = 4 source bytes
            while (len > 4)
            {
                // Decode 4 bytes at a time
                TBase64Utils.decode(b, off, 4, b, size); // NB: decoded in place
                off += 4;
                len -= 4;
                size += 3;
            }
            // Don't decode if we hit the end or got a single leftover byte (invalid
            // base64 but legal for skip of regular string type)
            if (len > 1)
            {
                // Decode remainder
                TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place
                size += len - 1;
            }
            // Sadly we must copy the byte[] (any way around this?)
            byte[] result = new byte[size];
            Array.Copy(b, 0, result, 0, size);
            return result;
        }

        private void ReadJSONObjectStart()
        {
            context.Read();
            ReadJSONSyntaxChar(LBRACE);
            PushContext(new JSONPairContext(this));
        }

        private void ReadJSONObjectEnd()
        {
            ReadJSONSyntaxChar(RBRACE);
            PopContext();
        }

        private void ReadJSONArrayStart()
        {
            context.Read();
            ReadJSONSyntaxChar(LBRACKET);
            PushContext(new JSONListContext(this));
        }

        private void ReadJSONArrayEnd()
        {
            ReadJSONSyntaxChar(RBRACKET);
            PopContext();
        }

        public override TMessage ReadMessageBegin()
        {
            TMessage message = new TMessage();
            ReadJSONArrayStart();
            if (ReadJSONInteger() != VERSION)
            {
                throw new TProtocolException(TProtocolException.BAD_VERSION,
                                             "Message contained bad version.");
            }

            var buf = ReadJSONString(false);
            message.Name = utf8Encoding.GetString(buf, 0, buf.Length);
            message.Type = (TMessageType)ReadJSONInteger();
            message.SeqID = (int)ReadJSONInteger();
            return message;
        }

        public override void ReadMessageEnd()
        {
            ReadJSONArrayEnd();
        }

        public override TStruct ReadStructBegin()
        {
            ReadJSONObjectStart();
            return new TStruct();
        }

        public override void ReadStructEnd()
        {
            ReadJSONObjectEnd();
        }

        public override TField ReadFieldBegin()
        {
            TField field = new TField();
            byte ch = reader.Peek();
            if (ch == RBRACE[0])
            {
                field.Type = TType.Stop;
            }
            else
            {
                field.ID = (short)ReadJSONInteger();
                ReadJSONObjectStart();
                field.Type = GetTypeIDForTypeName(ReadJSONString(false));
            }
            return field;
        }

        public override void ReadFieldEnd()
        {
            ReadJSONObjectEnd();
        }

        public override TMap ReadMapBegin()
        {
            TMap map = new TMap();
            ReadJSONArrayStart();
            map.KeyType = GetTypeIDForTypeName(ReadJSONString(false));
            map.ValueType = GetTypeIDForTypeName(ReadJSONString(false));
            map.Count = (int)ReadJSONInteger();
            ReadJSONObjectStart();
            return map;
        }

        public override void ReadMapEnd()
        {
            ReadJSONObjectEnd();
            ReadJSONArrayEnd();
        }

        public override TList ReadListBegin()
        {
            TList list = new TList();
            ReadJSONArrayStart();
            list.ElementType = GetTypeIDForTypeName(ReadJSONString(false));
            list.Count = (int)ReadJSONInteger();
            return list;
        }

        public override void ReadListEnd()
        {
            ReadJSONArrayEnd();
        }

        public override TSet ReadSetBegin()
        {
            TSet set = new TSet();
            ReadJSONArrayStart();
            set.ElementType = GetTypeIDForTypeName(ReadJSONString(false));
            set.Count = (int)ReadJSONInteger();
            return set;
        }

        public override void ReadSetEnd()
        {
            ReadJSONArrayEnd();
        }

        public override bool ReadBool()
        {
            return (ReadJSONInteger() == 0 ? false : true);
        }

        public override sbyte ReadByte()
        {
            return (sbyte)ReadJSONInteger();
        }

        public override short ReadI16()
        {
            return (short)ReadJSONInteger();
        }

        public override int ReadI32()
        {
            return (int)ReadJSONInteger();
        }

        public override long ReadI64()
        {
            return (long)ReadJSONInteger();
        }

        public override double ReadDouble()
        {
            return ReadJSONDouble();
        }

        public override string ReadString()
        {
            var buf = ReadJSONString(false);
            return utf8Encoding.GetString(buf, 0, buf.Length);
        }

        public override byte[] ReadBinary()
        {
            return ReadJSONBase64();
        }

    }
}
