using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Collections;


namespace Bouncer
{
    class Sprite
    {
        /// <summary>
        /// Прямоугольник, определяющий позицию спрайта на экране. 
        /// </summary>
        public Rectangle SpriteRect;

        /// <summary>
        /// Изображение, содержащее спрайт. 
        /// </summary>
        public Image SpriteImage;

        /// <summary>
        /// Ширина спрайта
        /// </summary>
        public int Width;

        /// <summary>
        /// Высота спрайта
        /// </summary>
        public int Height;

        /// <summary>
        /// Коордитата X спрайта. Берется из свойств прямоугольника
        /// </summary>
        public int X
        {
            get
            {
                return SpriteRect.X;
            }
            set
            {
                SpriteRect.X = value;
            }
        }

        /// <summary>
        /// Координата Y спрайта. Берется из свойств прямоугольника
        /// </summary>
        public int Y
        {
            get
            {
                return SpriteRect.Y;
            }
            set
            {
                SpriteRect.Y = value;
            }
        }

        /// <summary>
        /// Определяет пересечение двух спрайтов
        /// </summary>
        /// <param name="target">другой спрайт</param>
        /// <returns>true, если спрайты пересекаются</returns>
        public bool IntersectsWith(Sprite target)
        {
            return SpriteRect.IntersectsWith(target.SpriteRect);
        }

        /// <summary>
        /// Определяет наличие точки внутри спрайта
        /// </summary>
        /// <param name="x">Координата x точки</param>
        /// <param name="y">Координата y точки</param>
        /// <returns>true, если точка внутри спрайта</returns>
        public bool Contains(int x, int y)
        {
            return SpriteRect.Contains(x, y);
        }

        /// <summary>
        /// Правая сторона спрайта
        /// </summary>
        public int Right
        {
            get
            {
                return SpriteRect.Right;
            }
        }

        /// <summary>
        /// Левая сторона спрайта
        /// </summary>
        public int Left
        {
            get
            {
                return SpriteRect.Left;
            }
        }

        /// <summary>
        /// Нижняя часть спрайта
        /// </summary>
        public int Bottom
        {
            get
            {
                return SpriteRect.Bottom;
            }
        }

        /// <summary>
        /// Создаем экземпляр спрайта
        /// </summary>
        /// <param name="assembly">Сборка из которой загружаются
        /// ресурсы программы</param>
        /// <param name="filename">Имя файла, загружаемого из ресурса. 
        /// Имя файла не содержит название сборки, 
        /// которое присоединяется к имени файла автоматически
        /// </param>
        /// <param name="x">Координата x</param>
        /// <param name="y">Координата y</param>

        public Sprite(
            System.Reflection.Assembly assembly,
            string filename, int x, int y)
        {
            System.Reflection.AssemblyName name = assembly.GetName();
            SpriteImage = new System.Drawing.Bitmap(
                assembly.GetManifestResourceStream(name.Name + "." + filename));
            SpriteRect.Width = SpriteImage.Width;
            SpriteRect.Height = SpriteImage.Height;
            Width = SpriteImage.Width;
            Height = SpriteImage.Height;
            SpriteRect.X = x;
            SpriteRect.Y = y;
        }

        /// <summary>
        /// Создаем экземпляр спрайта из файла
        /// </summary>
        /// <param name="assembly">Сборка из которой загружаются
        /// ресурсы программы</param>
        /// <param name="filename">Имя файла, загружаемого из ресурса. 
        /// Имя файла не содержит название сборки, 
        /// которое присоединяется к имени файла автоматически
        /// </param>

        public Sprite(
            System.Reflection.Assembly assembly,
            string filename)
            : this(assembly, filename, 0, 0)
        {
        }

        /// <summary>
        /// Создаем экземпляр спрайта из другого спрайта
        /// </summary>
        /// <param name="s">source sprite</param>
        /// <param name="x">x position</param>
        /// <param name="y">y position</param>
        public Sprite(Sprite s, int x, int y)
        {
            this.SpriteImage = s.SpriteImage;
            SpriteRect.Width = SpriteImage.Width;
            SpriteRect.Height = SpriteImage.Height;
            Width = SpriteImage.Width;
            Height = SpriteImage.Height;
            SpriteRect.X = x;
            SpriteRect.Y = y;
        }
    }

    /// <summary>
    /// Используем фоновый рисунок в качестве спрайта
    /// </summary>
    class Background : Sprite
    {
        /// <summary>
        /// Создаем фоновый спрайт из файла-источника
        /// </summary>
        /// <param name="assembly">Сборка, из которой
        /// загружается ресурс</param>
        /// <param name="filename">файл-источник</param>
        public Background(
            System.Reflection.Assembly assembly,
            string filename)
            : base(assembly, filename)
        {
        }
    }

    /// <summary>
    /// A sprite which appears in the background, i.e. it does not move
    /// once drawn. It is either visible or invisible. Setting the state to
    /// dissappearing or appearing causes a transition to the appropriate state during
    /// a redraw.
    /// </summary>
    class BackSprite : Sprite
    {
        // possible states for our background sprite
        public enum BackSpriteState { appearing, visible, disappearing, invisible };

        // by default we arrange things so that the sprite is drawn when
        // it arrives
        public BackSpriteState DrawState = BackSpriteState.appearing;

        /// <summary>
        /// Make a bakground sprite from a source file
        /// </summary>
        /// <param name="assembly">The assembly from which the
        /// embedded resource will be loaded</param>
        /// <param name="filename">source file</param>
        public BackSprite(
            System.Reflection.Assembly assembly,
            string filename)
            : base(assembly, filename, 0, 0)
        {
        }

        /// <summary>
        /// Make a bakground sprite from an existing one
        /// </summary>
        /// <param name="s">source sprite</param>
        /// <param name="x">x position of new sprite</param>
        /// <param name="y">y position of new sprite</param>
        public BackSprite(Sprite s, int x, int y)
            : base(s, x, y)
        {
        }

        /// <summary>
        /// Make a background sprite from an existing one
        /// </summary>
        /// <param name="s">source sprite</param>
        public BackSprite(Sprite s)
            : base(s, 0, 0)
        {
        }

        /// <summary>
        /// Make a background sprite from an existing one
        /// </summary>
        /// <param name="s">source sprite</param>
        /// <param name="x">x position of new sprite</param>
        /// <param name="y">y position of new sprite</param>
        /// <param name="drawState">initial draw state</param>
        public BackSprite(Sprite s, int x, int y, BackSpriteState drawState)
            : this(s, x, y)
        {
            DrawState = drawState;
        }

    }

    /// <summary>
    /// Foreground sprite. Erased at the start of every redraw cycle.
    /// </summary>
    class ForeSprite : Sprite
    {
        /// <summary>
        /// If true the sprite is to be redrawn
        /// </summary>
        public bool Visible = true;

        /// <summary>
        /// Create a foreground sprite from a source file
        /// </summary>
        /// <param name="assembly">assembly containing resource</param>
        /// <param name="filename">name of source file</param>
        public ForeSprite(
            System.Reflection.Assembly assembly,
            string filename)
            : base(assembly, filename, 0, 0)
        {
        }

        /// <summary>
        /// Create a foreground sprite from another
        /// </summary>
        /// <param name="s">source sprite</param>
        /// <param name="x">x position of new sprite</param>
        /// <param name="y">y position of new sprite</param>
        public ForeSprite(Sprite s, int x, int y)
            : base(s, x, y)
        {
        }
    }

    /// <summary>
    /// The playfield. Only one of these needs to be created. It contains
    /// a redraw action which allows it to be drawn on the target. It also manages
    /// the display of text in the top section.
    /// </summary>
    class PlayField
    {
        /// <summary>
        /// The background image which sits behind all our objects
        /// </summary>
        Background Back;

        /// <summary>
        /// Background sprites which are drawn behind foreground ones
        /// </summary>
        ArrayList BackgroundSprites = new ArrayList();

        /// <summary>
        /// Foreground sprites which are drawn last of all
        /// </summary>
        ArrayList ForegroundSprites = new ArrayList();

        /// <summary>
        /// Back buffer, has the background image and all the
        /// background sprites drawn on it. We erase foreground
        /// sprites by copying areas from the bakground buffer
        /// onto the foreground
        /// </summary>
        private Bitmap BackBuffer = null;

        /// <summary>
        /// Foreground buffer, used to draw all the images
        /// and the foreground sprites
        /// </summary>
        private Bitmap ForeBuffer = null;

        /// <summary>
        /// width of the displayed window - background may be larger
        /// </summary>
        /// 
        public int WindowWidth;
        /// <summary>
        /// height of the displayed window - background may be larger
        /// </summary>
        /// 
        public int WindowHeight;

        /// <summary>
        /// height of the message area at the top of the playfield
        /// </summary>
        public int Messageheight = 15;

        /// <summary>
        /// white mask for clean redraws
        /// </summary>
        private System.Drawing.Imaging.ImageAttributes transparentWhite;

        // font we are going to use for our messages
        // public so the user can change it
        public Font messageFont = null;
        /// <summary>
        /// message area
        /// public so the user can change it
        /// </summary>
        public Rectangle message;

        /// <summary>
        /// Colour of the messages
        /// </summary>
        public Color MessageColour = Color.Black;
        /// <summary>
        /// Colour of the area behind the messages
        /// </summary>
        public Color MessageBackColour = Color.White;
        /// <summary>
        /// The message brush
        /// </summary>
        private SolidBrush messageBrush = new SolidBrush(Color.Black);
        /// <summary>
        /// requests the redraw of the message text
        /// </summary>
        private bool newMessage = false;

        /// <summary>
        /// Create a new playfield object with the given background
        /// </summary>
        /// <param name="back">the background image</param>
        /// <param name="windowWidth">width of the window</param>
        /// <param name="windowHeight">height of the window</param>
        public PlayField(Background back, int windowWidth, int windowHeight)
        {
            Back = back;
            WindowWidth = windowWidth;
            WindowHeight = windowHeight;
            // make a new back buffer
            BackBuffer = new Bitmap(Back.Width, Back.Height);
            // make a new foreground buffer
            ForeBuffer = new Bitmap(Back.Width, Back.Height);
            // clear the images
            ClearBackground();
            // set up the white mask
            transparentWhite = new System.Drawing.Imaging.ImageAttributes();
            transparentWhite.SetColorKey(Color.White, Color.White);

            // create the default font and message area
            messageFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular);
            message = new Rectangle(0, 0, WindowWidth, Messageheight);
            MessageColour = Color.Red;
            MessageBackColour = Color.White;
        }

        /// <summary>
        /// Create a new playfield object with the given background
        /// </summary>
        /// <param name="back">the background image</param>
        /// <param name="windowWidth">width of the window</param>
        /// <param name="windowHeight">height of the window</param>
        /// <param name="foregroundSprites">array of foreground sprites to add</param>
        /// <param name="backgroundSprites">array of background sprites to add</param>
        public PlayField(
            Background back,
            int windowWidth,
            int windowHeight,
            ForeSprite[] foregroundSprites,
            BackSprite[] backgroundSprites)
            : this(back, windowWidth, windowHeight)
        {
            foreach (ForeSprite s in foregroundSprites)
            {
                this.AddForegroundSprite(s);
            }
            foreach (BackSprite s in backgroundSprites)
            {
                this.AddBackgroundSprite(s);
            }
        }

        /// <summary>
        /// clears everything back to empty
        /// </summary>
        public void ClearBackground()
        {
            // draw the empty background image on it
            using (Graphics g = Graphics.FromImage(BackBuffer))
            {
                g.DrawImage(Back.SpriteImage, 0, 0);
            }
            using (Graphics g = Graphics.FromImage(ForeBuffer))
            {
                g.DrawImage(Back.SpriteImage, 0, 0);
            }
        }

        /// <summary>
        /// Add a sprite to the list of background ones
        /// </summary>
        /// <param name="s">sprite to add</param>
        public void AddBackgroundSprite(BackSprite s)
        {
            BackgroundSprites.Add(s);
        }

        /// <summary>
        /// Add a sprite to the list of foreground ones
        /// </summary>
        /// <param name="s">sprite to add</param>
        public void AddForegroundSprite(ForeSprite s)
        {
            ForegroundSprites.Add(s);
        }

        /// <summary>
        /// Removes all the foreground sprites from the
        /// background
        /// </summary>
        public void ClearForeground()
        {
            // first update background sprites
            using (Graphics back = Graphics.FromImage(BackBuffer))
            using (Graphics fore = Graphics.FromImage(ForeBuffer))
            {
                // first deal with any background sprites 
                foreach (BackSprite s in BackgroundSprites)
                {
                    switch (s.DrawState)
                    {
                        case BackSprite.BackSpriteState.appearing:
                            // need to draw the sprite on the buffers
                            back.DrawImage(
                                s.SpriteImage,			// Image
                                s.SpriteRect,			// destination rectange
                                0,						// srcX
                                0,						// srcY
                                s.Width,				// srcWidth
                                s.Height,				// srcHeight
                                GraphicsUnit.Pixel,		// srcUnit
                                transparentWhite);		// ImageAttributes
                            // copy this image into our foregound
                            fore.DrawImage(
                                BackBuffer,
                                s.SpriteRect,			// destination rectange
                                s.SpriteRect,			// source rectange
                                GraphicsUnit.Pixel);	// srcUnit
                            // mark as visible
                            s.DrawState = BackSprite.BackSpriteState.visible;
                            break;
                        case BackSprite.BackSpriteState.disappearing:
                            // need to erase the sprite by drawing the background
                            // image over it
                            back.DrawImage(
                                Back.SpriteImage,
                                s.SpriteRect,
                                s.SpriteRect,
                                GraphicsUnit.Pixel);
                            fore.DrawImage(
                                Back.SpriteImage,
                                s.SpriteRect,
                                s.SpriteRect,
                                GraphicsUnit.Pixel);
                            s.DrawState = BackSprite.BackSpriteState.invisible;
                            break;
                        case BackSprite.BackSpriteState.invisible:
                            break;
                        case BackSprite.BackSpriteState.visible:
                            break;
                    }
                }

                // now erase all the foreground sprites
                foreach (Sprite s in ForegroundSprites)
                {
                    fore.DrawImage(
                        BackBuffer,
                        s.SpriteRect,
                        s.SpriteRect,
                        GraphicsUnit.Pixel);
                }

                // now clear the text area if we need to
                if (newMessage)
                {
                    messageBrush.Color = MessageBackColour;
                    fore.FillRectangle(messageBrush, message);
                    back.FillRectangle(messageBrush, message);
                    messageBrush.Color = MessageColour;
                    back.DrawString(MessageValue, messageFont, messageBrush, message);
                    // copying an image is faster than writing
                    fore.DrawImage(
                        BackBuffer,
                        message,
                        message,
                        GraphicsUnit.Pixel);
                    newMessage = false;
                }
            }
        }

        /// <summary>
        /// Draws all the foreground sprites at new positions
        /// </summary>
        public void DrawForeground()
        {
            using (Graphics g = Graphics.FromImage(ForeBuffer))
            {
                foreach (ForeSprite s in ForegroundSprites)
                {
                    if (s.Visible)
                    {
                        g.DrawImage(
                            s.SpriteImage,			// Image
                            s.SpriteRect,			// destination rectange
                            0,						// srcX
                            0,						// srcY
                            s.Width,				// srcWidth
                            s.Height,				// srcHeight
                            GraphicsUnit.Pixel,		// srcUnit
                            transparentWhite);		// ImageAttributes
                    }
                }
            }
        }

        /// <summary>
        /// Message to be displayed in the message area
        /// </summary>
        private string MessageValue;

        /// <summary>
        /// Message property. Writing to it sets a flag which forces a 
        /// message text update.
        /// </summary>
        public string Message
        {
            set
            {
                if (MessageValue != value)
                {
                    // store the new message
                    MessageValue = value;
                    // request an update
                    newMessage = true;
                }
            }
        }

        // draw position of the playfield
        int X = 0;
        int Y = 0;

        /// <summary>
        /// Paint method, puts the back buffer on the destination
        /// </summary>
        /// <param name="sender">source of message</param>
        /// <param name="e">paint arguments</param>
        public void doPaint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            e.Graphics.DrawImage(ForeBuffer, X, Y);
        }
    }
}
