#region Header
/**
 * JsonReader.cs
 *   Stream-like access to JSON text.
 *
 * The authors disclaim copyright to this source code. For more details, see
 * the COPYING file included with this distribution.
 **/
#endregion

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

namespace SSCMS.Core.Utils.Serialization.LitJson
{
    public enum JsonToken
    {
        None,

        ObjectStart,
        PropertyName,
        ObjectEnd,

        ArrayStart,
        ArrayEnd,

        Int,
        Long,
        Double,

        String,

        Boolean,
        Null
    }


    public class JsonReader
    {
        #region Fields
        private static IDictionary<int, IDictionary<int, int[]>> parse_table;

        private Stack<int>    automaton_stack;
        private int           current_input;
        private int           current_symbol;
        private bool          end_of_json;
        private bool          end_of_input;
        private Lexer         lexer;
        private bool          parser_in_string;
        private bool          parser_return;
        private bool          read_started;
        private TextReader    reader;
        private bool          reader_is_owned;
        private object        token_value;
        private JsonToken     token;
        #endregion


        #region Public Properties
        public bool AllowComments {
            get { return lexer.AllowComments; }
            set { lexer.AllowComments = value; }
        }

        public bool AllowSingleQuotedStrings {
            get { return lexer.AllowSingleQuotedStrings; }
            set { lexer.AllowSingleQuotedStrings = value; }
        }

        public bool EndOfInput => end_of_input;

        public bool EndOfJson => end_of_json;

        public JsonToken Token => token;

        public object Value => token_value;

        #endregion


        #region Constructors
        static JsonReader ()
        {
            PopulateParseTable ();
        }

        public JsonReader (string json_text) :
            this (new StringReader (json_text), true)
        {
        }

        public JsonReader (TextReader reader) :
            this (reader, false)
        {
        }

        private JsonReader (TextReader reader, bool owned)
        {
            if (reader == null)
                throw new ArgumentNullException ("reader");

            parser_in_string = false;
            parser_return = false;

            read_started = false;
            automaton_stack = new Stack<int> ();
            automaton_stack.Push ((int) ParserToken.End);
            automaton_stack.Push ((int) ParserToken.Text);

            lexer = new Lexer (reader);

            end_of_input = false;
            end_of_json  = false;

            this.reader = reader;
            reader_is_owned = owned;
        }
        #endregion


        #region Static Methods
        private static void PopulateParseTable ()
        {
            parse_table = new Dictionary<int, IDictionary<int, int[]>> ();

            TableAddRow (ParserToken.Array);
            TableAddCol (ParserToken.Array, '[',
                         '[',
                         (int) ParserToken.ArrayPrime);

            TableAddRow (ParserToken.ArrayPrime);
            TableAddCol (ParserToken.ArrayPrime, '"',
                         (int) ParserToken.Value,

                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, '[',
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, ']',
                         ']');
            TableAddCol (ParserToken.ArrayPrime, '{',
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.Number,
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.True,
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.False,
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');
            TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.Null,
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest,
                         ']');

            TableAddRow (ParserToken.Object);
            TableAddCol (ParserToken.Object, '{',
                         '{',
                         (int) ParserToken.ObjectPrime);

            TableAddRow (ParserToken.ObjectPrime);
            TableAddCol (ParserToken.ObjectPrime, '"',
                         (int) ParserToken.Pair,
                         (int) ParserToken.PairRest,
                         '}');
            TableAddCol (ParserToken.ObjectPrime, '}',
                         '}');

            TableAddRow (ParserToken.Pair);
            TableAddCol (ParserToken.Pair, '"',
                         (int) ParserToken.String,
                         ':',
                         (int) ParserToken.Value);

            TableAddRow (ParserToken.PairRest);
            TableAddCol (ParserToken.PairRest, ',',
                         ',',
                         (int) ParserToken.Pair,
                         (int) ParserToken.PairRest);
            TableAddCol (ParserToken.PairRest, '}',
                         (int) ParserToken.Epsilon);

            TableAddRow (ParserToken.String);
            TableAddCol (ParserToken.String, '"',
                         '"',
                         (int) ParserToken.CharSeq,
                         '"');

            TableAddRow (ParserToken.Text);
            TableAddCol (ParserToken.Text, '[',
                         (int) ParserToken.Array);
            TableAddCol (ParserToken.Text, '{',
                         (int) ParserToken.Object);

            TableAddRow (ParserToken.Value);
            TableAddCol (ParserToken.Value, '"',
                         (int) ParserToken.String);
            TableAddCol (ParserToken.Value, '[',
                         (int) ParserToken.Array);
            TableAddCol (ParserToken.Value, '{',
                         (int) ParserToken.Object);
            TableAddCol (ParserToken.Value, (int) ParserToken.Number,
                         (int) ParserToken.Number);
            TableAddCol (ParserToken.Value, (int) ParserToken.True,
                         (int) ParserToken.True);
            TableAddCol (ParserToken.Value, (int) ParserToken.False,
                         (int) ParserToken.False);
            TableAddCol (ParserToken.Value, (int) ParserToken.Null,
                         (int) ParserToken.Null);

            TableAddRow (ParserToken.ValueRest);
            TableAddCol (ParserToken.ValueRest, ',',
                         ',',
                         (int) ParserToken.Value,
                         (int) ParserToken.ValueRest);
            TableAddCol (ParserToken.ValueRest, ']',
                         (int) ParserToken.Epsilon);
        }

        private static void TableAddCol (ParserToken row, int col,
                                         params int[] symbols)
        {
            parse_table[(int) row].Add (col, symbols);
        }

        private static void TableAddRow (ParserToken rule)
        {
            parse_table.Add ((int) rule, new Dictionary<int, int[]> ());
        }
        #endregion


        #region Private Methods
        private void ProcessNumber (string number)
        {
            if (number.IndexOf ('.') != -1 ||
                number.IndexOf ('e') != -1 ||
                number.IndexOf ('E') != -1) {

                double n_double;
                if (Double.TryParse (number, out n_double)) {
                    token = JsonToken.Double;
                    token_value = n_double;

                    return;
                }
            }

            int n_int32;
            if (Int32.TryParse (number, out n_int32)) {
                token = JsonToken.Int;
                token_value = n_int32;

                return;
            }

            long n_int64;
            if (Int64.TryParse (number, out n_int64)) {
                token = JsonToken.Long;
                token_value = n_int64;

                return;
            }

            // Shouldn't happen, but just in case, return something
            token = JsonToken.Int;
            token_value = 0;
        }

        private void ProcessSymbol ()
        {
            if (current_symbol == '[')  {
                token = JsonToken.ArrayStart;
                parser_return = true;

            } else if (current_symbol == ']')  {
                token = JsonToken.ArrayEnd;
                parser_return = true;

            } else if (current_symbol == '{')  {
                token = JsonToken.ObjectStart;
                parser_return = true;

            } else if (current_symbol == '}')  {
                token = JsonToken.ObjectEnd;
                parser_return = true;

            } else if (current_symbol == '"')  {
                if (parser_in_string) {
                    parser_in_string = false;

                    parser_return = true;

                } else {
                    if (token == JsonToken.None)
                        token = JsonToken.String;

                    parser_in_string = true;
                }

            } else if (current_symbol == (int) ParserToken.CharSeq) {
                token_value = lexer.StringValue;

            } else if (current_symbol == (int) ParserToken.False)  {
                token = JsonToken.Boolean;
                token_value = false;
                parser_return = true;

            } else if (current_symbol == (int) ParserToken.Null)  {
                token = JsonToken.Null;
                parser_return = true;

            } else if (current_symbol == (int) ParserToken.Number)  {
                ProcessNumber (lexer.StringValue);

                parser_return = true;

            } else if (current_symbol == (int) ParserToken.Pair)  {
                token = JsonToken.PropertyName;

            } else if (current_symbol == (int) ParserToken.True)  {
                token = JsonToken.Boolean;
                token_value = true;
                parser_return = true;

            }
        }

        private bool ReadToken ()
        {
            if (end_of_input)
                return false;

            lexer.NextToken ();

            if (lexer.EndOfInput) {
                Close ();

                return false;
            }

            current_input = lexer.Token;

            return true;
        }
        #endregion


        public void Close ()
        {
            if (end_of_input)
                return;

            end_of_input = true;
            end_of_json  = true;

            if (reader_is_owned)
                reader.Close ();

            reader = null;
        }

        public bool Read ()
        {
            if (end_of_input)
                return false;

            if (end_of_json) {
                end_of_json = false;
                automaton_stack.Clear ();
                automaton_stack.Push ((int) ParserToken.End);
                automaton_stack.Push ((int) ParserToken.Text);
            }

            parser_in_string = false;
            parser_return    = false;

            token       = JsonToken.None;
            token_value = null;

            if (! read_started) {
                read_started = true;

                if (! ReadToken ())
                    return false;
            }


            int[] entry_symbols;

            while (true) {
                if (parser_return) {
                    if (automaton_stack.Peek () == (int) ParserToken.End)
                        end_of_json = true;

                    return true;
                }

                current_symbol = automaton_stack.Pop ();

                ProcessSymbol ();

                if (current_symbol == current_input) {
                    if (! ReadToken ()) {
                        if (automaton_stack.Peek () != (int) ParserToken.End)
                            throw new JsonException (
                                "Input doesn't evaluate to proper JSON text");

                        if (parser_return)
                            return true;

                        return false;
                    }

                    continue;
                }

                try {

                    entry_symbols =
                        parse_table[current_symbol][current_input];

                } catch (KeyNotFoundException e) {
                    throw new JsonException ((ParserToken) current_input, e);
                }

                if (entry_symbols[0] == (int) ParserToken.Epsilon)
                    continue;

                for (var i = entry_symbols.Length - 1; i >= 0; i--)
                    automaton_stack.Push (entry_symbols[i]);
            }
        }

    }
}
