#nullable enable
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Swan.Reflection;
namespace Swan.Formatters {
  /// 
  /// A very simple, light-weight JSON library written by Mario
  /// to teach Geo how things are done
  /// 
  /// This is an useful helper for small tasks but it doesn't represent a full-featured
  /// serializer such as the beloved Json.NET.
  /// 
  public static partial class Json {
    private class Converter {
      private static readonly ConcurrentDictionary MemberInfoNameCache = new ConcurrentDictionary();
      private static readonly ConcurrentDictionary ListAddMethodCache = new ConcurrentDictionary();
      private readonly Object? _target;
      private readonly Type _targetType;
      private readonly Boolean _includeNonPublic;
      private readonly JsonSerializerCase _jsonSerializerCase;
      private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) {
        this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
        this._includeNonPublic = includeNonPublic;
        this._jsonSerializerCase = jsonSerializerCase;
        if(source == null) {
          return;
        }
        Type sourceType = source.GetType();
        if(this._targetType == null || this._targetType == typeof(Object)) {
          this._targetType = sourceType;
        }
        if(sourceType == this._targetType) {
          this._target = source;
          return;
        }
        if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
          return;
        }
        this.ResolveObject(source, ref this._target);
      }
      internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean includeNonPublic = false) {
        Object? nullRef = null;
        return new Converter(source, targetType ?? typeof(Object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
      }
      private static Object? FromJsonResult(Object source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
      private static Type? GetAddMethodParameterType(Type targetType) => ListAddMethodCache.GetOrAdd(targetType, x => x.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 1)?.GetParameters()[0].ParameterType!);
      private static void GetByteArray(String sourceString, ref Object? target) {
        try {
          target = Convert.FromBase64String(sourceString);
        } // Try conversion from Base 64
        catch(FormatException) {
          target = Encoding.UTF8.GetBytes(sourceString);
        } // Get the string bytes in UTF8
      }
      private Object GetSourcePropertyValue(IDictionary sourceProperties, MemberInfo targetProperty) {
        String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase));
        return sourceProperties.GetValueOrDefault(targetPropertyName);
      }
      private Boolean TrySetInstance(Object? targetInstance, Object source, ref Object? target) {
        if(targetInstance == null) {
          // Try to create a default instance
          try {
            source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
          } catch {
            return false;
          }
        } else {
          target = targetInstance;
        }
        return true;
      }
      private Object? GetResult() => this._target ?? this._targetType.GetDefault();
      private void ResolveObject(Object source, ref Object? target) {
        switch(source) {
          // Case 0: Special Cases Handling (Source and Target are of specific convertible types)
          // Case 0.1: Source is string, Target is byte[]
          case String sourceString when this._targetType == typeof(Byte[]):
            GetByteArray(sourceString, ref target);
            break;
          // Case 1.1: Source is Dictionary, Target is IDictionary
          case Dictionary sourceProperties when target is IDictionary targetDictionary:
            this.PopulateDictionary(sourceProperties, targetDictionary);
            break;
          // Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
          case Dictionary sourceProperties:
            this.PopulateObject(sourceProperties);
            break;
          // Case 2.1: Source is List, Target is Array
          case List