using System;
using System.Collections.Generic;
using Microsoft.DirectX;
using System.Windows.Forms;
using System.Drawing;

namespace Canon
{
    class CanonInterface
    {
        /* Notion de composants graphiques
         * Le modèle graphique utilisé dans ChtiCanon est basé sur une API
         * graphique dite immédiate.
         * Cela signifie qu'on a un accès direct à l'écran, et qu'on utilise
         * le processeur graphique pour dessiner dessus. Pour animer les objets
         * on va simplement redessiner tout l'écran avec l'état des objets mis
         * à jour. On aura ainsi l'impression que les objets sont animés
         * puisque leur position, couleur ou autre changera à chaque image.
         * Dans ChtiCanon nous avons fixé un dt (delta temps) de 16 ms (1/60
         * de seconde), et on anime l'objet pour ce dt puis redessine l'écran à
         * chaque fois. Pour cela, chaque objet doit mettre à disposition trois
         * méthodes:
         *   - handle: calculer ce qui s'est passé pour l'objet durant le
         *             dernier dt et mettre à jour ses variables internes pour
         *             le dessin.
         *   - draw:   dessiner l'objet dans son état actuel en utilisant les
         *             variables internes.
         * Les composants reçoivent l'état de la souris pour la gestion. Ils
         * doivent retourner vrai s'ils ont fait quelque chose de la souris
         * (l'état ne sera alors pas passé à d'autres éléments), faux sinon
         * (par exemple pour un objet qui n'a que faire du clic, comme la liste
         * des joueurs).
         */
        public abstract class Component
        {
            public Vector2 pos, size;
            // Retourne true si géré l'événement, false sinon
            public abstract bool handle(MouseState mouse);
            public abstract void draw();
            public bool inMe(MouseState mouse)
            {
                return mouse.AbsolutePosition.X >= pos.X && mouse.AbsolutePosition.Y >= pos.Y &&
                    mouse.AbsolutePosition.X <= pos.X + size.X && mouse.AbsolutePosition.Y <= pos.Y + size.Y;
            }
        }

        /* Notion de containers
         * Les containers sont des composants spéciaux, en ce sens qu'ils
         * contiennent d'autres composants. L'utilité est que leur gestion
         * (handle) et leur dessin (draw) est géré directement par la classe
         * de base, ce qui peut simplifier le code de dessin.
         * Toutefois si on souhaite gérer de façon très précise l'ordre des
         * objets (par exemple pour faire passer un objet par-dessus un autre)
         * il se peut qu'on ne soit pas satisfait par ce que permet le
         * container. Rien n'empêche alors d'appeler les méthodes soi-même pour
         * une liste de composants gardés en interne.
         */
        public class Container : Component
        {
            protected List<Component> children = new List<Component>();

            // Ajoute un composant au container
            public void add(Component g)
            {
                children.Add(g);
            }
            // Gère les enfants. Retourne vrai si un des enfants a géré
            // l'événement de la souris.
            public override bool handle(MouseState mouse)
            {
                bool processed = false;
                Vector2 oldPos = mouse.addToPosition(-pos.X, -pos.Y);
                for (int i = 0; i < children.Count; i++)
                    processed |= children[i].handle(mouse);
                mouse.Position = oldPos;
                return processed;
            }
            // Dessine les enfants
            public override void draw()
            {
                for (int i = 0; i < children.Count; i++)
                {
                    Component g = children[i];
                    Vector2 oldPos = g.pos;
                    g.pos += pos;
                    g.draw();
                    g.pos = oldPos;
                }
            }
        }

        // Etat de la souris
        public class MouseState
        {
            private const int nbButtons = 3;
            private bool[] buttonDown = new bool[nbButtons];
            private int[] buttonReleased = new int[nbButtons];
            private int[] buttonClick = new int[nbButtons];
            private int[] buttonDoubleClick = new int[nbButtons];
            private bool left = false;
            private Vector2 position;
            public Vector2 Position
            {
                get { return position; }
                set { position = value; }
            }
            public Vector2 AbsolutePosition
            {
                get { return new Vector2(position.X * Game.screenWidth, position.Y * Game.screenHeight); }
            }
            public void handle()
            {
                for (int i = 0; i < nbButtons; i++)
                {
                    if (buttonClick[i] > 0)
                        buttonClick[i]--;
                    if (buttonReleased[i] > 0)
                        buttonReleased[i]--;
                    if (buttonDoubleClick[i] > 0)
                        buttonDoubleClick[i]--;
                }
            }
            public Vector2 transformed(Matrix m)
            {
                Vector2 r = position;
                r.X *= Game.screenWidth;
                r.Y *= Game.screenHeight;
                r.TransformCoordinate(Matrix.Invert(m));
                r.X /= Game.screenWidth;
                r.X /= Game.screenWidth;
                return r;
            }
            // Rajoute une valeur à la position et retourne l'ancienne valeur
            public Vector2 addToPosition(float x, float y)
            {
                Vector2 oldPos = position;
                position.X += x / Game.screenWidth;
                position.Y += y / Game.screenHeight;
                return oldPos;
            }

            private static int buttonNo(MouseButtons b)
            {
                if (b == MouseButtons.Left)
                    return 0;
                else if (b == MouseButtons.Right)
                    return 1;
                else if (b == MouseButtons.Middle)
                    return 2;
                return -1;
            }
            public void setPosition(float x, float y) { position.X = x; position.Y = y; left = false; }
            public void setClick(MouseButtons button) { buttonClick[buttonNo(button)] = 2; }
            public void setDoubleClick(MouseButtons button) { buttonDoubleClick[buttonNo(button)] = 2; }
            public void setDown(MouseButtons button) { buttonDown[buttonNo(button)] = true; }
            public void setUp(MouseButtons button) { buttonDown[buttonNo(button)] = false; }
            public void setReleased(MouseButtons button) { buttonReleased[buttonNo(button)] = 2; }
            public bool isClicked(MouseButtons button) { return buttonClick[buttonNo(button)] == 1; }
            public bool isDoubleClicked(MouseButtons button) { return buttonDoubleClick[buttonNo(button)] == 1; }
            public bool isDown(MouseButtons button) { return buttonDown[buttonNo(button)]; }
            public bool isReleased(MouseButtons button) { return buttonReleased[buttonNo(button)] == 1; }
            public bool isInScreen() { return !left; }
            public void setLeft() { position = new Vector2(-1000, -1000); left = true; }
        }

        public class ComponentManager : Container { }

        public class ChromeButton : Button
        {
            private static CanonGfx.Image img;
            public ChromeButton(Vector2 pos, Vector2 size)
                : base(pos, size)
            {
                if (img == null)
                    img = Game.Drawer.createImage("button.png");
            }
            public override void draw()
            {
                if (highlighted && down)
                    img.setSourceRectPixels(new CanonGfx.Rectangle(0, 100.5f, 80, 49));
                else if (highlighted)
                    img.setSourceRectPixels(new CanonGfx.Rectangle(0, 50.5f, 80, 49));
                else
                    img.setSourceRectPixels(new CanonGfx.Rectangle(0, 0.5f, 80, 49));
                img.setPosition(pos.X, pos.Y);
                img.setScale(size.X, size.Y);
                Game.Drawer.drawImage(img);
            }
            public override void onClick(MouseButtons button) { }
        }

        public abstract class Button : Component
        {
            protected bool highlighted = false, down = false;

            public Button(Vector2 pos, Vector2 size)
            {
                this.pos = pos;
                this.size = size;
            }

            public override bool handle(MouseState mouse)
            {
                highlighted = inMe(mouse);
                if (mouse.isClicked(MouseButtons.Left) && highlighted)
                {
                    down = true;
                    onDown(MouseButtons.Left);
                }
                if (mouse.isReleased(MouseButtons.Left) && down && highlighted)
                    onClick(MouseButtons.Left);
                if (!mouse.isDown(MouseButtons.Left))
                    down = false;
                return highlighted || down;
            }
            // Arrive quand un clic total (cliqué + relâché sur le même contrôle) est réalisé
            public abstract void onClick(MouseButtons button);
            // Arrive quand le bouton est juste enfoncé
            public virtual void onDown(MouseButtons button) { }
        }

        // Bouton qui s'aggrandit quand on le sélectionne
        public abstract class GrowingPictogramButton : Button
        {
            private VectorPositionInterpolator scale = new VectorPositionInterpolator(1, 1, 0.3f);
            private float minSize, bigSize, maxSize;
            public GrowingPictogramButton(Vector2 pos, Vector2 size, float minSize, float bigSize, float maxSize)
                : base(pos, size)
            {
                this.minSize = minSize;
                this.bigSize = bigSize;
                this.maxSize = maxSize;
            }
            public override bool handle(MouseState mouse)
            {
                bool oldHighlight = highlighted, oldDown = down;
                bool result = base.handle(mouse);
                bool up = oldDown && !down;
                if (highlighted && !oldHighlight && !down)      // Juste pressé
                    scale.Dest = new Vector2(maxSize, maxSize);
                else if (highlighted && down)                   // Pressé, souris maintenue dessus
                    scale.Dest = new Vector2(minSize, minSize);
                else if (highlighted && (Math.Abs(scale.X - maxSize) < 0.1f || up))     // Arrivé au point culimant -> redescend (ou bien juste relâché)
                    scale.Dest = new Vector2(bigSize, bigSize);
                else if (!highlighted)                      // Pas sélectionné
                    scale.Dest = new Vector2(1, 1);
                scale.handle();
                return result;
            }
            // Dessine l'arrière plan du bouton, en commençant aux coordonnées (0, 0), et de taille max 'size'
            protected virtual void drawBack() { }
            // Dessine le pictorgramme du bouton, en commençant aux coordonnées (0, 0), et de taille max 'size'. Il sera éventuellement redimensionné
            protected virtual void drawPicto() { }
            // Dessin de base
            public override void draw()
            {
                Matrix state = Game.Drawer.CurrentMatrix;
                // Arrière plan
                Game.Drawer.moveScreen(pos.X, pos.Y);
                drawBack();
                // Pictogramme
                Game.Drawer.CurrentMatrix = state;
                Game.Drawer.moveScreen(pos.X, pos.Y);
                Game.Drawer.moveScreen(-size.X / 2 * (scale.X - 1), -size.Y / 2 * (scale.Y - 1));
                Game.Drawer.scaleScreen(scale.X, scale.Y);
                drawPicto();
                Game.Drawer.CurrentMatrix = state;
            }
        }

        public class GameActionButton : CanonInterface.ChromeButton
        {
            protected Game g;
            private static CanonGfx.Image img;
            protected int index;

            public GameActionButton(Game g, Vector2 pos, int index)
                : base(pos, new Vector2(50, 30))
            {
                this.g = g;
                this.index = index;
                if (img == null)
                    img = Game.Drawer.createImage("icoAction.png");
                img.setScale(32, 32);
            }

            public override void draw()
            {
                base.draw();
                img.setSourceRectPixels(new CanonGfx.Rectangle(0, 32 * index, 32, 32));
                img.setPosition(pos.X + (size.X - 32) / 2, pos.Y + (size.Y - 32) / 2);
                Game.Drawer.drawImage(img);
            }
        }

        public class ZoomButton : GameActionButton
        {
            public ZoomButton(Game g, Vector2 pos)
                : base(g, pos, 0) { }
            public override void onClick(MouseButtons button)
            {
                g.zoomButtonPressed();
            }
        }
        public class PlayerPanelButton : GameActionButton
        {
            public PlayerPanelButton(Game g, Vector2 pos)
                : base(g, pos, 2) {
                size = new Vector2(24, 16);
            }
            public override void onClick(MouseButtons button)
            {
                g.playerPanelButtonPressed();
            }
        }
        public class GunListButton : GameActionButton
        {
            public GunListButton(Game g, Vector2 pos)
                : base(g, pos, 1) { }
            public override void onClick(MouseButtons button)
            {
                g.gunListButtonPressed();
            }
        }
        public class WindIndicator : Component
        {
            private static CanonGfx.Image img, fleche;
            public FloatPositionInterpolator Angle = new FloatPositionInterpolator(0, 0.05f), Force = new FloatPositionInterpolator(0, 0.1f);

            public WindIndicator(Vector2 pos) {
                this.pos = pos;
                this.size = new Vector2(48, 48);
                if (img == null)
                {
                    img = Game.Drawer.createImage("windcircle.png");
                    fleche = Game.Drawer.createImage("fleche.png", 48, 48);
                }
            }

            // Retourne une couleur correspondant à la force (entre 0 et 1)
            private Color colorFromForce(float force)
            {
                if (force < 0.4f)
                    return Color.FromArgb(0, 128 + (int)(force * 64 / 0.4f), 255 - (int)(force * 255 / 0.4f));
                else if (force < 0.7f)
                {
                    force -= 0.4f;
                    return Color.FromArgb((int)(force * 255 / 0.3f), 192 + (int)(force * 63 / 0.3f), 0);
                }
                else
                {
                    force -= 0.7f;
                    return Color.FromArgb(255, 255 - (int)(force * 255 / 0.3f), 0);
                }
            }
            // Dessine l'indicateur
            public override void draw()
            {
                img.setPosition(pos.X, pos.Y);
                Game.Drawer.drawImage(img);
                fleche.Angle = Angle.Current;
                fleche.Color = colorFromForce(Force.Current);
                Game.Drawer.drawImage(fleche);
            }
            public override bool handle(MouseState mouse)
            {
                Angle.handle();
                Force.handle();
                return inMe(mouse);
            }
            public void SetUp(float windDirection, float windForce)
            {
                Angle.Dest = windDirection;
                Force.Dest = windForce;
            }
        }

        // Indicateur de statut des joueurs (vie, nom)
        public class PlayerStatIndicator : Component
        {
            // Les joueurs qu'il doit gérer
            private Player[] p;
            private Game g;
            private static CanonGfx.Image img;
            public bool EnhancedView = false;
            private FloatPositionInterpolator leftpos = new FloatPositionInterpolator(0, 0.2f);

            public PlayerStatIndicator(Game game, Player[] players)
            {
                p = players;
                pos.Y = 96;
                g = game;
                if (img == null)
                    img = Game.Drawer.createImage("statbar.png");
            }
            public override void draw()
            {
                g.setClipping((int)pos.X, (int)pos.Y, 75, 16 * p.Length);
                for (int i = 0; i < p.Length; i++)
                {
                    img.Color = Color.FromArgb(224 << 24 | (p[i].Color.ToArgb() & 0xffffff));
                    img.setPosition(pos.X - 23, pos.Y + 16 * i);
                    Game.Drawer.drawImage(img);
                    Game.Drawer.dev.RenderState.ScissorTestEnable = true;
                    Game.Drawer.drawText(Game.fontSmall, (int)pos.X, (int)pos.Y + i * 16 + 2, p[i].Name, Color.Black);
                    Game.Drawer.dev.RenderState.ScissorTestEnable = false;
                    // Pour aligner à droite (chaque caractère fait 7 pixels)
                    string life = p[i].Life.ToString();
                    Game.Drawer.drawText(Game.fontSmall, (int)pos.X + 80 + (3 - life.Length) * 7, (int)pos.Y + i * 16 + 2, p[i].Life.ToString(), Color.Black);
                }
            }
            public override bool handle(MouseState mouse)
            {
                leftpos.Dest = EnhancedView ? -80 : 0;
                leftpos.handle();
                pos.X = leftpos.Current;
                return false;
            }
        }

        public class GunButton : GrowingPictogramButton
        {
            private static CanonGfx.Image img, text;
            private int index;
            private const int gunItemWidth = 32, gunItemHeight = 32, textItemWidth = 64, textItemHeight = 16;
            private GunPanel parent;

            public GunButton(GunPanel parent, Vector2 pos, int buttonIndex)
                : base(pos, new Vector2(64, 64), 0.9f, 1.25f, 1.6f)
            {
                index = buttonIndex;
                this.parent = parent;
                if (img == null)
                {
                    img = Game.Drawer.createImage("icoGun.png");
                    text = Game.Drawer.createImage("txtGun.png");
                }
                img.setScale(gunItemWidth, gunItemHeight);
                text.setScale(textItemWidth, textItemHeight);
            }
            public override void onClick(MouseButtons button)
            {
                parent.Open = false;
            }
            public override void onDown(MouseButtons button)
            {
                parent.SelectedGun = index;
            }
            protected override void drawBack()
            {
                text.setSourceRectPixels(new CanonGfx.Rectangle(0, index * textItemHeight, textItemWidth, textItemHeight));
                text.setPosition(0, 44);
                Game.Drawer.drawImage(text);
                // Nombre dispo
                if (index > 0)
                    Game.Drawer.drawText(Game.fontVerySmall, 4, 3, parent.Player.GunCount[index] + "x", Color.White);
            }
            protected override void drawPicto()
            {
                img.Color = (down && highlighted) ? Color.FromArgb(192, 192, 192) : Color.White;
                img.setSourceRectPixels(new CanonGfx.Rectangle(0, gunItemHeight * index, gunItemWidth, gunItemHeight));
                img.setPosition((size.X - gunItemWidth) / 2.0f, (size.Y - gunItemHeight - 4) / 2.0f);
                Game.Drawer.drawImage(img);
            }
        }

        public class GunPanel : Container
        {
            // Il est possible de le définir par qqn d'autre
            private VectorPositionInterpolator redRectPos = new VectorPositionInterpolator(0, 0, 0.4f);
            public int SelectedGun
            {
                get { return Player.SelectedGun; }
                set { Player.SelectedGun = value; }
            }
            private VectorPositionInterpolator gunPanelPos = new VectorPositionInterpolator(Game.screenWidth, 60, 0.2f);
            private bool gunPanelOpen = false;
            public bool Open
            {
                get { return gunPanelOpen; }
                set {
                    bool val = Player.CanCloseGunPanel ? value : true;
                    gunPanelOpen = val;
                    gunPanelPos.Dest = new Vector2(Game.screenWidth - (gunPanelOpen ? size.X : 0), 60);
                }
            }
            public Player Player;

            public GunPanel(Player p)
                : base()
            {
                size = new Vector2(256, 64);
                add(new CanonInterface.GunButton(this, new Vector2(0, 0), 0));
                add(new CanonInterface.GunButton(this, new Vector2(64, 0), 1));
                add(new CanonInterface.GunButton(this, new Vector2(128, 0), 2));
                add(new CanonInterface.GunButton(this, new Vector2(192, 0), 3));
                Player = p;
            }
            public override bool handle(MouseState mouse)
            {
                pos = gunPanelPos.Current;
                gunPanelPos.handle();
                // Met à jour le rectangle de sélection
                redRectPos.Dest.X = SelectedGun * 64;
                redRectPos.handle();
                return base.handle(mouse);
            }
            public override void draw()
            {
                float posX = redRectPos.X;
                Game.Drawer.drawFillRect(pos.X, pos.Y, posX, size.Y, unchecked((int)0x80000000));
                Game.Drawer.drawFillRect(pos.X + posX, pos.Y, 64, size.Y, unchecked((int)0x80800000));
                Game.Drawer.drawFillRect(pos.X + posX + 64, pos.Y, size.X - 64 - posX, size.Y, unchecked((int)0x80000000));
                base.draw();
            }
        }

        // Ecran affiché en cas de détection de triche
        public class CheatScreen : Component
        {
            private static CanonGfx.Image img;
            private int counter = 0;
            private string reason;
            public CheatScreen(Game g, string reason)
            {
                this.reason = reason;
                if (img == null)
                    img = Game.Drawer.createImage("cheat.png", 348, 183);
            }
            public override void draw()
            {
                int a = Math.Min(counter * 2, 127);
                Game.Drawer.drawFillRect(0, 0, Game.screenWidth, Game.screenHeight, a << 24);
                // Image indiquant le cheat
                img.setPosition((Game.screenWidth - img.Width) / 2, (Game.screenHeight - img.Height - 30) / 2);
                img.ColorARGB = a << 25 | 0x1ffffff;
                Game.Drawer.drawImage(img);
                // Affiche la raison, centrée, plus bas
                Game.Drawer.drawText(Game.fontSmall, (Game.screenWidth - Game.fontSmall.stringWidth(reason)) / 2, 270, reason, Color.White);
            }
            public override bool handle(MouseState mouse)
            {
                counter++;
                // Mange les clics de souris pour bloquer le jeu
                return true;
            }
        }

        // Ecran affiché quand quelqu'un a gagné
        public class WinnerScreen : Component
        {
            private string name, winText;
            // Petite attente (3 secondes) avant d'afficher le gagnant
            private int counter = -180;
            private FloatPositionInterpolator posWinner = new FloatPositionInterpolator(0, 0.1f),
                posWinText = new FloatPositionInterpolator(Game.screenWidth, 0.1f);
            // Nom du joueur qui a gagné
            public WinnerScreen(Game g, string playerName)
            {
                if (playerName != null)
                {
                    name = playerName;
                    winText = "is the winner!";
                }
                else
                {
                    name = "Too bad";
                    winText = "Nobody won";
                }
                posWinner.Current = -Game.font.stringWidth(name) * 2 - 4;
                posWinner.Dest = (Game.screenWidth / 3 - Game.font.stringWidth(name)) / 2;
                posWinText.Dest = (Game.screenWidth / 2 - Game.font.stringWidth(winText)) / 2;
            }
            public override void draw()
            {
                if (counter < 0)
                    return;
                Matrix m = Game.Drawer.CurrentMatrix;
                // Alpha progressif
                int a = Math.Min(counter * 2, 160);
                // Fond d'écran
                Game.Drawer.drawFillRect(0, 0, Game.screenWidth, Game.screenHeight, a << 24);
                // Dessine 2 fois les textes (ombrés)
                Game.Drawer.scaleScreen(3, 3);
                Game.Drawer.drawText(Game.font, posWinner.Current + 1, 100 / 3 + 1, name, Color.Black);
                Game.Drawer.drawText(Game.font, posWinner.Current, 100 / 3, name, Color.White);
                Game.Drawer.CurrentMatrix = m;
                Game.Drawer.scaleScreen(2, 2);
                Game.Drawer.drawText(Game.font, posWinText.Current + 1, 170 / 2 + 1, winText, Color.Black);
                Game.Drawer.drawText(Game.font, posWinText.Current, 170 / 2, winText, Color.White);
                Game.Drawer.CurrentMatrix = m;
            }
            public override bool handle(MouseState mouse)
            {
                // Evite l'overflow
                counter = Math.Min(counter + 1, 1024);
                if (counter >= 120)
                    posWinner.handle();
                if (counter >= 150)
                    posWinText.handle();
                // Mange les clics de souris pour bloquer le jeu
                return true;
            }
        }
    }
}
