using ET;

namespace KYFramework;

[ObjectSystem]
public class TimerComponentAwakeSystem : AwakeSystem<TimerComponent>
{
    public override void Awake(TimerComponent self)
    {
        TimerComponent.Instance = self;
    }
}

[ObjectSystem]
public class TimerComponentUpdateSystem : UpdateSystem<TimerComponent>
{
    public override void Update(TimerComponent self)
    {
        if (self.TimeId.Count == 0)
        {
            return;
        }

        long timeNow = self.GetNow();

        if (timeNow < self.minTime)
        {
            return;
        }

        foreach (KeyValuePair<long, List<long>> kv in self.TimeId)
        {
            long k = kv.Key;
            if (k > timeNow)
            {
                self.minTime = k;
                break;
            }

            self.timeOutTime.Enqueue(k);
        }

        while (self.timeOutTime.Count > 0)
        {
            long time = self.timeOutTime.Dequeue();
            var list = self.TimeId[time];
            for (int i = 0; i < list.Count; ++i)
            {
                long timerId = list[i];
                self.timeOutTimerIds.Enqueue(timerId);
            }

            self.TimeId.Remove(time);
        }

        while (self.timeOutTimerIds.Count > 0)
        {
            long timerId = self.timeOutTimerIds.Dequeue();

            if (!self.timerActions.Remove(timerId, out TimerAction timerAction))
            {
                continue;
            }

            self.Run(timerAction);
        }
    }
}

public static class TimerComponentSystem
{
    public static long GetNow(this TimerComponent self)
    {
        return TimeHelper.ClientFrameTime();
    }

    public static void Run(this TimerComponent self, TimerAction timerAction)
    {
        switch (timerAction.TimerClass)
        {
            case TimerClass.OnceTimer:
            {
                Game.EventSystem.Invoke(timerAction.Type, new TimerCallback() { Args = timerAction.Object });
                timerAction.Recycle();
                break;
            }
            case TimerClass.OnceWaitTimer:
            {
                ETTask tcs = timerAction.Object as ETTask;
                tcs.SetResult();
                timerAction.Recycle();
                break;
            }
            case TimerClass.RepeatedTimer:
            {
                long timeNow = self.GetNow();
                timerAction.StartTime = timeNow;
                self.AddTimer(timerAction);
                Game.EventSystem.Invoke(timerAction.Type, new TimerCallback() { Args = timerAction.Object });
                break;
            }
        }
    }

    private static void AddTimer(this TimerComponent self, TimerAction timer)
    {
        long tillTime = timer.StartTime + timer.Time;
        self.TimeId.Add(tillTime, timer.Id);
        self.timerActions.Add(timer.Id, timer);
        if (tillTime < self.minTime)
        {
            self.minTime = tillTime;
        }
    }

    public static bool Remove(this TimerComponent self, ref long id)
    {
        long i = id;
        id = 0;
        return self.Remove(i);
    }

    private static bool Remove(this TimerComponent self, long id)
    {
        if (id == 0)
        {
            return false;
        }

        if (!self.timerActions.Remove(id, out TimerAction timerAction))
        {
            return false;
        }

        timerAction.Recycle();
        return true;
    }

    public static async ETTask WaitTillAsync(this TimerComponent self, long tillTime,
        ETCancellationToken cancellationToken = null)
    {
        long timeNow = self.GetNow();
        if (timeNow >= tillTime)
        {
            return;
        }

        ETTask tcs = ETTask.Create(true);
        TimerAction timer =
            TimerAction.Create(self.GetId(), TimerClass.OnceWaitTimer, timeNow, tillTime - timeNow, 0, tcs);
        self.AddTimer(timer);
        long timerId = timer.Id;

        void CancelAction()
        {
            if (self.Remove(timerId))
            {
                tcs.SetResult();
            }
        }

        try
        {
            cancellationToken?.Add(CancelAction);
            await tcs;
        }
        finally
        {
            cancellationToken?.Remove(CancelAction);
        }
    }

    public static async ETTask WaitFrameAsync(this TimerComponent self, ETCancellationToken cancellationToken = null)
    {
        await self.WaitAsync(1, cancellationToken);
    }

    public static async ETTask WaitAsync(this TimerComponent self, long time,
        ETCancellationToken cancellationToken = null)
    {
        if (time == 0)
        {
            return;
        }

        long timeNow = self.GetNow();

        ETTask tcs = ETTask.Create(true);
        TimerAction timer = TimerAction.Create(self.GetId(), TimerClass.OnceWaitTimer, timeNow, time, 0, tcs);
        self.AddTimer(timer);
        long timerId = timer.Id;

        void CancelAction()
        {
            if (self.Remove(timerId))
            {
                tcs.SetResult();
            }
        }

        try
        {
            cancellationToken?.Add(CancelAction);
            await tcs;
        }
        finally
        {
            cancellationToken?.Remove(CancelAction);
        }
    }

    // 用这个优点是可以热更,缺点是回调式的写法,逻辑不连贯。WaitTillAsync不能热更,优点是逻辑连贯。
    // wait时间短并且逻辑需要连贯的建议WaitTillAsync
    // wait时间长不需要逻辑连贯的建议用NewOnceTimer
    public static long NewOnceTimer(this TimerComponent self, long tillTime, int type, object args)
    {
        long timeNow = self.GetNow();
        if (tillTime < timeNow)
        {
            Log.Error($"new once time too small: {tillTime}");
        }

        TimerAction timer =
            TimerAction.Create(self.GetId(), TimerClass.OnceTimer, timeNow, tillTime - timeNow, type, args);
        self.AddTimer(timer);
        return timer.Id;
    }

    public static long NewFrameTimer(this TimerComponent self, int type, object args)
    {
        return self.NewRepeatedTimerInner(100, type, args);
    }

    /// <summary>
    /// 创建一个RepeatedTimer
    /// </summary>
    private static long NewRepeatedTimerInner(this TimerComponent self, long time, int type, object args)
    {
        if (time < 100)
        {
            throw new Exception($"repeated timer < 100, timerType: time: {time}");
        }


        long timeNow = self.GetNow();
        TimerAction timer = TimerAction.Create(self.GetId(), TimerClass.RepeatedTimer, timeNow, time, type, args);

        // 每帧执行的不用加到timerId中,防止遍历
        self.AddTimer(timer);
        return timer.Id;
    }

    public static long NewRepeatedTimer(this TimerComponent self, long time, int type, object args)
    {
        if (time < 100)
        {
            Log.Error($"time too small: {time}");
            return 0;
        }

        return self.NewRepeatedTimerInner(time, type, args);
    }

    private static long GetId(this TimerComponent self)
    {
        return ++self.idGenerator;
    }
}