using System;
using System.Collections;
using System.Text;
using System.Collections.Generic;
/* Based on the JSON parser from 
 * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
 * 
 * I simplified it so that it doesn't throw exceptions
 * and can be used in Unity iPhone with maximum code stripping.
 */
/// 
/// This class encodes and decodes JSON strings.
/// Spec. details, see http://www.json.org/
/// 
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
/// All numbers are parsed to doubles.
/// 
public class MiniJSON
{
	private const int TOKEN_NONE = 0;
	private const int TOKEN_CURLY_OPEN = 1;
	private const int TOKEN_CURLY_CLOSE = 2;
	private const int TOKEN_SQUARED_OPEN = 3;
	private const int TOKEN_SQUARED_CLOSE = 4;
	private const int TOKEN_COLON = 5;
	private const int TOKEN_COMMA = 6;
	private const int TOKEN_STRING = 7;
	private const int TOKEN_NUMBER = 8;
	private const int TOKEN_TRUE = 9;
	private const int TOKEN_FALSE = 10;
	private const int TOKEN_NULL = 11;
	private const int BUILDER_CAPACITY = 2000;
	/// 
	/// On decoding, this value holds the position at which the parse failed (-1 = no error).
	/// 
	protected static int lastErrorIndex = -1;
	protected static string lastDecode = "";
	/// 
	/// Parses the string json into a value
	/// 
	/// A JSON string.
	/// An ArrayList, a Hashtable, a double, a string, null, true, or false
	public static object jsonDecode( string json )
	{
		// save the string for debug information
		MiniJSON.lastDecode = json;
		if( json != null )
		{
			char[] charArray = json.ToCharArray();
			int index = 0;
			bool success = true;
			object value = MiniJSON.parseValue( charArray, ref index, ref success );
			if( success )
				MiniJSON.lastErrorIndex = -1;
			else
				MiniJSON.lastErrorIndex = index;
			return value;
		}
		else
		{
			return null;
		}
	}
	/// 
	/// Converts a Hashtable / ArrayList / Dictionary(string,string) object into a JSON string
	/// 
	/// A Hashtable / ArrayList
	/// A JSON encoded string, or null if object 'json' is not serializable
	public static string jsonEncode( object json )
	{
		var builder = new StringBuilder( BUILDER_CAPACITY );
		var success = MiniJSON.serializeValue( json, builder );
		return ( success ? builder.ToString() : null );
	}
	/// 
	/// On decoding, this function returns the position at which the parse failed (-1 = no error).
	/// 
	/// 
	public static bool lastDecodeSuccessful()
	{
		return ( MiniJSON.lastErrorIndex == -1 );
	}
	/// 
	/// On decoding, this function returns the position at which the parse failed (-1 = no error).
	/// 
	/// 
	public static int getLastErrorIndex()
	{
		return MiniJSON.lastErrorIndex;
	}
	/// 
	/// If a decoding error occurred, this function returns a piece of the JSON string 
	/// at which the error took place. To ease debugging.
	/// 
	/// 
	public static string getLastErrorSnippet()
	{
		if( MiniJSON.lastErrorIndex == -1 )
		{
			return "";
		}
		else
		{
			int startIndex = MiniJSON.lastErrorIndex - 5;
			int endIndex = MiniJSON.lastErrorIndex + 15;
			if( startIndex < 0 )
				startIndex = 0;
			if( endIndex >= MiniJSON.lastDecode.Length )
				endIndex = MiniJSON.lastDecode.Length - 1;
			return MiniJSON.lastDecode.Substring( startIndex, endIndex - startIndex + 1 );
		}
	}
	#region Parsing
	protected static Hashtable parseObject( char[] json, ref int index )
	{
		Hashtable table = new Hashtable();
		int token;
		// {
		nextToken( json, ref index );
		bool done = false;
		while( !done )
		{
			token = lookAhead( json, index );
			if( token == MiniJSON.TOKEN_NONE )
			{
				return null;
			}
			else if( token == MiniJSON.TOKEN_COMMA )
			{
				nextToken( json, ref index );
			}
			else if( token == MiniJSON.TOKEN_CURLY_CLOSE )
			{
				nextToken( json, ref index );
				return table;
			}
			else
			{
				// name
				string name = parseString( json, ref index );
				if( name == null )
				{
					return null;
				}
				// :
				token = nextToken( json, ref index );
				if( token != MiniJSON.TOKEN_COLON )
					return null;
				// value
				bool success = true;
				object value = parseValue( json, ref index, ref success );
				if( !success )
					return null;
				table[name] = value;
			}
		}
		return table;
	}
	protected static ArrayList parseArray( char[] json, ref int index )
	{
		ArrayList array = new ArrayList();
		// [
		nextToken( json, ref index );
		bool done = false;
		while( !done )
		{
			int token = lookAhead( json, index );
			if( token == MiniJSON.TOKEN_NONE )
			{
				return null;
			}
			else if( token == MiniJSON.TOKEN_COMMA )
			{
				nextToken( json, ref index );
			}
			else if( token == MiniJSON.TOKEN_SQUARED_CLOSE )
			{
				nextToken( json, ref index );
				break;
			}
			else
			{
				bool success = true;
				object value = parseValue( json, ref index, ref success );
				if( !success )
					return null;
				array.Add( value );
			}
		}
		return array;
	}
	protected static object parseValue( char[] json, ref int index, ref bool success )
	{
		switch( lookAhead( json, index ) )
		{
		case MiniJSON.TOKEN_STRING:
			return parseString( json, ref index );
		case MiniJSON.TOKEN_NUMBER:
			return parseNumber( json, ref index );
		case MiniJSON.TOKEN_CURLY_OPEN:
			return parseObject( json, ref index );
		case MiniJSON.TOKEN_SQUARED_OPEN:
			return parseArray( json, ref index );
		case MiniJSON.TOKEN_TRUE:
			nextToken( json, ref index );
			return Boolean.Parse( "TRUE" );
		case MiniJSON.TOKEN_FALSE:
			nextToken( json, ref index );
			return Boolean.Parse( "FALSE" );
		case MiniJSON.TOKEN_NULL:
			nextToken( json, ref index );
			return null;
		case MiniJSON.TOKEN_NONE:
			break;
		}
		success = false;
		return null;
	}
	protected static string parseString( char[] json, ref int index )
	{
		string s = "";
		char c;
		eatWhitespace( json, ref index );
		// "
		c = json[index++];
		bool complete = false;
		while( !complete )
		{
			if( index == json.Length )
				break;
			c = json[index++];
			if( c == '"' )
			{
				complete = true;
				break;
			}
			else if( c == '\\' )
			{
				if( index == json.Length )
					break;
				c = json[index++];
				if( c == '"' )
				{
					s += '"';
				}
				else if( c == '\\' )
				{
					s += '\\';
				}
				else if( c == '/' )
				{
					s += '/';
				}
				else if( c == 'b' )
				{
					s += '\b';
				}
				else if( c == 'f' )
				{
					s += '\f';
				}
				else if( c == 'n' )
				{
					s += '\n';
				}
				else if( c == 'r' )
				{
					s += '\r';
				}
				else if( c == 't' )
				{
					s += '\t';
				}
				else if( c == 'u' )
				{
					int remainingLength = json.Length - index;
					if( remainingLength >= 4 )
					{
						char[] unicodeCharArray = new char[4];
						Array.Copy( json, index, unicodeCharArray, 0, 4 );
						uint codePoint = UInt32.Parse( new string( unicodeCharArray ), System.Globalization.NumberStyles.HexNumber );
						// convert the integer codepoint to a unicode char and add to string
						s += Char.ConvertFromUtf32( (int)codePoint );
						// skip 4 chars
						index += 4;
					}
					else
					{
						break;
					}
				}
			}
			else
			{
				s += c;
			}
		}
		if( !complete )
			return null;
		return s;
	}
	protected static double parseNumber( char[] json, ref int index )
	{
		eatWhitespace( json, ref index );
		int lastIndex = getLastIndexOfNumber( json, index );
		int charLength = ( lastIndex - index ) + 1;
		char[] numberCharArray = new char[charLength];
		Array.Copy( json, index, numberCharArray, 0, charLength );
		index = lastIndex + 1;
		return Double.Parse( new string( numberCharArray ) ); // , CultureInfo.InvariantCulture);
	}
	protected static int getLastIndexOfNumber( char[] json, int index )
	{
		int lastIndex;
		for( lastIndex = index; lastIndex < json.Length; lastIndex++ )
			if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 )
			{
				break;
			}
		return lastIndex - 1;
	}
	protected static void eatWhitespace( char[] json, ref int index )
	{
		for( ; index < json.Length; index++ )
			if( " \t\n\r".IndexOf( json[index] ) == -1 )
			{
				break;
			}
	}
	protected static int lookAhead( char[] json, int index )
	{
		int saveIndex = index;
		return nextToken( json, ref saveIndex );
	}
	protected static int nextToken( char[] json, ref int index )
	{
		eatWhitespace( json, ref index );
		if( index == json.Length )
		{
			return MiniJSON.TOKEN_NONE;
		}
		char c = json[index];
		index++;
		switch( c )
		{
		case '{':
			return MiniJSON.TOKEN_CURLY_OPEN;
		case '}':
			return MiniJSON.TOKEN_CURLY_CLOSE;
		case '[':
			return MiniJSON.TOKEN_SQUARED_OPEN;
		case ']':
			return MiniJSON.TOKEN_SQUARED_CLOSE;
		case ',':
			return MiniJSON.TOKEN_COMMA;
		case '"':
			return MiniJSON.TOKEN_STRING;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4': 
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '-': 
			return MiniJSON.TOKEN_NUMBER;
		case ':':
			return MiniJSON.TOKEN_COLON;
		}
		index--;
		int remainingLength = json.Length - index;
		// false
		if( remainingLength >= 5 )
		{
			if( json[index] == 'f' &&
				json[index + 1] == 'a' &&
				json[index + 2] == 'l' &&
				json[index + 3] == 's' &&
				json[index + 4] == 'e' )
			{
				index += 5;
				return MiniJSON.TOKEN_FALSE;
			}
		}
		// true
		if( remainingLength >= 4 )
		{
			if( json[index] == 't' &&
				json[index + 1] == 'r' &&
				json[index + 2] == 'u' &&
				json[index + 3] == 'e' )
			{
				index += 4;
				return MiniJSON.TOKEN_TRUE;
			}
		}
		// null
		if( remainingLength >= 4 )
		{
			if( json[index] == 'n' &&
				json[index + 1] == 'u' &&
				json[index + 2] == 'l' &&
				json[index + 3] == 'l' )
			{
				index += 4;
				return MiniJSON.TOKEN_NULL;
			}
		}
		return MiniJSON.TOKEN_NONE;
	}
	#endregion
	#region Serialization
	protected static bool serializeObjectOrArray( object objectOrArray, StringBuilder builder )
	{
		if( objectOrArray is Hashtable )
		{
			return serializeObject( (Hashtable)objectOrArray, builder );
		}
		else if( objectOrArray is ArrayList )
		{
			return serializeArray( (ArrayList)objectOrArray, builder );
		}
		else
		{
			return false;
		}
	}
	protected static bool serializeObject( Hashtable anObject, StringBuilder builder )
	{
		builder.Append( "{" );
		IDictionaryEnumerator e = anObject.GetEnumerator();
		bool first = true;
		while( e.MoveNext() )
		{
			string key = e.Key.ToString();
			object value = e.Value;
			if( !first )
			{
				builder.Append( ", " );
			}
			serializeString( key, builder );
			builder.Append( ":" );
			if( !serializeValue( value, builder ) )
			{
				return false;
			}
			first = false;
		}
		builder.Append( "}" );
		return true;
	}
	protected static bool serializeDictionary( Dictionary dict, StringBuilder builder )
	{
		builder.Append( "{" );
		bool first = true;
		foreach( var kv in dict )
		{
			if( !first )
				builder.Append( ", " );
			serializeString( kv.Key, builder );
			builder.Append( ":" );
			serializeString( kv.Value, builder );
			first = false;
		}
		builder.Append( "}" );
		return true;
	}
	protected static bool serializeArray( ArrayList anArray, StringBuilder builder )
	{
		builder.Append( "[" );
		bool first = true;
		for( int i = 0; i < anArray.Count; i++ )
		{
			object value = anArray[i];
			if( !first )
			{
				builder.Append( ", " );
			}
			if( !serializeValue( value, builder ) )
			{
				return false;
			}
			first = false;
		}
		builder.Append( "]" );
		return true;
	}
	protected static bool serializeValue( object value, StringBuilder builder )
	{
		//Type t = value.GetType();
		//UnityEngine.Debug.Log("type: " + t.ToString() + " isArray: " + t.IsArray);
		if( value == null )
		{
			builder.Append( "null" );
		}
		else if( value.GetType().IsArray )
		{
			serializeArray( new ArrayList( (ICollection)value ), builder );
		}
		else if( value is string )
		{
			serializeString( (string)value, builder );
		}
		else if( value is Char )
		{
			serializeString( Convert.ToString( (char)value ), builder );
		}
		else if( value is decimal )
		{
			serializeString( Convert.ToString( (decimal)value ), builder );
		}
		else if( value is Hashtable )
		{
			serializeObject( (Hashtable)value, builder );
		}
		else if( value is Dictionary )
		{
			serializeDictionary( (Dictionary)value, builder );
		}
		else if( value is ArrayList )
		{
			serializeArray( (ArrayList)value, builder );
		}
		else if( ( value is Boolean ) && ( (Boolean)value == true ) )
		{
			builder.Append( "true" );
		}
		else if( ( value is Boolean ) && ( (Boolean)value == false ) )
		{
			builder.Append( "false" );
		}
		else if( value.GetType().IsPrimitive )
		{
			serializeNumber( Convert.ToDouble( value ), builder );
		}
		else
		{
			return false;
		}
		return true;
	}
	protected static void serializeString( string aString, StringBuilder builder )
	{
		builder.Append( "\"" );
		char[] charArray = aString.ToCharArray();
		for( int i = 0; i < charArray.Length; i++ )
		{
			char c = charArray[i];
			if( c == '"' )
			{
				builder.Append( "\\\"" );
			}
			else if( c == '\\' )
			{
				builder.Append( "\\\\" );
			}
			else if( c == '\b' )
			{
				builder.Append( "\\b" );
			}
			else if( c == '\f' )
			{
				builder.Append( "\\f" );
			}
			else if( c == '\n' )
			{
				builder.Append( "\\n" );
			}
			else if( c == '\r' )
			{
				builder.Append( "\\r" );
			}
			else if( c == '\t' )
			{
				builder.Append( "\\t" );
			}
			else
			{
				int codepoint = Convert.ToInt32( c );
				if( ( codepoint >= 32 ) && ( codepoint <= 126 ) )
				{
					builder.Append( c );
				}
				else
				{
					builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) );
				}
			}
		}
		builder.Append( "\"" );
	}
	protected static void serializeNumber( double number, StringBuilder builder )
	{
		builder.Append( Convert.ToString( number ) ); // , CultureInfo.InvariantCulture));
	}
	#endregion
}
#region Extension methods
public static class MiniJsonExtensions
{
	public static string toJson( this Hashtable obj )
	{
		return MiniJSON.jsonEncode( obj );
	}
	public static string toJson( this Dictionary obj )
	{
		return MiniJSON.jsonEncode( obj );
	}
	public static ArrayList arrayListFromJson( this string json )
	{
		return MiniJSON.jsonDecode( json ) as ArrayList;
	}
	public static Hashtable hashtableFromJson( this string json )
	{
		return MiniJSON.jsonDecode( json ) as Hashtable;
	}
}
#endregion