/*
 * Based on the miniJSON by Calvin Rien
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

public static class WortiseJson
{
  public static object Deserialize (string json)
  {  
    if (json == null) {
      return null;
    }
    return Parser.Parse (json);
  }

  sealed class Parser : IDisposable
  {
    const string WHITE_SPACE = " \t\n\r";
    const string WORD_BREAK = " \t\n\r{}[],:\"";

    enum TOKEN
    {
      NONE,
      CURLY_OPEN,
      CURLY_CLOSE,
      SQUARED_OPEN,
      SQUARED_CLOSE,
      COLON,
      COMMA,
      STRING,
      NUMBER,
      TRUE,
      FALSE,
      NULL
          }
    ;

    StringReader json;

    Parser (string jsonString)
    {
      json = new StringReader (jsonString);
    }

    public static object Parse (string jsonString)
    {
      using (var instance = new Parser(jsonString)) {
        return instance.ParseValue ();
      }
    }

    public void Dispose ()
    {
      json.Dispose ();
      json = null;
    }

    Dictionary<string, object> ParseObject ()
    {
      Dictionary<string, object> table = new Dictionary<string, object> ();

      // ditch opening brace
      json.Read ();

      // {
      while (true) {
        switch (NextToken) {
        case TOKEN.NONE:
          return null;
        case TOKEN.COMMA:
          continue;
        case TOKEN.CURLY_CLOSE:
          return table;
        default:
                      // name
          string name = ParseString ();
          if (name == null) {
            return null;
          }

                      // :
          if (NextToken != TOKEN.COLON) {
            return null;
          }
                      // ditch the colon
          json.Read ();

                      // value
          table [name] = ParseValue ();
          break;
        }
      }
    }

    List<object> ParseArray ()
    {
      List<object> array = new List<object> ();

      // ditch opening bracket
      json.Read ();

      // [
      var parsing = true;
      while (parsing) {
        TOKEN nextToken = NextToken;

        switch (nextToken) {
        case TOKEN.NONE:
          return null;
        case TOKEN.COMMA:
          continue;
        case TOKEN.SQUARED_CLOSE:
          parsing = false;
          break;
        default:
          object value = ParseByToken (nextToken);

          array.Add (value);
          break;
        }
      }

      return array;
    }

    object ParseValue ()
    {
      TOKEN nextToken = NextToken;
      return ParseByToken (nextToken);
    }

    object ParseByToken (TOKEN token)
    {
      switch (token) {
      case TOKEN.STRING:
        return ParseString ();
      case TOKEN.NUMBER:
        return ParseNumber ();
      case TOKEN.CURLY_OPEN:
        return ParseObject ();
      case TOKEN.SQUARED_OPEN:
        return ParseArray ();
      case TOKEN.TRUE:
        return true;
      case TOKEN.FALSE:
        return false;
      case TOKEN.NULL:
        return null;
      default:
        return null;
      }
    }

    string ParseString ()
    {
      StringBuilder s = new StringBuilder ();
      char c;

      // ditch opening quote
      json.Read ();

      bool parsing = true;
      while (parsing) {

        if (json.Peek () == -1) {
          parsing = false;
          break;
        }

        c = NextChar;
        switch (c) {
        case '"':
          parsing = false;
          break;
        case '\\':
          if (json.Peek () == -1) {
            parsing = false;
            break;
          }

          c = NextChar;
          switch (c) {
          case '"':
          case '\\':
          case '/':
            s.Append (c);
            break;
          case 'b':
            s.Append ('\b');
            break;
          case 'f':
            s.Append ('\f');
            break;
          case 'n':
            s.Append ('\n');
            break;
          case 'r':
            s.Append ('\r');
            break;
          case 't':
            s.Append ('\t');
            break;
          case 'u':
            var hex = new StringBuilder ();

            for (int i=0; i< 4; i++) {
              hex.Append (NextChar);
            }

            s.Append ((char)Convert.ToInt32 (hex.ToString (), 16));
            break;
          }
          break;
        default:
          s.Append (c);
          break;
        }
      }

      return s.ToString ();
    }

    object ParseNumber ()
    {
      string number = NextWord;

      if (number.IndexOf ('.') == -1) {
        long parsedInt;
        Int64.TryParse (number, out parsedInt);
        return parsedInt;
      }

      double parsedDouble;
      Double.TryParse (number, out parsedDouble);
      return parsedDouble;
    }

    void EatWhitespace ()
    {
      while (WHITE_SPACE.IndexOf(PeekChar) != -1) {
        json.Read ();

        if (json.Peek () == -1) {
          break;
        }
      }
    }

    char PeekChar {
      get {
        return Convert.ToChar (json.Peek ());
      }
    }

    char NextChar {
      get {
        return Convert.ToChar (json.Read ());
      }
    }

    string NextWord {
      get {
        StringBuilder word = new StringBuilder ();

        while (WORD_BREAK.IndexOf(PeekChar) == -1) {
          word.Append (NextChar);

          if (json.Peek () == -1) {
            break;
          }
        }

        return word.ToString ();
      }
    }

    TOKEN NextToken {
      get {
        EatWhitespace ();

        if (json.Peek () == -1) {
          return TOKEN.NONE;
        }

        char c = PeekChar;
        switch (c) {
        case '{':
          return TOKEN.CURLY_OPEN;
        case '}':
          json.Read ();
          return TOKEN.CURLY_CLOSE;
        case '[':
          return TOKEN.SQUARED_OPEN;
        case ']':
          json.Read ();
          return TOKEN.SQUARED_CLOSE;
        case ',':
          json.Read ();
          return TOKEN.COMMA;
        case '"':
          return TOKEN.STRING;
        case ':':
          return TOKEN.COLON;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '-':
          return TOKEN.NUMBER;
        }

        string word = NextWord;

        switch (word) {
        case "false":
          return TOKEN.FALSE;
        case "true":
          return TOKEN.TRUE;
        case "null":
          return TOKEN.NULL;
        }

        return TOKEN.NONE;
      }
    }
  }

  /// <summary>
  /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
  /// </summary>
  /// <param name="json">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
  /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
  public static string Serialize (object obj)
  {
    return Serializer.Serialize (obj);
  }

  sealed class Serializer
  {
    StringBuilder builder;

    Serializer ()
    {
      builder = new StringBuilder ();
    }

    public static string Serialize (object obj)
    {
      var instance = new Serializer ();

      instance.SerializeValue (obj);

      return instance.builder.ToString ();
    }

    void SerializeValue (object value)
    {
      IList asList;
      IDictionary asDict;
      string asStr;

      if (value == null) {
        builder.Append ("null");
      } else if ((asStr = value as string) != null) {
        SerializeString (asStr);
      } else if (value is bool) {
        builder.Append (value.ToString ().ToLower ());
      } else if ((asList = value as IList) != null) {
        SerializeArray (asList);
      } else if ((asDict = value as IDictionary) != null) {
        SerializeObject (asDict);
      } else if (value is char) {
        SerializeString (value.ToString ());
      } else {
        SerializeOther (value);
      }
    }

    void SerializeObject (IDictionary obj)
    {
      bool first = true;

      builder.Append ('{');

      foreach (object e in obj.Keys) {
        if (!first) {
          builder.Append (',');
        }

        SerializeString (e.ToString ());
        builder.Append (':');

        SerializeValue (obj [e]);

        first = false;
      }

      builder.Append ('}');
    }

    void SerializeArray (IList anArray)
    {
      builder.Append ('[');

      bool first = true;

      foreach (object obj in anArray) {
        if (!first) {
          builder.Append (',');
        }

        SerializeValue (obj);

        first = false;
      }

      builder.Append (']');
    }

    void SerializeString (string str)
    {
      builder.Append ('\"');

      char[] charArray = str.ToCharArray ();
      foreach (var c in charArray) {
        switch (c) {
        case '"':
          builder.Append ("\\\"");
          break;
        case '\\':
          builder.Append ("\\\\");
          break;
        case '\b':
          builder.Append ("\\b");
          break;
        case '\f':
          builder.Append ("\\f");
          break;
        case '\n':
          builder.Append ("\\n");
          break;
        case '\r':
          builder.Append ("\\r");
          break;
        case '\t':
          builder.Append ("\\t");
          break;
        default:
          int codepoint = Convert.ToInt32 (c);
          if ((codepoint >= 32) && (codepoint <= 126)) {
            builder.Append (c);
          } else {
            builder.Append ("\\u" + Convert.ToString (codepoint, 16).PadLeft (4, '0'));
          }
          break;
        }
      }

      builder.Append ('\"');
    }

    void SerializeOther (object value)
    {
      if (value is float
        || value is int
        || value is uint
        || value is long
        || value is double
        || value is sbyte
        || value is byte
        || value is short
        || value is ushort
        || value is ulong
        || value is decimal) {
        builder.Append (value.ToString ());
      } else {
        SerializeString (value.ToString ());
      }
    }
  }
}