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

using System.Text;



using System;


namespace Rokojori
{ 
  public class JSONDeserializer
  {
    JSONSerializationSettings settings = new JSONSerializationSettings();
    Dictionary<Type,CustomSerializer> customSerializers = null; 


    public JSONDeserializer ( JSONSerializationSettings settings )
    {
      this.settings = settings == null ? new JSONSerializationSettings() : settings;
    }

    public T Deserialize<T>( string data ) where T:new()
    { 
      return Deserialize<T>( JSON.Parse( data ).AsObject() );
    }    

    public T Deserialize<T>( JSONObject jsonObject ) where T:new()
    { 
      Initialize();

      var output = new T();
      var keys = jsonObject.keys;

      for ( int i = 0; i < keys.Count; i++)      
      {
        var key = keys[ i ];
        DeserializeMemberField( jsonObject.Get( key ), output, key ); 
      }

      return output;
    }

    public void Deserialize<T>( string data, T output )
    { 
      Deserialize<T>( JSON.Parse( data ).AsObject(), output );
    }

    public void Deserialize<T>( JSONObject jsonObject, T output )
    { 
      Initialize();

      var keys = jsonObject.keys;

      for ( int i = 0; i < keys.Count; i++)      
      {
        var key = keys[ i ];
        DeserializeMemberField( jsonObject.Get( key ), output, key ); 
      }
    }

    void DeserializeMemberField( JSONData data, object parent, string name )
    {
      if ( "__class__" == name )
      {
        return;
      }
      
      if ( data.isNull )
      {
        SetMemberValue( parent, name, null );
        return;
      }

      var type = parent.GetType();
      var field = type.GetField( name );

      

      if ( field == null )
      {
        RJLog.Log( "Field not present: " + name );
        return;
      }

      var reference = new Reference( parent, name );
      var fieldType = field.FieldType;

      AssignValue( data, reference, fieldType );

    }

    void DeserializeListField( JSONData data, IList parent, int index )
    {
      if ( data.isNull )
      {
        SetListValue( parent, index, null );
        return;
      }

      var type = parent.GetType();
      var reference = new Reference( parent, index );
      var listType = type.GetGenericArguments()[ 0 ];

      AssignValue( data, reference, listType );
    }

    void DeserializeDictionaryField( JSONData data, IDictionary parent, string key, int index )
    { 
      var type = parent.GetType();
      var isIndexed = type.GetGenericArguments()[ 0 ] == typeof( int );

      if ( data.isNull )
      {
        if ( isIndexed )
        {
           SetDictionaryValue( parent, index, null );
        }
        else
        {
           SetDictionaryValue( parent, key, null );
        }
        return;
      }

      
      var reference = isIndexed ? new Reference( parent, index ) : new Reference( parent, key );
      var valueType = type.GetGenericArguments()[ 1 ];

      AssignValue( data, reference, valueType );
    }

    void AssignValue( JSONData data, Reference reference, Type type )
    {
      var customSerializer = GetCustomDeserializer( type );
      
      if ( customSerializer != null )
      {        
        customSerializer.Deserialize( data, reference );
        return;        
      }

      if ( typeof( int ) == type  )
      {
        SetNumber( data, reference, INT );
      }
      if ( typeof( long ) == type  )
      {
        SetNumber( data, reference, LONG );
      }
      else if ( typeof( float ) == type  )
      {
        SetNumber( data, reference, FLOAT );
      }
      else if ( typeof( double ) == type  )
      {
        SetNumber( data, reference, DOUBLE );
      }
      else if ( typeof( bool ) == type )
      {
        if ( ! data.isBoolean )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
          return;
        }
        reference.AssignValue( data.booleanValue );
      }
      else if ( type.IsEnum )
      {
        reference.AssignValue( data.enumValue( type ) );
      }
      else if ( typeof( string ) == type )
      {
        if ( ! data.isString )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
          return;
        }
        reference.AssignValue( data.stringValue );
      }
      else if ( ReflectionHelper.IsList( type ) )
      {
        if ( ! data.isArray )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() +  ">>" + data );
          return;
        }

        reference.AssignValue( CreateList( data, type ) );
      }
      else if ( IsSerializableDictionary( type ) )
      {
        if ( ! data.isObject )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() +  ">>" + data );
          return;
        }

        reference.AssignValue( CreateDictionary( data, type ) );
      }
      else 
      {
        if ( ! data.isObject )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
          return;
        }

        reference.AssignValue( CreateObject( data, type ) );
      }
    }

    IList CreateList( JSONData data, Type fieldType )
    {
      var array = data.AsArray();
      var list = (IList) Activator.CreateInstance( fieldType );
      
      for ( int i = 0 ; i < array.size; i++ )
      {
        var jsonValue = array.Get( i );
        DeserializeListField( jsonValue, list, i );
      }

      return list;
    }

    IDictionary CreateDictionary( JSONData data, Type fieldType )
    {
      var map = data.AsObject();
      var dict = (IDictionary) Activator.CreateInstance( fieldType );
      var keys = map.keys;

      var isIndexed = dict.GetType().GetGenericArguments()[ 0 ] == typeof( int );

      if ( isIndexed )
      {
        for ( int i = 0 ; i < keys.Count; i++ )
        {
          var jsonValue = map.Get( keys[ i ] );
          var index = RegexUtility.ParseInt( keys[ i ] );
          DeserializeDictionaryField( jsonValue, dict, "", index );
        }
      }
      else
      {
        for ( int i = 0 ; i < keys.Count; i++ )
        {
          var jsonValue = map.Get( keys[ i ] );
          DeserializeDictionaryField( jsonValue, dict, keys[ i ], 0 );
        }
      }

      return dict;
    }

    object CreateObject( JSONData data, Type type )
    { 
      var output = Activator.CreateInstance( type );
      var jsonObject = data.AsObject();
      var keys = jsonObject.keys;

      for ( int i = 0; i < keys.Count; i++)      
      {
        var key = keys[ i ];
        DeserializeMemberField( jsonObject.Get( key ), output, key ); 
      }

      return output;
    }

    void SetMemberValue( object instance, string name, object value )
    {
      ReflectionHelper.SetDataMemberValue( instance, name, value );
    }

    void SetListValue( IList list, int index, object value )
    {
      list[ index ] = value;
    }

    void SetDictionaryValue( IDictionary dictionary, string index, object value )
    {
      dictionary[ index ] = value;
    }

    void SetDictionaryValue( IDictionary dictionary, int index, object value )
    {
      dictionary[ index ] = value;
    }

  

    const int INT = 0;
    const int FLOAT = 1;
    const int DOUBLE = 2;
    const int LONG = 3;

    void SetNumber( JSONData data, Reference reference, int numberType )
    {
      if ( ! settings.saveNumbersWithType )
      {
        if ( ! data.isNumber )
        {
          RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
          return;
        } 

        switch ( numberType )
        {
          case INT:{ reference.AssignValue( data.intValue ); return; }
          case FLOAT:{ reference.AssignValue( data.floatValue ); return; } 
          case DOUBLE:{ reference.AssignValue( data.numberValue ); return; } 
          case LONG:{ reference.AssignValue( data.longValue ); return; } 
        }
        
        RJLog.Log( "Unknown number type: " + reference.GetInfo() + ">>" + data );
        return;

        
      }

      if ( ! data.isObject )
      {
        RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
        return;
      }

      var objectValue = data.AsObject();
      
      if ( ! objectValue.Contains( JSONSerializationSettings.NUMBER_VALUE  ) )
      {
        RJLog.Log( "Type not matching: " + reference.GetInfo() + ">>" + data );
        return;
      }

      switch ( numberType )
      {
        case INT:
        { 
          var value = objectValue.Get( JSONSerializationSettings.NUMBER_VALUE ).intValue;
          reference.AssignValue( value ); 
        } 
        break;

        case FLOAT:
        { 
          var value = objectValue.Get( JSONSerializationSettings.NUMBER_VALUE ).floatValue;
          reference.AssignValue( value ); 
        } 
        break;

        case DOUBLE:
        { 
          var value = objectValue.Get( JSONSerializationSettings.NUMBER_VALUE ).numberValue;
          reference.AssignValue( value ); 
        } 
        break;

        case LONG:
        { 
          var value = objectValue.Get( JSONSerializationSettings.NUMBER_VALUE ).longValue;
          reference.AssignValue( value ); 
        } 
        break;
        
      }

    }
    
    void Initialize()
    {
      if ( customSerializers != null ){ return ;}

      customSerializers = new Dictionary<Type,CustomSerializer>();

      settings.customSerializers.ForEach( s => AddSerializer( s ) );   

      JSONSerializationSettings.defaultSerializers.ForEach( s => AddSerializer( s ) );
    }

    void AddSerializer( CustomSerializer serializer )
    {
      var types = serializer.HandlingTypes();
      types.ForEach( type => AddSerializerForType( type, serializer ) );

    }

    void AddSerializerForType( Type type, CustomSerializer serializer )
    {
      if ( customSerializers.ContainsKey( type ) )
      {
        return;
      }      
      
      customSerializers[ type ] = serializer;      
    }


    CustomSerializer GetCustomDeserializer( Type type )
    {

      if ( customSerializers.ContainsKey( type ) )
      {
        return customSerializers[ type ];
      }

      return null;
    }

    static bool IsSerializableDictionary( Type type )
    {
      return JSONSerializationSettings.IsSerializableDictionary( type );
    }

  }
}