using ChocolArm64.Memory;
using ChocolArm64.State;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace ChocolArm64
{
    class TranslatedSub
    {
        private delegate long Aa64Subroutine(CpuThreadState register, MemoryManager memory);

        private const int MinCallCountForReJit = 250;

        private Aa64Subroutine _execDelegate;

        public static int StateArgIdx  { get; private set; }
        public static int MemoryArgIdx { get; private set; }

        public static Type[] FixedArgTypes { get; private set; }

        public DynamicMethod Method { get; private set; }

        public ReadOnlyCollection<Register> Params { get; private set; }

        private HashSet<long> _callers;

        private TranslatedSubType _type;

        private int _callCount;

        private bool _needsReJit;

        public TranslatedSub(DynamicMethod method, List<Register> Params)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            if (Params == null)
            {
                throw new ArgumentNullException(nameof(Params));
            }

            Method = method;
            this.Params = Params.AsReadOnly();

            _callers = new HashSet<long>();

            PrepareDelegate();
        }

        static TranslatedSub()
        {
            MethodInfo mthdInfo = typeof(Aa64Subroutine).GetMethod("Invoke");

            ParameterInfo[] Params = mthdInfo.GetParameters();

            FixedArgTypes = new Type[Params.Length];

            for (int index = 0; index < Params.Length; index++)
            {
                Type paramType = Params[index].ParameterType;

                FixedArgTypes[index] = paramType;

                if (paramType == typeof(CpuThreadState))
                {
                    StateArgIdx = index;
                }
                else if (paramType == typeof(MemoryManager))
                {
                    MemoryArgIdx = index;
                }
            }
        }

        private void PrepareDelegate()
        {
            string name = $"{Method.Name}_Dispatch";

            DynamicMethod mthd = new DynamicMethod(name, typeof(long), FixedArgTypes);

            ILGenerator generator = mthd.GetILGenerator();

            generator.EmitLdargSeq(FixedArgTypes.Length);

            foreach (Register reg in Params)
            {
                generator.EmitLdarg(StateArgIdx);

                generator.Emit(OpCodes.Ldfld, reg.GetField());
            }

            generator.Emit(OpCodes.Call, Method);
            generator.Emit(OpCodes.Ret);

            _execDelegate = (Aa64Subroutine)mthd.CreateDelegate(typeof(Aa64Subroutine));
        }

        public bool ShouldReJit()
        {
            if (_needsReJit && _callCount < MinCallCountForReJit)
            {
                _callCount++;

                return false;
            }

            return _needsReJit;
        }

        public long Execute(CpuThreadState threadState, MemoryManager memory)
        {
            return _execDelegate(threadState, memory);
        }

        public void AddCaller(long position)
        {
            lock (_callers)
            {
                _callers.Add(position);
            }
        }

        public long[] GetCallerPositions()
        {
            lock (_callers)
            {
                return _callers.ToArray();
            }
        }

        public void SetType(TranslatedSubType type)
        {
            _type = type;

            if (type == TranslatedSubType.SubTier0)
            {
                _needsReJit = true;
            }
        }

        public void MarkForReJit() => _needsReJit = true;
    }
}