﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;


namespace Arugula.Animation
{
    public class AnimatedSpriteSequence : PropertyAttribute
    {
    }

    public class AnimatedSpriteSkin : PropertyAttribute
    {
    }

    [ExecuteInEditMode]
    [RequireComponent(typeof(SpriteRenderer))]
    public class AnimatedSprite : MonoBehaviour
    {

        [System.Serializable]
        public class Skin
        {
            public string name;
            public Sprite defaultSprite;
            public List<Sprite> sprites = new List<Sprite>();

            public Sprite this[int index]
            {
                get
                {
                    return sprites[index];
                }
            }
        }

        [System.Serializable]
        public class Sequence
        {
            public string name;
            public List<int> frames;
            public List<float> durations;
            public List<Event> events;
            public float totalDuration
            {
                get
                {
                    return durations.Sum();
                }
            }

            public Sequence(string name)
            {
                this.name = name;
                frames = new List<int>();
                durations = new List<float>();
                events = new List<Event>();
            }

            public int GetIndexAtTime(float time)
            {
                if (time < durations[0])
                    return 0;

                float t = 0;
                int index = 0;
                while (t < time)
                {
                    t += durations[index];
                    index++;
                    if (index >= frames.Count)
                        return frames.Count - 1;
                }

                return index - 1;
            }

            public float GetTimeAtIndex(int index)
            {
                float t = 0;
                for (int i = 0; i < index; i++)
                {
                    t += durations[i];
                }

                return t;
            }
        }

        [System.Serializable]
        public class Event
        {
            public string name;
            public int intValue;
            public float floatValue;
            public string stringValue;
        }

        public enum UpdateMode
        {
            Update,
            FixedUpdate,
            Manual
        }

        public event UnityAction<Event> OnEvent = delegate { };
        public event UnityAction<bool> OnFinished = delegate { };

        public UpdateMode updateMode;
        public AnimatedSpriteData data;
        [AnimatedSpriteSkin]
        public string skin = "";
        public LoopMode loop = LoopMode.Repeat;
        [AnimatedSpriteSequence]
        public string defaultSequence = "";
        public float timeScale = 1;
        public bool generateShadow;
        public Vector3 shadowOffset = new Vector3(0, 0.01f, 0);
        public bool FlipX
        {
            get
            {
                return flipX;
            }
            set
            {
                if (flipX != value)
                {
                    flipX = value;
                    rend.flipX = value;
                    if (generateShadow)
                        shadowRend.flipX = value;
                }
            }
        }
        public bool FlipY
        {
            get
            {
                return flipY;
            }
            set
            {
                if (flipY != value)
                {
                    flipY = value;
                    rend.flipY = value;
                    if (generateShadow)
                        shadowRend.flipY = value;
                }
            }
        }
        public Color Color
        {
            get
            {
                return rend.color;
            }
            set
            {
                rend.color = value;
            }
        }

        SpriteRenderer rend;
        SpriteRenderer shadowRend;



        Sequence sequence;
        float time;
        float duration;
        int timeSign = 1;
        int index;
        int prevIndex;
        bool flipX;
        bool flipY;
        Skin currentSkin = null;

        private void Awake()
        {

            rend = GetComponent<SpriteRenderer>();

            //if (data != null && data.GetSkin(skin) != null)
            //    rend.sprite = data.GetSkin(skin).defaultSprite;

            if (Application.isPlaying)
            {
                flipX = rend.flipX;
                flipY = rend.flipY;

                currentSkin = data.GetSkin(skin);

                if (generateShadow)
                {
                    GameObject go = new GameObject("Shadow", typeof(SpriteRenderer));
                    go.transform.SetParent(transform, false);
                    go.transform.Rotate(90, 0, 0);
                    go.transform.localPosition = shadowOffset;
                    go.hideFlags = HideFlags.HideInHierarchy;
                    shadowRend = go.GetComponent<SpriteRenderer>();
                    shadowRend.spriteSortPoint = SpriteSortPoint.Center;
                    shadowRend.color = new Color(0, 0, 0, 0.5f);
                }

                Play(defaultSequence, loop);
            }
        }

        private void Update()
        {
            if (Application.isPlaying && updateMode == UpdateMode.Update && sequence != null)
                Update(Time.deltaTime);
#if UNITY_EDITOR
            else if ((rend.sprite == null && data != null && data.GetSkin(skin) != null) || (currentSkin != null && skin != currentSkin.name))
            {
                currentSkin = data.GetSkin(skin);
                if (currentSkin != null)
                    ShowFrame(currentSkin.defaultSprite);
            }
#endif
        }

        private void FixedUpdate()
        {
            if (Application.isPlaying && updateMode == UpdateMode.FixedUpdate && sequence != null)
                Update(Time.deltaTime);
        }

        void ShowFrame(Sprite frame)
        {
            rend.sprite = frame;
            if (generateShadow && shadowRend != null)
            {
                shadowRend.transform.localPosition = shadowOffset;
                shadowRend.sprite = frame;
                shadowRend.enabled = rend.enabled;
            }
            else if (!generateShadow && shadowRend)
                shadowRend.enabled = false;

        }

        public void Play(string name, LoopMode loop = LoopMode.None)
        {
            if (sequence != null && sequence.name == name && loop != LoopMode.None || currentSkin == null)
                return;

            sequence = data[name];
            if (sequence == null)
                return;

            if (skin != currentSkin.name)
                currentSkin = data.GetSkin(skin);

            time = 0;
            timeSign = 1;
            duration = sequence.totalDuration;
            index = 0;
            prevIndex = -1;
            this.loop = loop;
            Update(0);
        }

        public void Stop()
        {
            sequence = null;
            OnFinished(false);
        }

        public void Update(float deltaTime)
        {
            time += deltaTime * timeSign * timeScale;

            switch (loop)
            {
                case LoopMode.None:
                    if (time >= duration)
                    {
                        sequence = null;
                        OnFinished(true);
                        return;
                    }
                    break;
                case LoopMode.Repeat:
                    if (time > duration)
                        time = time - duration;
                    break;
                case LoopMode.PingPong:
                    if (timeSign == 1)
                    {
                        if (time > duration)
                        {
                            timeSign *= -1;
                            time -= time - duration;
                            time -= sequence.durations[index];
                        }
                    }
                    else
                    {
                        if (time < 0)
                        {
                            timeSign *= -1;
                            time += -time;
                            time += sequence.durations[index];
                        }
                    }
                    break;
            }

            index = sequence.GetIndexAtTime(time);

            if (index != prevIndex)
            {
                ShowFrame(currentSkin[sequence.frames[index]]);
                var ev = sequence.events[index];
                if (ev != null)
                {
                    if (ev.name != "")
                        OnEvent(ev);
                }
            }

            prevIndex = index;
        }
    }

}
