using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Object = UnityEngine.Object;

public class TimelineController : MonoBehaviour
{
    [System.Serializable]
    public class CopyBufferData
    {
        public ObjectContainer TrackedObject;
        public FrameContent Content;
    }

    public static TimelineController Instance;

    public TMPro.TMP_InputField FrameCountInput;
    public TMPro.TMP_Text SelectedObjectLabel;
    public TMPro.TMP_InputField
        InputFrameRate,
        InputFrameDelay;

    public int FrameCount;
    public int CurrentFrame;
    public ScrollRect Timeline;
    public ScrollRect ObjectsList;
    public Slider TimelineSlider;
    public CopyBufferData CopyBuffer = null;

    public List<TimelineFrameContainer> Frames;

    public bool Play;
    public float FrameDelayMS = 33;
    public float FrameRate = 30;

    private void Awake()
    {
        Instance = this;
    }

    IEnumerator Start()
    {
        while(ModelViewerMain.GetInstance() == null)
        {
            yield return 0;
        }

        SetFrameRate("30");
        FrameCount = (int)float.Parse(FrameCountInput.text, CultureInfo.InvariantCulture);
        FillTimeline(FrameCount);
        UpdateTimeline();

        StartCoroutine(UpdateLoop());
    }

    IEnumerator UpdateLoop()
    {
        while (true)
        {
            if (Play)
            {
                ChangeCurrentFrame(1, false);
            }
            yield return new WaitForSeconds(FrameDelayMS/1000);
        }
    }

    public void TogglePlay()
    {
        Play = !Play;
    }

    public void ChangeCurrentFrame(int frameChange)
    {
        TimelineController.ChangeCurrentFrame(frameChange, true);
    }

    public static void ChangeCurrentFrame(int frameChange, bool stopPlayback)
    {
        int frame = Instance.CurrentFrame + frameChange;
        if (frame < 0)
            frame = Instance.FrameCount - 1;
        if (frame >= Instance.FrameCount)
            frame = 0;

        if (stopPlayback)
            Instance.Play = false;

        SetCurrentFrame(frame);
    }

    public static void SetCurrentFrame(int frameIndex)
    {
        var main = ModelViewerMain.GetInstance();
        Instance.CurrentFrame = frameIndex;
        //Debug.Log(frameIndex);

        foreach (var container in main.CurrentScene.AllObjects)
        {
            container.GetClosestFrames(frameIndex, out var previousFrame, out var nextFrame);
            var blendAmount = previousFrame == nextFrame? 0 : 1 - (float)(nextFrame.FrameNum - frameIndex) / (nextFrame.FrameNum - previousFrame.FrameNum);

            container.Lerp(previousFrame.ObjectData, nextFrame.ObjectData, blendAmount);
        }
        UpdateTimeline();
    }

    public static void SetCurrentFrame(ObjectContainer container)
    {
        int frameIndex = Instance.CurrentFrame;

        container.GetClosestFrames(frameIndex, out var previousFrame, out var nextFrame);
        var blendAmount = previousFrame == nextFrame ? 0 : 1 - (float)(nextFrame.FrameNum - frameIndex) / (nextFrame.FrameNum - previousFrame.FrameNum);

        container.Lerp(previousFrame.ObjectData, nextFrame.ObjectData, blendAmount);
    }

    public void FillTimeline(int frames)
    {
        if (frames > Frames.Count)
        {
            int diff = frames - Frames.Count;
            for (int i = 0; i < diff; i++)
            {
                var frame = Instantiate(SharedResources.Instance.TimelineFrame, Timeline.content).SetNum(Frames.Count);
                frame.name = Frames.Count.ToString();
                Frames.Add(frame);
            }
        }
        foreach (var frame in Frames)
        {
            frame.gameObject.SetActive(frame.FrameIndex < frames);
        }
    }

    public void OnSliderScroll()
    {
        var sliderPos = TimelineSlider.handleRect.position;
        var newFrame = Frames.Where(f => f.gameObject.activeInHierarchy).OrderBy(frame => Vector3.Distance(sliderPos, frame.GetComponent<RectTransform>().position)).First();
        if (CurrentFrame != newFrame.FrameIndex)
        {
            SetCurrentFrame(newFrame.FrameIndex);
        }
    }
    
    public void SetFrameCount(string text)
    {
        FrameCount = int.Parse(text);
        FillTimeline(FrameCount);
        UpdateTimeline();
    }

    public void SetFrameDelay(string text)
    {
        FrameDelayMS = (int)float.Parse(text, CultureInfo.InvariantCulture);

        if (FrameDelayMS == 0)
        {
            FrameDelayMS = 1;
        }

        FrameRate = 1 / (FrameDelayMS / 1000);
        InputFrameRate.SetTextWithoutNotify(Mathf.Round(FrameRate).ToString());
        InputFrameDelay.SetTextWithoutNotify(Mathf.Round(FrameDelayMS).ToString());
    }

    public void SetFrameRate(string text)
    {
        FrameRate = (int)float.Parse(text, CultureInfo.InvariantCulture);

        if(FrameRate == 0)
        {
            FrameRate = 1;
        }

        FrameDelayMS = (1 / FrameRate) * 1000;
        InputFrameRate.SetTextWithoutNotify(Mathf.Round(FrameRate).ToString());
        InputFrameDelay.SetTextWithoutNotify(Mathf.Round(FrameDelayMS).ToString());
    }

    public static void SwapFrames(int frameNum1, int frameNum2)
    {
        if (frameNum1 < 0 || frameNum1 >= Instance.FrameCount) return;
        if (frameNum2 < 0 || frameNum2 >= Instance.FrameCount) return;

        var selectedObject = GetCurrentObject();
        var frames = selectedObject.Frames;

        var frame1 = selectedObject.TryGetFrame(frameNum1);
        var frame2 = selectedObject.TryGetFrame(frameNum2);

        if (frame1 != null)
        {
            frame1.FrameNum = frameNum2;
        }

        if (frame2 != null)
        {
            frame2.FrameNum = frameNum1;
        }

        selectedObject.Frames = selectedObject.Frames.OrderBy(f => f.FrameNum).ToList();

        SetCurrentFrame(Instance.CurrentFrame);
    }

    public static void UpdateTimeline()
    {
        var container = GetCurrentObject();

        Instance.SelectedObjectLabel.text = container.name;

        var frames = Instance.Frames.ToList();

        for (int i = 0; i < container.Frames.Count; i++)
        {
            var frameNum = container.Frames[i].FrameNum;
            if (frameNum >= frames.Count) return;

            var frame = frames[frameNum];
            frame.UpdateContent(container, container.Frames[i]);
            frames[frameNum] = null;
        }

        foreach(var frame in frames)
        {
            if (frame == null) continue;

            frame.UpdateContent(container, null);
        }
    }

    public static ObjectContainer GetCurrentObject()
    {
        ObjectContainer container = ModelViewerMain.GetInstance().SelectedObject;

        if (container == null)
        {
            container = ModelViewerMain.GetInstance().MainCameraOrbit;
        }

        return container;
    }

    public void OnButtonSet()
    {
        GetCurrentObject().SetKeyframe(CurrentFrame);
    }

    public void OnButtonDelete()
    {
        Frames[CurrentFrame].DeleteFrame();
    }

    public void OnButtonCopy()
    {
        Frames[CurrentFrame].CopyFrame();
    }


    public void OnButtonPaste()
    {
        var data = CopyBuffer;
        if (data == null) return;

        if (GetCurrentObject() == data.TrackedObject)
        {
            if (Input.GetKey(KeyCode.LeftShift)) PasteStep0(data);
            else PasteStep1(data, new PoseLoadOptions(true));
            return;
        }

        UIPopupMessage.Create("You're pasting from a different object. Continue?",
            () =>
            {
                if (Input.GetKey(KeyCode.LeftShift)) PasteStep0(data);
                else PasteStep1(data, new PoseLoadOptions(true));
            },
            () => { }
        );
    }

    private void PasteStep0(CopyBufferData data)
    {
        UIPopupPastePanel.Create(
            (pasteOptions) => { PasteStep1(data, pasteOptions); Debug.Log(pasteOptions.Root); },
            () => { });
    }

    private void PasteStep1(CopyBufferData data, PoseLoadOptions options)
    {
        int frameNum = CurrentFrame;
        var trackedObject = data.TrackedObject;
        if (trackedObject == null)
        {
            Error.Log(Color.red, "Object has been deleted?");
            return;
        }

        if (!trackedObject.GetCurrentFrame(frameNum, out var currentFrame))
        {
            trackedObject.PastePose(data.Content.ObjectData, options);
            currentFrame.SetObjectData(trackedObject.SerializeFrame());
            SetCurrentFrame(trackedObject);
            return;
        }

        UIPopupMessage.Create($"Replace existing data for {trackedObject.name}?",
            () => {
                trackedObject.PastePose(data.Content.ObjectData, options);
                currentFrame.SetObjectData(trackedObject.SerializeFrame());
                SetCurrentFrame(trackedObject);
            },
            () =>{}
         );
    }
}
