using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Drawing;
using Canon;
using System.IO;

namespace CanonGfx
{
    public class Gfx
    {
        public Device dev;
        public Matrix CurrentMatrix
        {
            get { return dev.Transform.World; }
            set { dev.Transform.World = value; }
        }
        private bool Lost = false;
        private PresentParameters pp;

        public Gfx(Device dev) {
            this.dev = dev;
            pp = dev.PresentationParameters;
        }
        public void beginScene() {
            // Avant de commencer à dessiner, on essaie de récupérer la device si on l'a perdue
            if (Lost)
                attemptDeviceRecovery();
            // Si ça a échoué, inutile de continuer...
            if (Lost)
                return;
            dev.BeginScene();
            dev.RenderState.ZBufferEnable = false;
            dev.RenderState.Lighting = false;
            dev.RenderState.CullMode = Cull.None;
            // Alpha blending
            dev.RenderState.AlphaBlendEnable = true;
            dev.RenderState.SourceBlend = Blend.SourceAlpha;
            dev.RenderState.DestinationBlend = Blend.InvSourceAlpha;
            // So that texture will also take the diffuse alpha
//            dev.SetRenderState(RenderStates.TextureFactor, System.Drawing.Color.FromArgb(128, 255, 255, 255).ToArgb());
            dev.SetTextureStageState(0, TextureStageStates.ColorArgument1, (int)TextureArgument.TextureColor);
            dev.SetTextureStageState(0, TextureStageStates.AlphaArgument1, (int)TextureArgument.TextureColor);
            dev.SetTextureStageState(0, TextureStageStates.AlphaArgument2, (int)TextureArgument.Diffuse);
            dev.SetTextureStageState(0, TextureStageStates.AlphaOperation, (int)TextureOperation.Modulate);
            dev.Transform.Projection = Matrix.OrthoLH(Game.screenWidth, Game.screenHeight, 1, -1) * Matrix.Scaling(1, -1, 1);
            dev.Transform.View = Matrix.Translation(-Game.screenWidth / 2 - 0.5f, -Game.screenHeight / 2 - 0.5f, 0);
            // set up texture transform for sprite
/*            dev.TextureState[0].TextureTransform = TextureTransform.Count2;
            Matrix tt = Matrix.Identity;
            tt.M22 = -1; // flip v texcoord
            dev.Transform.Texture0 = tt;*/
        }

        public void endScene() {
            dev.Transform.World = Matrix.Identity;
            try
            {
                dev.EndScene();
                dev.Present();
            }
            catch (Exception)
            {
                // Perdu la device! On essaiera de la récupérer
                Lost = true;
            }
        }

        // Tente de retrouver la device si elle a été perdue (changement de résolution, etc.)
        private void attemptDeviceRecovery()
        {
            try
            {
                dev.TestCooperativeLevel();
            }
            catch (DeviceLostException) {}
            catch (DeviceNotResetException)
            {
                try
                {
                    dev.Reset(pp);
                    Lost = false;
                }
                catch (DeviceLostException) { }
            }
        }

        public void clearScreen(Color c)
        {
            // Efface la fenêtre
            dev.Clear(ClearFlags.Target, c, 1, 0);
        }

/*        public Microsoft.DirectX.Direct3D.Font createFont(string name, int size)
        {
            System.Drawing.Font font = new System.Drawing.Font(name, size);
            return new Microsoft.DirectX.Direct3D.Font(dev, font);
        }

        public void drawText(Microsoft.DirectX.Direct3D.Font font, int x, int y, string texte, Color color)
        {
            font.DrawText(null, texte, 0, 0, color);
        }*/

        public Font createFont(string imageFile, string sizeFile)
        {
            return new Font(this, imageFile, sizeFile);
        }

        public void drawText(Font f, float x, float y, string texte, Color color)
        {
            int col = color.ToArgb();
            CustomVertex.PositionColoredTextured[] verts = new CustomVertex.PositionColoredTextured[6 * texte.Length];
            int noVert = 0;
            foreach (char c in texte)
            {
                int charWidth = f.charWidth(c) + 3, charHeight = f.TextureHeight;
                float u = (float)f.charPosOnTexture(c) / f.TextureWidth, uWidth = (float)(charWidth) / f.TextureWidth,
                      v = 0, vHeight = 1;
                verts[noVert].Position = new Vector3(x, y, 0);
                verts[noVert].Tu = u;
                verts[noVert].Tv = v;
                verts[noVert++].Color = col;
                verts[noVert].Position = new Vector3(x + charWidth, y, 0);
                verts[noVert].Tu = u + uWidth;
                verts[noVert].Tv = v;
                verts[noVert++].Color = col;
                verts[noVert].Position = new Vector3(x, y + charHeight, 0);
                verts[noVert].Tu = u;
                verts[noVert].Tv = v + vHeight;
                verts[noVert++].Color = col;

                // Triangle 2
                verts[noVert].Position = new Vector3(x + charWidth, y, 0);
                verts[noVert].Tu = u + uWidth;
                verts[noVert].Tv = v;
                verts[noVert++].Color = col;
                verts[noVert].Position = new Vector3(x, y + charHeight, 0);
                verts[noVert].Tu = u;
                verts[noVert].Tv = v + vHeight;
                verts[noVert++].Color = col;
                verts[noVert].Position = new Vector3(x + charWidth, y + charHeight, 0);
                verts[noVert].Tu = u + uWidth;
                verts[noVert].Tv = v + vHeight;
                verts[noVert++].Color = col;
                x += f.charWidth(c);
            }
            dev.VertexFormat = CustomVertex.PositionColoredTextured.Format;
            setBilinearFiltering(true);
            dev.SetTexture(0, f.Texture);
            dev.DrawUserPrimitives(PrimitiveType.TriangleList, noVert / 3, verts);
            dev.SetTexture(0, null);
        }

        public Image createImage(string file)
        {
            return new Image(TextureLoader.FromFile(dev, Game.getResourceFile(file), 0, 0, 1, Usage.None, Format.A8B8G8R8, Pool.Managed, Filter.None, Filter.None, 0));
        }
        public Image createImage(string file, int width, int height)
        {
            return createImage(file, width, height, Format.A8B8G8R8);
        }
        public Image createImage(string file, int width, int height, Format f)
        {
            return new Image(TextureLoader.FromFile(dev, Game.getResourceFile(file), width, height, 1, Usage.None, f, Pool.Managed, Filter.Linear, Filter.Linear, 0), width, height);
        }

        public void drawImage(Image img)
        {
            if (img.Angle != 0)
            {
                float oldX = img.X, oldY = img.Y;
                Matrix m = CurrentMatrix;
                moveScreen(img.X, img.Y);
                rotateScreen(-img.Angle, img.ScaleWidth / 2, img.ScaleHeight / 2);
                img.X = img.Y = 0;
                drawImageSimple(img);
                img.X = oldX; img.Y = oldY;
                CurrentMatrix = m;
            }
            else
                drawImageSimple(img);
        }

        public void drawImageSimple(Image img)
        {
            CustomVertex.PositionColoredTextured[] verts = new CustomVertex.PositionColoredTextured[4];
            Rectangle source = img.getSourceRect();
            int col = img.ColorARGB;
            verts[0].Position = new Vector3(img.X, img.Y, 0);
            verts[0].Tu = source.X;
            verts[0].Tv = source.Y;
            verts[0].Color = col;
            verts[1].Position = new Vector3(img.X + img.ScaleWidth, img.Y, 0);
            verts[1].Tu = source.X + source.Width;
            verts[1].Tv = source.Y;
            verts[1].Color = col;
            verts[2].Position = new Vector3(img.X, img.Y + img.ScaleHeight, 0);
            verts[2].Tu = source.X;
            verts[2].Tv = source.Y + source.Height;
            verts[2].Color = col;
            verts[3].Position = new Vector3(img.X + img.ScaleWidth, img.Y + img.ScaleHeight, 0);
            verts[3].Tu = source.X + source.Width;
            verts[3].Tv = source.Y + source.Height;
            verts[3].Color = col;
            dev.VertexFormat = CustomVertex.PositionColoredTextured.Format;
            setBilinearFiltering(img.BilinearFiltering);
            setWrapping(img.Wrapping);
            dev.SetTexture(0, img.Tex);
            dev.DrawUserPrimitives(PrimitiveType.TriangleStrip, 2, verts);
            dev.SetTexture(0, null);
        }

        public void drawFillRect(float x0, float y0, float width, float height, int color)
        {
            CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[4];
            verts[0].Position = new Vector3(x0, y0, 0);
            verts[0].Color = color;
            verts[1].Position = new Vector3(x0 + width, y0, 0);
            verts[1].Color = color;
            verts[2].Position = new Vector3(x0, y0 + height, 0);
            verts[2].Color = color;
            verts[3].Position = new Vector3(x0 + width, y0 + height, 0);
            verts[3].Color = color;
            dev.VertexFormat = CustomVertex.PositionColored.Format;
            dev.DrawUserPrimitives(PrimitiveType.TriangleStrip, 2, verts);
        }
        
        // Fait une rotation autour de d'un point
        public void rotateScreen(float angle, float x, float y)
        {
            dev.Transform.World =
                Matrix.Translation(-x, -y, 0) *
                Matrix.RotationZ(angle) *
                Matrix.Translation(x, y, 0) *
                dev.Transform.World;
        }
        public void scaleScreen(float x, float y)
        {
            dev.Transform.World = Matrix.Scaling(x, y, 1) * dev.Transform.World;
        }
        public void moveScreen(float x, float y)
        {
            dev.Transform.World = Matrix.Translation(x, y, 0) * dev.Transform.World;
        }

        private void setBilinearFiltering(bool enable)
        {
            if (enable)
            {
                dev.SetSamplerState(0, SamplerStageStates.MinFilter, (int)TextureFilter.Linear);
                dev.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.Linear);
            }
            else
            {
                dev.SetSamplerState(0, SamplerStageStates.MinFilter, (int)TextureFilter.None);
                dev.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.None);
            }
        }
        private void setWrapping(bool enable)
        {
            if (enable)
            {
                Game.Drawer.dev.SetSamplerState(0, SamplerStageStates.AddressU, (int)TextureAddress.Wrap);
                Game.Drawer.dev.SetSamplerState(0, SamplerStageStates.AddressV, (int)TextureAddress.Wrap);
            }
            else
            {
                Game.Drawer.dev.SetSamplerState(0, SamplerStageStates.AddressU, (int)TextureAddress.Border);
                Game.Drawer.dev.SetSamplerState(0, SamplerStageStates.AddressV, (int)TextureAddress.Border);
            }
        }
    }

    // Une image (texture)
    public class Image
    {
        // Tex accessible seulement en lecture depuis l'extérieur
        private Texture tex;
        internal Texture Tex {
            get { return tex; }
            private set { tex = value; }
        }
        public int ColorARGB = ~0;
        public Color Color
        {
            get { return Color.FromArgb(ColorARGB); }
            set { ColorARGB = value.ToArgb(); }
        }
        protected Rectangle src, dst;
        protected int width = 0, height = 0;
        public bool BilinearFiltering = true, Wrapping = false;
        public float Angle = 0;
        public float X { get { return dst.X; } set { dst.X = value; } }
        public float Y { get { return dst.Y; } set { dst.Y = value; } }
        public int Width { get { return width; } }
        public int Height { get { return height; } }

        // Permet d'obtenir ou définir le rectangle source (la partie à l'intérieur de l'image)
        public Rectangle getSourceRect()
        {
            return src.clone();
        }
        public void setSourceRect(Rectangle r)
        {
            src = r.clone();
        }
        public void setSourceRectPixels(Rectangle r)
        {
            src = new Rectangle(r.X / width, r.Y / height, r.Width / width, r.Height / height);
        }
        public void setPosition(float x, float y)
        {
            dst.X = x;
            dst.Y = y;
        }
        public void setScale(float x, float y)
        {
            dst.Width = x;
            dst.Height = y;
        }
        public float ScaleWidth { get { return dst.Width; } }
        public float ScaleHeight { get { return dst.Height; } }


        public Image(Texture t)
        {
            SurfaceDescription desc = t.GetLevelDescription(0);
            width = desc.Width;
            height = desc.Height;
            tex = t;
            dst = new Rectangle(0, 0, width, height);
            src = new Rectangle(0, 0, 1, 1);
        }
        public Image(Texture t, int width, int height)
        {
            this.tex = t;
            this.width = width;
            this.height = height;
            this.dst = new Rectangle(0, 0, width, height);
            this.src = new Rectangle(0, 0, 1, 1);
        }
    }

    public class Font
    {
        private Image texture;
        private int[] sizes = new int[256];
        private int[] pos = new int[256];
        private int height;

        public Texture Texture { get { return texture.Tex; } }
        public int CharHeight { get { return height; } }
        public int TextureWidth { get { return texture.Width; } }
        public int TextureHeight { get { return texture.Height; } }

        public Font(Gfx g, string imageFile, string sizeFile)
        {
            int currentPos = 0;
            StreamReader r = new StreamReader(Game.getResourceFile(sizeFile));
            texture = g.createImage(imageFile);
            height = int.Parse(r.ReadLine());
            for (int i = 0; i < 256; i++)
            {
                pos[i] = currentPos;
                sizes[i] = int.Parse(r.ReadLine());
                currentPos += sizes[i] + 4;
            }
            r.Close();
        }

/*        public void drawChar(Gfx g, int x, int y, char c, int color)
        {
            texture.setSourceRectPixels(new Rectangle(0, 4 + height * (int)c, sizes[(int)c] + 3, height));
            texture.setPosition(x, y);
            texture.ColorARGB = color;
            texture.setScale(sizes[(int)c] + 3, height);
            g.drawImage(texture);
        }*/

        public int stringWidth(string s)
        {
            int size = 0;
            foreach (char c in s)
                size += sizes[(int)c];
            return size;
        }
        public int charWidth(char c) { return sizes[(int)c]; }
        public int charPosOnTexture(char c) { return pos[(int)c]; }
    }


    public class Rectangle
    {
        public float X, Y, Width, Height;
        public Rectangle(float x, float y, float width, float height)
        {
            X = x;
            Y = y;
            Width = width;
            Height = height;
        }
        public Rectangle clone() { return new Rectangle(X, Y, Width, Height); }
    }
}
