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

using System.Text;



using System;
using System.Linq;

using System.Numerics;

namespace Rokojori
{ 
  public class JSONSerializer
  {
    JSONSerializationSettings settings = new JSONSerializationSettings();
    HashSet<object> processedObjects = new HashSet<object>();
    HashSet<Type> alwaysReprocessTypes = new HashSet<Type>()
    {
      typeof( DateTime ), typeof( BigInteger )
    };

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

    Dictionary<Type,CustomSerializer> customSerializers = null;     

    public string Serialize( object value )
    {      

      Initialize();

      if ( value == null )
      {
        return JSON.Stringify( new JSONValue() );
      }
      else if ( value is int )
      {
        return JSON.Stringify( CreateNumber( (int) value ) );
      } 
      else if ( value is double )
      {
        return JSON.Stringify( CreateNumber( (double) value ) );
      }
      else if ( value is float )
      {
        return JSON.Stringify( CreateNumber( (float) value ) );
      }
      else if ( value is string )
      {
        return JSON.Stringify( new JSONValue( (string) value ) );
      }
      else if ( value is bool )
      {
        return JSON.Stringify( new JSONValue( (bool) value ) );
      }
      else if ( ReflectionHelper.IsList( value ) )
      {
        return JSON.Stringify( CreateArray( value as IList ) );
      }
      else if ( IsSerializableDictionary( value ) )
      {
        return JSON.Stringify( CreateMapLikeObject( value as IDictionary ) );
      }
      else 
      {
        var serializer = GetCustomSerializer( value );

        if ( serializer != null )
        {
          return JSON.Stringify( serializer.Serialize( value ) ); 
        }
        else
        {
          return JSON.Stringify( CreateObject( value ) ); 
        }
        
      }
    }
    
    void Initialize()
    {
      processedObjects.Clear();

      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 GetCustomSerializer( object value )
    {
      if ( value == null ){ return null; }

      var type = value.GetType();

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

      return null;
    } 

    static bool IsSerializableDictionary( object value )
    {
      return JSONSerializationSettings.IsSerializableDictionary( value );
    }

    

    JSONData CreateNumber( int value )
    {
      if ( settings.saveNumbersWithType )
      {
        var jsonObject = new JSONObject();
        jsonObject.Set( JSONSerializationSettings.NUMBER_TYPE, JSONSerializationSettings.INT_NUMBER_TYPE );
        jsonObject.Set( JSONSerializationSettings.NUMBER_VALUE, value );

        return jsonObject;
      }

      return new JSONValue( value ); 
    }

    JSONData CreateNumber( float value )
    {
      if ( settings.saveNumbersWithType )
      {
        var jsonObject = new JSONObject();
        jsonObject.Set( JSONSerializationSettings.NUMBER_TYPE, JSONSerializationSettings.FLOAT_NUMBER_TYPE );
        jsonObject.Set( JSONSerializationSettings.NUMBER_VALUE, value );

        return jsonObject;
      }

      return new JSONValue( value ); 
    }

    JSONData CreateNumber( double value )
    {
      if ( settings.saveNumbersWithType )
      {
        var jsonObject = new JSONObject();
        jsonObject.Set( JSONSerializationSettings.NUMBER_TYPE, JSONSerializationSettings.DOUBLE_NUMBER_TYPE );
        jsonObject.Set( JSONSerializationSettings.NUMBER_VALUE, value );

        return jsonObject;
      }

      return new JSONValue( value ); 
    }

    bool IsProcessableObject( object value )
    {
      if (System.Attribute.IsDefined( value.GetType(), typeof(JSONAlwaysProcessable) ) )
      {
        return true;
      }

      if ( alwaysReprocessTypes.Contains( value.GetType() ) )
      {
        return true;
      }

      if ( processedObjects.Contains( value ) )
      {
        RJLog.Error( "Cycle detected: " + value );
        var interfaces = typeof( object ).GetInterfaces();
        
        for ( int i = 0; i < interfaces.Length; i++ )
        {
          RJLog.Error( "Interfaces[" + i + "]" + interfaces[ i ].Name );
        }

        

        if ( settings.throwErrorOnCycles )
        {
          throw new System.Exception( "Cycle detected: " + value );
        }        

        return false;
      }

      processedObjects.Add( value );

      return true;
    } 

    JSONData CreateArray( IList value )
    {
      if ( ! IsProcessableObject( value ) )
      {
        return new JSONValue();  
      }

      var jsonArray = new JSONArray();

      //RJLog.Log(value );

      for ( int i = 0; i < value.Count; i++ )
      { 
        AssignArrayMember( jsonArray, i, value[ i ] );
      }

      return jsonArray;
    }

    JSONData CreateMapLikeObject( IDictionary value )
    {
       if ( ! IsProcessableObject( value ) )
      {
        return new JSONValue();  
      }

      var jsonObject = new JSONObject();

      foreach ( var key in value.Keys )
      { 
        AssignObjectMember( jsonObject, key + "", value[ key ] );
      }

      return jsonObject;
    }

    JSONData CreateObject( object obj )
    {
       if ( ! IsProcessableObject( obj ) )
      {
        return new JSONValue();  
      }      

      var type = obj.GetType();
      var fields = type.GetFields();
      var jsonObject = new JSONObject();

      foreach ( var f in fields ) 
      {
        if ( f.IsStatic )
        {
          continue;
        }
        
        var name = f.Name;
        var value = f.GetValue( obj );

        if ( value == null )
        {
          jsonObject.Set( name, new JSONValue() );
        }
        else if ( value is int )
        {
          jsonObject.Set( name, CreateNumber( (int) value ) ); 
        } 
        else if ( value is double )
        { 
          jsonObject.Set( name, CreateNumber( (double) value ) );
        }
        else if ( value is float )
        {
          jsonObject.Set( name, CreateNumber( (float) value ) );
        }
        else if ( value is string )
        {
          jsonObject.Set( name, (string) value );
        }
        else if ( value is bool )
        {
          jsonObject.Set( name, (bool) value );
        }
        else if ( value is Enum )
        {
          jsonObject.Set( name, (int) value );
        }
        else if ( ReflectionHelper.IsList( value ) )
        {
          jsonObject.Set( name, CreateArray( value as IList ) );
        }
        else if ( IsSerializableDictionary( value ) )
        {
          jsonObject.Set( name, CreateMapLikeObject( value as IDictionary ) );
        }
        else 
        {
          var exporter = GetCustomSerializer( value );

          if ( exporter != null )
          {
            jsonObject.Set( name, exporter.Serialize( value ) ); 
          }
          else
          {
            jsonObject.Set( name, CreateObject( value ) ); 
          }
          
        }
      } 

      return jsonObject;
    }

    void AssignArrayMember( JSONArray jsonArray, int index, object value )
    {
      if ( value == null )
      {
        jsonArray.Set( index, new JSONValue() );
      }
      else if ( value is int )
      {
        jsonArray.Set( index, CreateNumber( (int) value ) );
      } 
      else if ( value is double )
      {
        jsonArray.Set( index, CreateNumber( (double) value ) );
      }
      else if ( value is float )
      {
        jsonArray.Set( index, CreateNumber( (float) value ) );
      }
      else if ( value is string )
      {
        jsonArray.Set( index, (string) value );
      }
      else if ( value is bool )
      {
        jsonArray.Set( index, (bool) value );
      }
      else if ( ReflectionHelper.IsList( value ) )
      {
        jsonArray.Set( index, CreateArray( value as IList) );
      }
      else if ( IsSerializableDictionary( value ) )
      {
        jsonArray.Set( index, CreateMapLikeObject( value as IDictionary ) );
      }
      else 
      {
        var exporter = GetCustomSerializer( value );

        if ( exporter != null )
        {
          jsonArray.Set( index, exporter.Serialize( value ) ); 
        }
        else
        {
          jsonArray.Set( index, CreateObject( value ) ); 
        }
        
      }
    }

    void AssignObjectMember( JSONObject jsonObject, string name, object value )
    {
      if ( value == null )
      {
        jsonObject.Set( name, new JSONValue() );
      }
      else if ( value is int )
      {
        jsonObject.Set( name, CreateNumber( (int) value ) );
      } 
      else if ( value is double )
      {
        jsonObject.Set( name, CreateNumber( (double) value ) );
      }
      else if ( value is float )
      {
        jsonObject.Set( name, CreateNumber( (float) value ) );
      }
      else if ( value is string )
      {
        jsonObject.Set( name, (string) value );
      }
      else if ( value is bool )
      {
        jsonObject.Set( name, (bool) value );
      }
      else if ( ReflectionHelper.IsList( value ) )
      {
        jsonObject.Set( name, CreateArray( value as IList ) );
      }
      else if ( IsSerializableDictionary( value ) )
      {
        jsonObject.Set( name, CreateMapLikeObject( value as IDictionary ) );
      }
      else
      {
        var exporter = GetCustomSerializer( value );

        if ( exporter != null )
        {
          jsonObject.Set( name, exporter.Serialize( value ) ); 
        }
        else
        {
          jsonObject.Set( name, CreateObject( value ) ); 
        }
      }
           
    }
   

  }
}