using System;
using Unity.VisualScripting.FullSerializer.Internal;

namespace Unity.VisualScripting.FullSerializer
{
    public class fsPrimitiveConverter : fsConverter
    {
        public override bool CanProcess(Type type)
        {
            return
                type.Resolve().IsPrimitive ||
                type == typeof(string) ||
                type == typeof(decimal);
        }

        public override bool RequestCycleSupport(Type storageType)
        {
            return false;
        }

        public override bool RequestInheritanceSupport(Type storageType)
        {
            return false;
        }

        public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
        {
            var instanceType = instance.GetType();

            if (Serializer.Config.Serialize64BitIntegerAsString && (instanceType == typeof(Int64) || instanceType == typeof(UInt64)))
            {
                serialized = new fsData((string)Convert.ChangeType(instance, typeof(string)));
                return fsResult.Success;
            }

            if (UseBool(instanceType))
            {
                serialized = new fsData((bool)instance);
                return fsResult.Success;
            }

            if (UseInt64(instanceType))
            {
                serialized = new fsData((Int64)Convert.ChangeType(instance, typeof(Int64)));
                return fsResult.Success;
            }

            if (UseDouble(instanceType))
            {
                // Casting from float to double introduces floating point jitter,
                // ie, 0.1 becomes 0.100000001490116. Casting to decimal as an
                // intermediate step removes the jitter. Not sure why.
                if (instance.GetType() == typeof(float) &&
                    // Decimal can't store
                    // float.MinValue/float.MaxValue/float.PositiveInfinity/float.NegativeInfinity/float.NaN
                    // - an exception gets thrown in that scenario.
                    (float)instance != float.MinValue &&
                    (float)instance != float.MaxValue &&
                    !float.IsInfinity((float)instance) &&
                    !float.IsNaN((float)instance)
                )
                {
                    serialized = new fsData((double)(decimal)(float)instance);
                    return fsResult.Success;
                }

                serialized = new fsData((double)Convert.ChangeType(instance, typeof(double)));
                return fsResult.Success;
            }

            if (UseString(instanceType))
            {
                serialized = new fsData((string)Convert.ChangeType(instance, typeof(string)));
                return fsResult.Success;
            }

            serialized = null;
            return fsResult.Fail("Unhandled primitive type " + instance.GetType());
        }

        public override fsResult TryDeserialize(fsData storage, ref object instance, Type storageType)
        {
            var result = fsResult.Success;

            if (UseBool(storageType))
            {
                if ((result += CheckType(storage, fsDataType.Boolean)).Succeeded)
                {
                    instance = storage.AsBool;
                }
                return result;
            }

            if (UseDouble(storageType) || UseInt64(storageType))
            {
                if (storage.IsDouble)
                {
                    instance = Convert.ChangeType(storage.AsDouble, storageType);
                }
                else if (storage.IsInt64)
                {
                    instance = Convert.ChangeType(storage.AsInt64, storageType);
                }
                else if (Serializer.Config.Serialize64BitIntegerAsString && storage.IsString &&
                         (storageType == typeof(Int64) || storageType == typeof(UInt64)))
                {
                    instance = Convert.ChangeType(storage.AsString, storageType);
                }
                else
                {
                    return fsResult.Fail(GetType().Name + " expected number but got " + storage.Type + " in " + storage);
                }
                return fsResult.Success;
            }

            if (UseString(storageType))
            {
                if ((result += CheckType(storage, fsDataType.String)).Succeeded)
                {
                    var str = storage.AsString;

                    if (storageType == typeof(char))
                    {
                        if (storageType == typeof(char))
                        {
                            if (str.Length == 1)
                            {
                                instance = str[0];
                            }
                            else
                            {
                                instance = default(char);
                            }
                        }
                    }
                    else
                    {
                        instance = str;
                    }
                }
                return result;
            }

            return fsResult.Fail(GetType().Name + ": Bad data; expected bool, number, string, but got " + storage);
        }

        private static bool UseBool(Type type)
        {
            return type == typeof(bool);
        }

        private static bool UseInt64(Type type)
        {
            return type == typeof(sbyte) || type == typeof(byte) ||
                type == typeof(Int16) || type == typeof(UInt16) ||
                type == typeof(Int32) || type == typeof(UInt32) ||
                type == typeof(Int64) || type == typeof(UInt64);
        }

        private static bool UseDouble(Type type)
        {
            return type == typeof(float) ||
                type == typeof(double) ||
                type == typeof(decimal);
        }

        private static bool UseString(Type type)
        {
            return type == typeof(string) ||
                type == typeof(char);
        }
    }
}
