using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;

namespace ILRuntime.Runtime.Enviorment
{
    public unsafe abstract class ValueTypeBinder
    {
        protected CLRType clrType;
        protected Enviorment.AppDomain domain;

        public CLRType CLRType
        {
            get { return clrType; }
            set
            {
                if (clrType == null)
                {
                    clrType = value;
                    domain = value.AppDomain;
                }
                else
                    throw new NotSupportedException();
            }
        }

        public abstract void CopyValueTypeToStack(object ins, StackObject* ptr, IList<object> mStack);

        public abstract object ToObject(StackObject* esp, IList<object> managedStack);

        public virtual void RegisterCLRRedirection(Enviorment.AppDomain appdomain)
        {

        }

        protected void CopyValueTypeToStack<K>(ref K ins, StackObject* esp, IList<object> mStack)
            where K : struct
        {
            switch (esp->ObjectType)
            {
                case ObjectTypes.ValueTypeObjectReference:
                    {
                        var dst = ILIntepreter.ResolveReference(esp);
                        var vb = ((CLRType)domain.GetTypeByIndex(dst->Value)).ValueTypeBinder as ValueTypeBinder<K>;
                        if (vb != null)
                        {
                            vb.CopyValueTypeToStack(ref ins, dst, mStack);
                        }
                        else
                            throw new NotSupportedException();
                    }
                    break;
                case ObjectTypes.Object:
                    mStack[esp->Value] = ins;
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        protected void AssignFromStack<K>(ref K ins, StackObject* esp, IList<object> mStack)
            where K : struct
        {
            switch (esp->ObjectType)
            {
                case ObjectTypes.Null:
                    throw new NullReferenceException();
                case ObjectTypes.Object:
                    ins = (K)mStack[esp->Value];
                    break;
                case ObjectTypes.ValueTypeObjectReference:
                    {
                        var dst = ILIntepreter.ResolveReference(esp);
                        var vb = ((CLRType)domain.GetTypeByIndex(dst->Value)).ValueTypeBinder as ValueTypeBinder<K>;
                        if (vb != null)
                        {
                            vb.AssignFromStack(ref ins, dst, mStack);
                        }
                        else
                            throw new NotSupportedException();
                    }
                    break;
                default:
                    throw new NotImplementedException();
            }
        }        
    }

    public unsafe abstract class ValueTypeBinder<T> : ValueTypeBinder
    {
        public override unsafe void CopyValueTypeToStack(object ins, StackObject* ptr, IList<object> mStack)
        {
            T obj = (T)ins;
            CopyValueTypeToStack(ref obj, ptr, mStack);
        }

        public abstract void CopyValueTypeToStack(ref T ins, StackObject* ptr, IList<object> mStack);

        public override unsafe object ToObject(StackObject* esp, IList<object> managedStack)
        {
            T obj = default(T);
            AssignFromStack(ref obj, esp, managedStack);
            return obj;
        }

        public abstract void AssignFromStack(ref T ins, StackObject* ptr, IList<object> mStack);

        public void ParseValue(ref T value, ILIntepreter intp, StackObject* ptr_of_this_method, IList<object> mStack, bool shouldFree = true)
        {
            var a = ILIntepreter.GetObjectAndResolveReference(ptr_of_this_method);
            if (a->ObjectType == ObjectTypes.ValueTypeObjectReference)
            {
                var ptr = ILIntepreter.ResolveReference(a);
                AssignFromStack(ref value, ptr, mStack);
                if (shouldFree)
                    intp.FreeStackValueType(ptr_of_this_method);
            }
            else
            {
                value = (T)StackObject.ToObject(a, intp.AppDomain, mStack);
                if (shouldFree)
                    intp.Free(ptr_of_this_method);
            }
        }

        public void WriteBackValue(ILRuntime.Runtime.Enviorment.AppDomain domain, StackObject* ptr_of_this_method, IList<object> mStack, ref T instance_of_this_method)
        {
            ptr_of_this_method = ILIntepreter.GetObjectAndResolveReference(ptr_of_this_method);
            switch (ptr_of_this_method->ObjectType)
            {
                case ObjectTypes.Object:
                    {
                        mStack[ptr_of_this_method->Value] = instance_of_this_method;
                    }
                    break;
                case ObjectTypes.FieldReference:
                    {
                        var obj = mStack[ptr_of_this_method->Value];
                        if (obj is ILTypeInstance)
                        {
                            ((ILTypeInstance)obj)[ptr_of_this_method->ValueLow] = instance_of_this_method;
                        }
                        else
                        {
                            var t = domain.GetType(obj.GetType()) as CLRType;
                            t.SetFieldValue(ptr_of_this_method->ValueLow, ref obj, instance_of_this_method);
                        }
                    }
                    break;
                case ObjectTypes.StaticFieldReference:
                    {
                        var t = domain.GetType(ptr_of_this_method->Value);
                        if (t is ILType)
                        {
                            ((ILType)t).StaticInstance[ptr_of_this_method->ValueLow] = instance_of_this_method;
                        }
                        else
                        {
                            ((CLRType)t).SetStaticFieldValue(ptr_of_this_method->ValueLow, instance_of_this_method);
                        }
                    }
                    break;
                case ObjectTypes.ArrayReference:
                    {
                        var instance_of_arrayReference = mStack[ptr_of_this_method->Value] as T[];
                        instance_of_arrayReference[ptr_of_this_method->ValueLow] = instance_of_this_method;
                    }
                    break;
                case ObjectTypes.ValueTypeObjectReference:
                    {
                        var dst = ILIntepreter.ResolveReference(ptr_of_this_method);
                        CopyValueTypeToStack(ref instance_of_this_method, dst, mStack);
                    }
                    break;
            }
        }

        public void PushValue(ref T value, ILIntepreter intp, StackObject* ptr_of_this_method, IList<object> mStack)
        {
            intp.AllocValueType(ptr_of_this_method, clrType);
            var dst = ILIntepreter.ResolveReference(ptr_of_this_method);
            CopyValueTypeToStack(ref value, dst, mStack);
        }
    }
}
