using System;
using System.Linq;
using UnityEngine;
using Newtonsoft.Json;
using System.Collections.Generic;

public class ObjectContainer : MonoBehaviour, IKeyframeSetter
{
    [JsonIgnore] public bool DoNotSave;
    public int ModelId;
    public string GUID = "";
    public bool LoadInProgress = false;

    public List<UIHandle> Handles = new List<UIHandle>();
    public List<Renderer> Renderers = new List<Renderer>();
    public Dictionary<SkinnedMeshRenderer, List<MorphHelper>> Morphs = new Dictionary<SkinnedMeshRenderer, List<MorphHelper>>();
    public List<FrameContent> Frames = new List<FrameContent>();
    public List<GameObject> InstantiatedObjects = new List<GameObject>();
    protected bool _setKeyframe = true;
    protected bool _applicationQuitting = false;

    public void Awake()
    {
        if (GUID == "")
            GUID = System.Guid.NewGuid().ToString();
    }

    public static T Create<T>(string name = "Container") where T : ObjectContainer
    {
        var container = new GameObject(name).AddComponent<T>();
        return container;
    }

    protected virtual void Start()
    {
        if (ModelViewerMain.GetInstance() == null) return;
        if (DoNotSave) return;

        if (Frames.Count == 0)
        {
            SetKeyframe();
        }

        ModelViewerMain.RegisterObject(this);
    }

    public virtual void PasteContainer(ObjectContainerSerializable bones, PoseLoadOptions pasteParams)
    {
        GUID = bones.GUID;
    }

    public virtual Vector3 GetCenter() { return transform.position; }

    public void ToggleVisible(GameObject go, bool value)
    {
        go.SetActive(value);
    }

    public void ToggleRendererVisible(Renderer r, bool value)
    {
        r.enabled = value;
    }

    public virtual ObjectContainerSerializable Serialize()
    {
        return new ObjectContainerSerializable(this);
    }

    public virtual KeyframeData SerializeFrame()
    {
        return new KeyframeData(this);
    }

    public virtual void Deserialize(ObjectContainerSerializable serialized)
    {
        throw new NotImplementedException(GetType().FullName);
    }

    protected virtual void OnDestroy()
    {
        if (_applicationQuitting) return;

        foreach (var rend in Renderers)
        {
            for (int i = rend.sharedMaterials.Length - 1; i >= 0; i--)
            {
                Destroy(rend.sharedMaterials[i]);
            }
        }
        foreach (var obj in InstantiatedObjects)
        {
            Destroy(obj);
        }
        ModelViewerMain.UnregisterObject(this);
        if (gameObject != null)
            Destroy(gameObject);
        ModelViewerMain.OnObjectDeleteEvent.Invoke(this);
        Resources.UnloadUnusedAssets();
        System.GC.Collect();
    }

    #region Timeline
    public virtual DataType GetDataType()
    {
        switch (this)
        {
            case SceneContainer _:
                return DataType.Scene;
            case CameraContainer _:
                return DataType.Camera;
            default:
                return DataType.Character;
        }
    }

    public FrameContent TryGetFrame(int frameNum)
    {
        return Frames.FirstOrDefault(f => f.FrameNum == frameNum);
    }

    public void GetClosestFrames(int frame, out FrameContent previousFrame, out FrameContent nextFrame)
    {
        previousFrame = null;
        nextFrame = null;

        var frames = Frames.OrderBy(f => f.FrameNum).ToArray();

        for (int i = 0; i < Frames.Count; i++)
        {
            if (Frames[i].FrameNum < frame)
            {
                previousFrame = nextFrame = frames[i];
            }
            else if (Frames[i].FrameNum == frame)
            {
                previousFrame = nextFrame = frames[i];
                break;
            }
            else if (Frames[i].FrameNum > frame)
            {
                if (previousFrame == null) previousFrame = frames[i];
                nextFrame = frames[i];
                break;
            }
        }
    }

    /// <summary> Returns true if frame existed or false if it was created </summary>
    public bool GetCurrentFrame(int frame, out FrameContent currentFrame)
    {
        GetClosestFrames(frame, out var previousFrame, out var nextFrame);

        if (previousFrame.FrameNum == frame)
        {
            currentFrame = previousFrame;
            return true;
        }
        else if (nextFrame.FrameNum == frame)
        {
            currentFrame = nextFrame;
            return true;
        }
        else
        {
            currentFrame = new FrameContent(frame);
            Frames.Add(currentFrame);
            Frames = Frames.OrderBy(f => f.FrameNum).ToList();
            return false;
        }
    }

    public virtual void SetKeyframe(int frameNum = -1)
    {
        if (DoNotSave || !_setKeyframe) return;
        //Debug.Log("Setting frame for " + name);
        //if (Frames.Count == 0) SetDefaultFrame();
        if (frameNum == -1) frameNum = TimelineController.Instance.CurrentFrame;

        GetClosestFrames(frameNum, out var previousFrame, out var nextFrame);

        FrameContent currentFrame = null;
        if (previousFrame == null || previousFrame.FrameNum != frameNum)
        {
            currentFrame = new FrameContent(frameNum);
            Frames.Add(currentFrame);
            Frames = Frames.OrderBy(f => f.FrameNum).ToList();
        }
        else if (previousFrame.FrameNum == frameNum) currentFrame = previousFrame;
        else if (nextFrame.FrameNum == frameNum) currentFrame = nextFrame;

        currentFrame.SetObjectData(SerializeFrame());

        TimelineController.UpdateTimeline();
    }

    public void SetDefaultFrame()
    {
        Frames = new List<FrameContent>()
        {
            new FrameContent(0).SetObjectData(SerializeFrame())
        };
    }


    public virtual void DeserializeFrames(ObjectContainerSerializable serialized)
    {
        Frames = serialized.Frames.Select(f => new FrameContent(f)).ToList();
    }

    public virtual void Lerp(KeyframeData frame1, KeyframeData frame2, float amount)
    {
        if (frame1 == frame2)
        {
            PastePose(frame1, new PoseLoadOptions(true));
        }
        else
        {
            PastePose(frame1.Lerp(frame2, amount), new PoseLoadOptions(true));
        }
    }

    public virtual void PastePose(KeyframeData frame, PoseLoadOptions pasteParams)
    {
        if (pasteParams.Root)
        {
            transform.SetTransform(frame.Root);
        }
    }

    public virtual void Deselect()
    {
        throw new NotImplementedException(this.GetType().FullName);
    }

    public virtual void Select()
    {
        throw new NotImplementedException(this.GetType().FullName);
    }

    public virtual void Destroy()
    {
        Destroy(this.gameObject);
    }

    public void EnableKeyframeSet()
    {
        _setKeyframe = true;
    }

    public void DisableKeyframeSet()
    {
        _setKeyframe = false;
    }
    #endregion

    private void OnApplicationQuit()
    {
        _applicationQuitting = true;
    }
}
