using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ECOT.Engine
{
    /// <summary>
    /// 
    /// </summary>
    public class Sprite : IDrawable, IUpdatable, ICloneable
    {
        #region Initialization

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        public Sprite(string filename)
            : this(filename, 0, 0, 0, 0, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="hotSpot"></param>
        public Sprite(string filename, HotSpotType hotSpot)
            : this(filename, 0, 0, 0, 0, hotSpot)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public Sprite(string filename, int x, int y, uint width, uint height)
            : this(filename, x, y, width, height, 1, 0.0f, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="hotSpot"></param>
        public Sprite(string filename, int x, int y, uint width, uint height, HotSpotType hotSpot)
            : this(filename, x, y, width, height, 1, 0.0f, hotSpot)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        public Sprite(string filename, uint width, uint height, uint nbFrames, float frameDuration)
            : this(filename, 0, 0, width, height, nbFrames, frameDuration, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        public Sprite(string filename, int x, int y, uint width, uint height, uint nbFrames, float frameDuration)
            : this(Kernel.Instance.Medias.Get<Texture2D>(filename), x, y, width, height, nbFrames, frameDuration, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        /// <param name="hotSpot"></param>
        public Sprite(string filename, int x, int y, uint width, uint height, uint nbFrames, float frameDuration, HotSpotType hotSpot)
            : this(Kernel.Instance.Medias.Get<Texture2D>(filename), x, y, width, height, nbFrames, frameDuration, hotSpot)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public Sprite(Texture2D texture, int x, int y, uint width, uint height)
            : this(texture, x, y, width, height, 1, 0.0f, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="hotSpot"></param>
        public Sprite(Texture2D texture, int x, int y, uint width, uint height, HotSpotType hotSpot)
            : this(texture, x, y, width, height, 1, 0.0f, hotSpot)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        public Sprite(Texture2D texture, uint width, uint height, uint nbFrames, float frameDuration)
            : this(texture, 0, 0, width, height, nbFrames, frameDuration, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        public Sprite(Texture2D texture, int x, int y, uint width, uint height, uint nbFrames, float frameDuration)
            : this(texture, x, y, width, height, nbFrames, frameDuration, Sprite.DefaultHotSpotType)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="nbFrames"></param>
        /// <param name="frameDuration"></param>
        /// <param name="hotSpot"></param>
        public Sprite(Texture2D texture, int x, int y, uint width, uint height, uint nbFrames, float frameDuration, HotSpotType hotSpot)
        {
            if (width == 0) width = Convert.ToUInt32(texture.Width);
            if (height == 0) height = Convert.ToUInt32(texture.Height);

            for (uint i = 0; i < nbFrames; ++i)
            {
                uint iPerWidth = i * width;
                int X = (int)(x + iPerWidth % texture.Width);
                int Y = (int)(y + iPerWidth / texture.Width * height);
                SpriteFrame frame = new SpriteFrame(texture, X, Y,
                    (int)width, (int)height, frameDuration);

                frames.Add(frame);
            }

            Stop();
            // ! A mettre après stop pour initialiser la frame actuelle
            HotSpot = Utilities.GetHotSpot(hotSpot, Size);
        }

        public Sprite(string[] filenames, float frameDuration)
        {
            foreach (string filename in filenames)
            {
                Texture2D texture = Kernel.Instance.Medias.Get<Texture2D>(filename);
                SpriteFrame frame = new SpriteFrame(texture, 0, 0, texture.Width, texture.Height, frameDuration);
                frames.Add(frame);
            }

            Stop();
            HotSpot = Utilities.GetHotSpot(DefaultHotSpotType, Size);
        }

        public Sprite(string[] filenames, float frameDuration, int x, int y)
        {
                      

            foreach (string filename in filenames)
            {
                Texture2D texture = Kernel.Instance.Medias.Get<Texture2D>(filename);
                SpriteFrame frame = new SpriteFrame(texture, x, y, texture.Width, texture.Height, frameDuration);
                frames.Add(frame);
            }

            Stop();
            HotSpot = Utilities.GetHotSpot(DefaultHotSpotType, Size);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="textures"></param>
        /// <param name="frameDuration"></param>
        public Sprite(Texture2D[] textures, float frameDuration)
        {
            foreach (Texture2D texture in textures)
            {
                SpriteFrame frame = new SpriteFrame(texture, 0, 0, texture.Width, texture.Height, frameDuration);
                frames.Add(frame);
            }

            Stop();
            // ! A mettre après stop pour initialiser la frame actuelle
            HotSpot = Utilities.GetHotSpot(DefaultHotSpotType, Size);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="frames"></param>
        public Sprite(SpriteFrame[] frames)
        {
            this.frames.AddRange(frames);
            Stop();
        }

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public SpriteFrame CurrentFrame
        {
            get { return curFrames; }
            set { curFrames = value; }
        }


        public PlayMode AnimationType
        {
            get { return playMode; }
            set { playMode = value; }
        }


        public List<SpriteFrame> Frames
        {
            get { return this.frames; }
        }


        /// <summary>
        /// 
        /// </summary>
        public bool Playing
        {
            get { return playing; }
            set { playing = value; }
        }


        /// <summary>
        /// 
        /// </summary>
        public int CurrentIndex
        {
            get { return frameIndex; }
            set
            {
                frameIndex = value;
                curFrames = frames[frameIndex];
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public int FrameCount
        {
            get { return frames.Count; }
        }


        /// <summary>
        /// 
        /// </summary>
        public Vector2 HotSpot
        {
            get { return hotSpot; }
            set
            {
                hotSpot = value;
                hotSpotType = HotSpotType.Custom;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public HotSpotType HotSpotType
        {
            get { return hotSpotType; }
            set
            {
                hotSpotType = value;
                hotSpot = Utilities.GetHotSpot(value, Size);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public int Width
        {
            get { return curFrames.Width; }
            set { curFrames.Width = value; }
        }


        /// <summary>
        /// 
        /// </summary>
        public int Height
        {
            get { return curFrames.Height; }
            set { curFrames.Height = value; }
        }


        /// <summary>
        /// 
        /// </summary>
        public Size Size
        {
            get { return new Size(Width, Height); }
            set { Width = value.Width; Height = value.Height; }
        }


        /// <summary>
        /// Définit / Retourne le HotSpotType par défaut pour la création des sprites.
        /// <remarks>
        /// <list type="">
        /// <item>
        /// Si vous affectez un nouveau DefaultHotSpotType, celui-ci ne prendra effet que pour les sprites créés
        /// après l'affectation. Les sprites déjà créés garderont leur HotSpot actuel.
        /// </item>
        /// <item>
        /// Par défaut le HotSpotType est définit à <value>Center</value>.
        /// </item>
        /// </list>
        /// </remarks>
        /// </summary>
        public static HotSpotType DefaultHotSpotType
        {
            get { return defaultHotSpotType; }
            set { defaultHotSpotType = value; }
        }

        public float Depth
        {
            get { return this.depth; }
            set { this.depth = value; }
        }

        #endregion

        #region Animation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="animType"></param>
        public void Play(PlayMode animType)
        {
            playing = true;
            playMode = animType;

            if (animType == PlayMode.Reverse)
            {
                this.frameIndex = this.frames.Count - 1;
                this.frameMod = -1;
                this.curFrames = this.frames[this.frameIndex];
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public void Pause()
        {
            playing = false;
        }


        /// <summary>
        /// 
        /// </summary>
        public void Stop()
        {
            playing = false;
            frameIndex = 0;
            curFrames = frames[0];
            curTime = 0.0f;
        }

        #endregion

        #region IDrawable

        public void Draw(Vector2 position, float layer, float rotation, float scaling, Color color)
        {
            SpriteBatch spriteBatch = Kernel.Instance.SpriteBatch;

            spriteBatch.Draw(this.CurrentFrame.Texture, position,
                new Rectangle(this.curFrames.X, this.curFrames.Y, this.curFrames.Width, this.curFrames.Height),
                color, rotation, hotSpot, scaling, SpriteEffects.None, this.depth);
        }

        public void Draw(SpriteBatch sb, Vector2 position, float layer, float rotation, float scaling, Color color)
        {
            sb.Draw(this.CurrentFrame.Texture, position,
                new Rectangle(this.curFrames.X, this.curFrames.Y, this.curFrames.Width, this.curFrames.Height),
                color, rotation, hotSpot, scaling, SpriteEffects.None, this.depth);
        }

        public void Draw(Matrix transform, Color color)
        {
            // TODO
        }

        #endregion

        public event EventHandler OnEndAnimation;

        #region IUpdatable

        public bool Enabled
        {
            get { return true; }
            set { }
        }

        public void Update(GameTime gameTime)
        {
            // Si l'animation est en pause ou que le sprite ne contient qu'une frame, on ne fait rien.
            if (playing == false || frames.Count == 1) 
                return;
            curTime += gameTime.ElapsedGameTime.Milliseconds / 1000.0f;

            // Le temps de la frame n'est pas encore écoule, on ne fait rien.
            if (curTime < frames[frameIndex].Duration)
                return;

            curTime = 0.0f;
            bool endAnimation = false;

            // Si on arrive en fin d'animation, seul le mode PingPong et Loop continuent.
            if (frameIndex == FrameCount - 1 && playMode != PlayMode.Reverse)
            {
                if (playMode == PlayMode.Normal)
                    Pause();
                else if (playMode == PlayMode.Loop)
                    frameIndex = 0;
                else if (playMode == PlayMode.Pinpong)
                    frameMod = -1;
                // N'arrive jamais ...
                else
                    throw new NotImplementedException();

                endAnimation = true;
            }
            else if (playMode == PlayMode.Reverse && frameIndex == 0)
            {
                Pause();
                endAnimation = true;
            }
            else
                // Si on arrive en début d'animation, on inverse le modulateur pour PingPong
                if (frameIndex == 0 && playMode == PlayMode.Pinpong && frameMod == -1)
                {
                    frameMod = +1;
                    endAnimation = true;
                }

            if (playing)
            {
                frameIndex += frameMod;
                curFrames = frames[frameIndex];
            }

            if (this.OnEndAnimation != null && endAnimation)
                this.OnEndAnimation(this, null);
        }

        #endregion

        #region Fields

        /// <summary>
        /// 
        /// </summary>
        private List<SpriteFrame> frames = new List<SpriteFrame>();

        /// <summary>
        /// 
        /// </summary>
        private SpriteFrame curFrames = null;

        /// <summary>
        /// 
        /// </summary>
        private PlayMode playMode;

        /// <summary>
        /// 
        /// </summary>
        private SpriteEffects spriteFX = SpriteEffects.None;

        /// <summary>
        /// 
        /// </summary>
        private bool playing = false;

        /// <summary>
        /// 
        /// </summary>
        private int frameIndex = 0;

        /// <summary>
        /// 
        /// </summary>
        private float curTime = 0.0f;

        /// <summary>
        /// 
        /// </summary>
        private int frameMod = 1;

        /// <summary>
        /// 
        /// </summary>
        private Vector2 hotSpot;

        /// <summary>
        /// 
        /// </summary>
        private HotSpotType hotSpotType = DefaultHotSpotType;

        /// <summary>
        /// Default HotSpotType for sprites.
        /// </summary>
        private static HotSpotType defaultHotSpotType = HotSpotType.TopLeft;// HotSpotType.Center;

        private float depth = 0.0f;

        #endregion

        #region ICloneable Membres

        public object Clone()
        {
            Sprite clone = new Sprite(this.frames.ToArray());
            clone.playMode = this.playMode;
            clone.spriteFX = this.spriteFX;
            clone.hotSpot = this.hotSpot;
            clone.hotSpotType = this.hotSpotType;
            return clone;
        }

        #endregion
    }
}
