using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MonoGame.FourEngine.Resources
{
    /// <summary>
    /// Framed texture resource
    /// </summary>
    public class Sprite : IResource
    {
        /// <summary>
        /// Permanent resources are not deleted on ResourceManager.Clear
        /// </summary>
        public bool Permanent { get; protected set; }

        /// <summary>
        /// Get one Sprite Frame
        /// </summary>
        /// <param name="frame">Position in Sprite</param>
        /// <returns>One Frame from a Sprite</returns>
        public Frame this[Point frame] => new Frame(Texture, Size, Spacing, Margin, frame);

        /// <summary>
        /// Texture data
        /// </summary>
        public Texture2D Texture { get; }

        /// <summary>
        /// Dimensions of one Frame
        /// </summary>
        public Point Size { get; }

        /// <summary>
        /// Space between frames that we should not draw
        /// </summary>
        public Point Spacing { get; }

        /// <summary>
        /// Margin from top left point
        /// </summary>
        public Point Margin { get; }
        
        /// <summary>
        /// Number of available frames
        /// </summary>
        public Point FrameCount => new Point(
            Texture.Width / Size.X,
            Texture.Height / Size.Y);

        /// <summary>
        /// Create new Sprite instance
        /// </summary>
        /// <param name="def">Sprite parameters</param>
        public Sprite(SpriteDefinition def)
        {
            Texture = ReadTexture2D(def.FileName);
            Size = def.Size;
            Spacing = def.Spacing;
            Margin = def.Margin;
        }

        private Texture2D ReadTexture2D(string fileName)
        {
            using (var stream = ServiceLocator.ResourceLoader.GetStream(fileName))
                return Texture2D.FromStream(FourEngineGame.Current.GraphicsDevice, stream);
        }

        public void Dispose()
            => Texture?.Dispose();

        /// <summary>
        /// One Frame from a Sprite
        /// </summary>
        public class Frame
        {
            /// <summary>
            /// Texture data
            /// </summary>
            public Texture2D Texture { get; }

            /// <summary>
            /// Dimensions of one Frame
            /// </summary>
            public Point Size { get; }

            /// <summary>
            /// Space between frames that we should not draw
            /// </summary>
            public Point Spacing { get; }

            /// <summary>
            /// Margin from top left point
            /// </summary>
            public Point Margin { get; }

            /// <summary>
            /// Position in Sprite
            /// </summary>
            public Point Current { get; }

            /// <summary>
            /// Create new Frame instance
            /// </summary>
            /// <param name="texture">Texture data</param>
            /// <param name="size">Dimensions of one Frame</param>
            /// <param name="spacing">Space between frames that we should not draw</param>
            /// <param name="frame">Position in Sprite</param>
            public Frame(Texture2D texture, Point size, Point spacing, Point margin, Point frame)
            {
                Texture = texture;
                Size = size;
                Spacing = spacing;
                Margin = margin;
                Current = frame;
            }

            /// <summary>
            /// Draw this Frame
            /// </summary>
            /// <param name="batch">SpriteBatch to draw this Frame</param>
            /// <param name="position">Position where to draw this Frame</param>
            public void Draw(SpriteBatch batch, Point position, Color color)
            {
                var drawArgs = CreateDrawArgs(position, color);
                batch.Draw(
                    Texture,
                    drawArgs.DestinationRectangle,
                    drawArgs.SourceRectangle,
                    drawArgs.Color,
                    drawArgs.Rotation,
                    drawArgs.Origin,
                    drawArgs.SpriteEffects,
                    0);
            }

            public SpriteDrawEventArgs CreateDrawArgs(Point position, Color color)
                => new SpriteDrawEventArgs(this)
                {
                    DestinationRectangle = new Rectangle(
                        position,
                        Size),
                    SourceRectangle = new Rectangle(
                        Current * (Size + Spacing) + Margin,
                        Size),
                    Color = color,
                    SpriteEffects = SpriteEffects.None
                };
        }
    }

    /// <summary>
    /// Defines Sprite parameters
    /// </summary>
    public class SpriteDefinition : IResourceDefinition
    {
        /// <summary>
        /// Texture file name
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Dimensions of one Frame
        /// </summary>
        public Point Size { get; set; }

        /// <summary>
        /// Space between frames that we should not draw
        /// </summary>
        public Point Spacing { get; set; }

        /// <summary>
        /// Margin from top left point
        /// </summary>
        public Point Margin { get; set; }
    }
}