using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenGLSupport;
using System.IO;

/// <summary>
/// Rendering rules for each component
/// </summary>
namespace PG_Sim
{
    public static class Renderer
    {
        // Textures
        private static List<uint> textures = new List<uint>();
        private static List<string> texNames = new List<string>();

        // Display lists
        private static List<uint> displayLists = new List<uint>();

        // show flags
        public static bool showForce = false;
        public static bool showGlide = false;
        public static int camera = 1;
        public static string toPrint = "";

        // pause flag
        public static bool pause = false;

        // land flag and state func
        private static bool land = false;
        private static double land_alpha = 0;
        private static double land_pos = 0;

        // terain table
        private static double[,] terrain_height = new double[100, 100];
        
        // Initialize
        public static void Init()
        {
            // load textures
            Renderer.LoadTextures();

            // generate display lists
            Renderer.GenerateDisplayLists();

            // load heightmap
            Import file_points = new Import("teren_seznam_bodu100.txt");
            double[][] points = file_points.loadPoints();
            for (Int32 i = 0; i < points.Length; i++)
            {
                Int32 x = (Int32)Math.Round(points[i][0] / 100) + 50;
                Int32 y = (Int32)Math.Round(points[i][1] / 100) + 50;

                terrain_height[x, y] = points[i][2];
                //Console.WriteLine("[" + x + "," + y + "] = " + terrain_height[x, y]);
            }
        }
        public static void Destroy() 
        {
            // release textures
            Renderer.ReleaseTextures();

            // delete display lists
            Renderer.DeleteDisplayLists();
        }

        // Draw each frame of scene
        public static void Scene()
        {
            // get actual position
            kinematics pos = Physics.GetPosition();
                        
            // collision test
            if (Colision(pos.x, pos.y, pos.z) && Physics.run_solver != false)
            {
                Physics.run_solver = false;
                land = true;
                Console.WriteLine("Collision with terain detected!");
            }

            // výběr kamery
            switch (camera)
            {
                case 1:
                    // pohled pilota
                    glu.LookAt(pos.x, pos.y, pos.z, pos.x + Math.Cos(pos.rz + Math.PI / 2), pos.y + Math.Sin(pos.rz + Math.PI / 2), pos.z, 0, 0, 1);
                    break;
                case 2:
                    // pohled šikmo zepředu
                    glu.LookAt(-9 + pos.x, 10 + pos.y, pos.z + 10, pos.x, pos.y, pos.z, 0, 0, 1);
                    break;
                case 3:
                    // stojící natáčející pozorovatel
                    glu.LookAt(Math.Round(pos.x / 100) * 100, Math.Round(pos.y / 100) * 100, Math.Max(5,Math.Round(pos.z / 40) * 40), pos.x, pos.y, pos.z, 0, 0, 1);
                    break;
                case 4:
                    // pohled z vrchu
                    glu.LookAt(0.1 + pos.x, 0.1 + pos.y, pos.z + 18, pos.x, pos.y, pos.z, 1, 0, 0);
                    break;
            }

            // zapnutí opičáren
            gl.Enable(GL.POLYGON_SMOOTH);
            gl.Enable(GL.LINE_SMOOTH);
            gl.Enable(GL.BLEND);

            // render terain and skybox
            gl.Translated(0, 0, -1.5); // height of harness and pilot (strange, hm?)
            Renderer.CallDisplayList("skybox");
            Renderer.CallDisplayList("terain");
            gl.Translated(0, 0, 1.5);
                       
            // move and turn with glider
            gl.Translated(pos.x, pos.y, pos.z);
            gl.Rotated(pos.rz * 180 / Math.PI, 0, 0, 1);
            gl.Rotated(pos.ry * 180 / Math.PI, 0, 1, 0);
            gl.Rotated(pos.rx * 180 / Math.PI, 1, 0, 0);
            
            if (showGlide)
                Renderer.CallDisplayList("glide");

            if (showForce)
            {
                gl.Begin(GL.LINES);
                TestPrintForce();
                gl.End();
            }
            if (true)//(camera == 1)
            {
                // tisk textových informací do OpenGL
                gl.LoadIdentity();
                gl.BlendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
                gl.Color4f(1f, 1f, 1f, 1f);
                float fontSize = 0.1f;
                gl.Rotated(90, -1, 0, 0);
                
                // vykreslení levého horního rohu
                gl.Translated(-0.8, 0.7, 0.7);
                drawMessage(Math.Round(pos.z).ToString() + "m"); // výška 
                gl.Translated(0.05f, 0, -fontSize);
                drawMessage((Math.Round(pos.vz * 10) / 10).ToString() + "m/s"); // opadání  
                gl.Translated(0, 0, -fontSize);
                drawMessage((Math.Round((Math.Sqrt(Math.Pow(pos.vx, 2) + Math.Pow(pos.vy, 2))) * 10) / 10).ToString() + "m/s"); // dopředná rychlost

                // vykreslení stažení řidiček (levý a pravý dolní roh
                gl.Translated(-0.1, 0, -1.1);
                drawMessage(Math.Round(pos.leftB * 100).ToString() + "%"); // levá řidička
                gl.Translated(1.65, 0, 0);
                drawMessage(Math.Round(pos.rightB * 100).ToString() + "%"); // pravá řidička

                // speed systém 
                gl.Translated(0.02, 0, 0.4);
                // vykreslení stupnice
                for (int s = 0; s <= 40; s++)
                {
                    if (s % 10 == 0 || s < 40 * pos.speed)
                    {
                        gl.Translated(-0.04, 0, 0);
                        drawMessage("–"); // rozdělovník – nemazat!!!
                        gl.Translated(0.04, 0, 0);
                    }
                    else
                        drawMessage("-");
                    gl.Translated(0, 0, 0.0125);
                }

                // náklon sedačky
                gl.Translated(-1.2, 0, -0.9);
                drawMessage("("); // levá zarážka
                gl.Translated(0.44, 0, 0);
                drawMessage(")"); // pravá zarážka
                gl.Translated(-0.22 + 0.20 * pos.harness, 0, 0.004);
                drawMessage("o"); // pozice sedačky (náklon)
                gl.Translated(0, -0.4, 0.5);
                
                gl.LoadIdentity();
                gl.Rotated(90, -1, 0, 0);

                if (land)
                {
                    gl.Color4d(1, 1, 1, Math.Sin(land_alpha));
                    gl.Translated(-0.1, 0.3 * Math.Sin(land_pos) + 0.1, 0.1);
                    CallDisplayList("iconLOK");
                    land_alpha = Math.Min(Math.PI/2, land_alpha + 0.01);
                    land_pos = Math.Min(Math.PI / 2, land_pos + 0.01);
                }
                //gl.Disable(GL.DEPTH_TEST);
                gl.Disable(GL.BLEND);
            }
        }

        // Test terain contact
        private static bool Colision(double x, double y, double z)
        {
            // coordinates
            Int32 nx = (Int32)x / 100 + 50;
            Int32 ny = (Int32)y / 100 + 50;

            // get terrain height 
            double map_height = terrain_height[nx, ny];
            //Console.WriteLine("[" + nx + "," + ny + "] = " + map_height);
            
            // test collision
            if (map_height > 8 && z < map_height)
                return true;
            if (z < map_height)// && map_height > 9)
                return true;
            else
                return false;
        }

        // ***************** Rendering components **********************
        private static void SkyBox()
        {
            float tex_pos = (256f - 1f) / 256f;
            float tex_zero = 1f / 256f;
            float SB_height = 5000;
            float SB_size = 5000;

            gl.Enable(GL.TEXTURE_2D);

            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("SkyBox0"));
            gl.Begin(GL.QUADS);
            gl.Color3f(1f, 1f, 1f);
            // zadní díl
            gl.TexCoord2f(tex_pos, tex_pos); gl.Vertex3f(SB_size, -SB_size, 0f);
            gl.TexCoord2f(tex_zero, tex_pos); gl.Vertex3f(-SB_size, -SB_size, 0f);
            gl.TexCoord2f(tex_zero, tex_zero); gl.Vertex3f(-SB_size, -SB_size, SB_height);
            gl.TexCoord2f(tex_pos, tex_zero); gl.Vertex3f(SB_size, -SB_size, SB_height);
            gl.End();

            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("SkyBox1"));
            gl.Begin(GL.QUADS);
            // pravý díl
            gl.TexCoord2f(tex_zero, tex_zero); gl.Vertex3f(SB_size, SB_size, 0f);
            gl.TexCoord2f(tex_pos, tex_zero); gl.Vertex3f(SB_size, -SB_size, 0f);
            gl.TexCoord2f(tex_pos, tex_pos); gl.Vertex3f(SB_size, -SB_size, SB_height);
            gl.TexCoord2f(tex_zero, tex_pos); gl.Vertex3f(SB_size, SB_size, SB_height);
            gl.End();

            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("SkyBox2"));
            gl.Begin(GL.QUADS);
            gl.Color3f(1f, 1f, 1f);
            // přední díl
            gl.TexCoord2f(tex_zero, tex_zero); gl.Vertex3f(-SB_size, SB_size, 0f);
            gl.TexCoord2f(tex_pos, tex_zero); gl.Vertex3f(SB_size, SB_size, 0f);
            gl.TexCoord2f(tex_pos, tex_pos); gl.Vertex3f(SB_size, SB_size, SB_height);
            gl.TexCoord2f(tex_zero, tex_pos); gl.Vertex3f(-SB_size, SB_size, SB_height);
            gl.End();

            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("SkyBox3"));
            gl.Begin(GL.QUADS);
            // levý díl
            gl.TexCoord2f(tex_pos, tex_pos); gl.Vertex3f(-SB_size, -SB_size, 0f);
            gl.TexCoord2f(tex_zero, tex_pos); gl.Vertex3f(-SB_size, SB_size, 0f);
            gl.TexCoord2f(tex_zero, tex_zero); gl.Vertex3f(-SB_size, SB_size, SB_height);
            gl.TexCoord2f(tex_pos, tex_zero); gl.Vertex3f(-SB_size, -SB_size, SB_height);
            gl.End();

            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("SkyBox4"));
            gl.Begin(GL.QUADS);
            // vrchní díl
            gl.TexCoord2f(tex_zero, tex_zero); gl.Vertex3f(-SB_size, SB_size, SB_height);
            gl.TexCoord2f(tex_pos, tex_zero); gl.Vertex3f(SB_size, SB_size, SB_height);
            gl.TexCoord2f(tex_pos, tex_pos); gl.Vertex3f(SB_size, -SB_size, SB_height);
            gl.TexCoord2f(tex_zero, tex_pos); gl.Vertex3f(-SB_size, -SB_size, SB_height);
            gl.End();
            gl.Disable(GL.TEXTURE_2D);
        }
        public static void Terain() 
        {
            TerainGen flat = new TerainGen();
            gl.Enable(GL.TEXTURE_2D);
            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("gmaps"));
            gl.Begin(GL.TRIANGLES);
            //gl.Begin(GL.LINE_LOOP);
            //gl.Translated(0, 0, -2);
            //načteme útvar ze souboru
            flat.Load(Support.settings.basePath + "terain_flat.raw");
            flat.parseData();

            gl.End();
            gl.Disable(GL.TEXTURE_2D);
        }
        public static void Glider()
        {
            new ObjectGen("glider.obj");
        }
        public static void Letters(float size)
        {
            // čítač display listů
            int i = 4;
            // rozměr dílku textury
            double dil = 0.0625;
            // vytvoří pro každý znak obdélník se správnou texturou
            for (int cell = 0; cell < 16; cell++)
            {
                for (int row = 0; row < 16; row++) // radky
                {
                    displayLists.Add(gl.GenLists(1));
                    gl.NewList(displayLists[i], GL.COMPILE);
                    gl.Enable(GL.TEXTURE_2D);
                    gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("font2"));
                    gl.Begin(GL.QUADS);
                    gl.TexCoord2d(dil * row, -dil * cell);              gl.Vertex3f(0f, 0f, 0f);
                    gl.TexCoord2d(dil * (row + 1), -dil * cell);        gl.Vertex3f(size, 0f, 0f);
                    gl.TexCoord2d(dil * (row + 1), -dil * (cell + 1));  gl.Vertex3f(size, 0f, -size);
                    gl.TexCoord2d(dil * row, -dil * (cell + 1));        gl.Vertex3f(0f, 0f, -size);
                    gl.End();
                    gl.Disable(GL.TEXTURE_2D);

                    gl.EndList();

                    i++;
                }
            }
        }
        private static void LoadIcons()
        {
            // Landing OK
            displayLists.Add(gl.GenLists(1));
            gl.NewList(displayLists[3], GL.COMPILE);
            gl.Enable(GL.TEXTURE_2D);
            gl.BindTexture(GL.TEXTURE_2D, getHandleTexture("icons"));
            gl.Begin(GL.QUADS);
            gl.TexCoord2d(0, 0); gl.Vertex3f(0f, 0f, 0f);
            gl.TexCoord2d(0.15, 0); gl.Vertex3f(0.2f, 0f, 0f);
            gl.TexCoord2d(0.15, -1); gl.Vertex3f(0.2f, 0f, -0.104f);
            gl.TexCoord2d(0, -1); gl.Vertex3f(0f, 0f, -0.104f);
            gl.End();
            gl.Disable(GL.TEXTURE_2D);

            gl.EndList();
        }

        // ************ Loading, handling and releasing textures *****************
        private static void LoadTextures() 
        {
            // go through file textures
            string[] files = Directory.GetFiles(Support.settings.basePath + "textures/");
            foreach (string file in files)
            {
                // load texture
                textures.Add(CustomGL.LoadTexture(file));

                // save name
                texNames.Add(System.IO.Path.GetFileName(file));
            }
        }
        private static void ReleaseTextures() 
        {
            // all textures release at once
            textures.ForEach(delegate(uint name)
            {
                CustomGL.ReleaseTexture(ref name);
            });
        }
        private static uint getHandleTexture(string name) 
        {
            for (int i = 0; i < texNames.Count; i++)
            {
                string withoutExtension = System.IO.Path.GetFileNameWithoutExtension(texNames[i]);
                if (texNames[i] == name || withoutExtension == name)
                {
                    return textures[i];
                }
            }
            return 0;
        }

        // ************ Generating and deleting display lists ***********
        private static void GenerateDisplayLists() 
        {            
            // SkyBox
            displayLists.Add(gl.GenLists(1));
            gl.NewList(displayLists[0], GL.COMPILE);
            Renderer.SkyBox();
            gl.EndList();

            // Terain
            displayLists.Add(gl.GenLists(1));
            gl.NewList(displayLists[1], GL.COMPILE);
            Renderer.Terain();
            gl.EndList();

            // Glider
            displayLists.Add(gl.GenLists(1));
            gl.NewList(displayLists[2], GL.COMPILE);
            Renderer.Glider();
            gl.EndList();

            // vytvoření ikon
            LoadIcons();

            // vytvoření display listu pro každé písmeno s danou velikostí
            Letters(0.1f);
       }
        private static void TestPrintForce()
        {
            int i = 0;
            /*foreach (Force f in Physics.FPrint)
            {
                if (f.type == "suma")
                    // main direction
                    gl.Color3f(0.725f, 0.976f, 0.455f);
                else if (f.type == "moment")
                    // main direction
                    gl.Color3f(0.87f, 0.77f, 0.19f);
                else if (f.type == "tmodra")
                    // main direction
                    gl.Color3f(0f, 0.5f, 0.75f);
                else if (f.type == "lift")
                    // main direction
                    gl.Color3f(0.1f, 0.1f, 0.88f);
                else if (f.type == "drag")
                    // main direction
                    gl.Color3f(0.87f, 0.1f, 0.1f);
                else
                    // main direction
                    gl.Color3f(0.9f, 0f, 0f);
                
                CSML.Matrix coord = f.A;
                
                gl.Vertex3d(coord[1, 1].Re, coord[2, 1].Re, coord[3, 1].Re);
                coord = f.finalPoint();

                gl.Vertex3d(coord[1, 1].Re, coord[2, 1].Re, coord[3, 1].Re);
                i++;
            }*/
            gl.Color3f(0.87f, 0.1f, 0.1f);
            gl.Vertex3d(0, 0, 0);
            gl.Vertex3d(0, 0, -5);
               
        }
        private static void DeleteDisplayLists() 
        {
            // all display lists at once
            displayLists.ForEach(delegate(uint name)
            {
                gl.DeleteLists(name,1);
            });
        }
        public static uint CallDisplayList(string name) 
        {
            uint index = 0;
            if (name == "skybox")
                index = displayLists[0];
            if (name == "terain")
                index = displayLists[1];
            if (name == "glide")
                index = displayLists[2];
            if (name == "iconLOK")
                index = displayLists[3];

            gl.CallList(index);

            return index;
        }
        public static void drawMessage(string message)
        {
            float fontSize = 0.1f;
            int length = message.Length;
            float repeater = 0; // o kolik vrátit kurzor
            for (int i = length; i > 0; i--)
            {
                char letter = message[i-1];
                if (letter == '–')
                    letter = (char)151;
                float move = fontSize * widthLetter(letter);
                uint index = displayLists[4 + letter];

                gl.Translatef(fontSize - move, 0f, 0f);
                gl.CallList(index);
                gl.Translatef(-fontSize, 0f, 0f);
                repeater += move;
            }
            gl.Translatef(repeater, 0f, 0f);
        }
        
        // get normalized width of each letter (maximum is 1)
        private static float widthLetter(char letter)
        {
            switch (letter)
            {
                // extra narrow symbols
                case ',':
                case '.':
                case '/':
                    return 0.2f;
                // narrow symbols
                case '!':
                case '(':
                case ')': 
                case '-':
                case '1':
                case ':':
                case ';':
                    return 0.4f;
                // wide symbols
                case 'm':
                case 'M':
                    return 0.6f;
                // special symbols
                case '_':
                    return 0.36f;
                // other normal symbols
                default :
                    return 0.5f;            
            }
        }

        /// <summary>
        /// Generate object to OpenGL
        /// </summary>
        class ObjectGen 
        {
            public ObjectGen(string name)
            {
                // load data
                Import element = new Import("object/"+name);
                List<Support.bodyObj> geometry = element.Parse();

                // go through all bodies and print triangles and quads
                foreach (Support.bodyObj body in geometry)
                {
                    bool colorSet = false;

                    // initialize triangles
                    this.initOpenGLShape(body.texture, "triangles");
                    
                    // print all triangles
                    foreach (Support.trianglVect triangl in body.triangles)
                    {
                        if (!colorSet)
                        {
                            gl.Color3f(body.faceColor.red, body.faceColor.green, body.faceColor.blue);
                            colorSet = true;
                        }
                        gl.TexCoord2f(triangl.A.texX, triangl.A.texY);
                        gl.Vertex3f(triangl.A.coordX, triangl.A.coordY, triangl.A.coordZ);

                        gl.TexCoord2f(triangl.B.texX, triangl.B.texY);
                        gl.Vertex3f(triangl.B.coordX, triangl.B.coordY, triangl.B.coordZ);

                        gl.TexCoord2f(triangl.C.texX, triangl.C.texY);
                        gl.Vertex3f(triangl.C.coordX, triangl.C.coordY, triangl.C.coordZ);
                    }
                    this.endOpenGLShape();

                    // initialize and print all quads
                    if (body.quads.Count > 0)
                    {
                        this.initOpenGLShape(body.texture, "quads");

                        foreach (Support.quadsVect quad in body.quads)
                        {
                            gl.TexCoord2f(quad.A.texX, quad.A.texY);
                            gl.Vertex3f(quad.A.coordX, quad.A.coordY, quad.A.coordZ);

                            gl.TexCoord2f(quad.B.texX, quad.B.texY);
                            gl.Vertex3f(quad.B.coordX, quad.B.coordY, quad.B.coordZ);

                            gl.TexCoord2f(quad.C.texX, quad.C.texY);
                            gl.Vertex3f(quad.C.coordX, quad.C.coordY, quad.C.coordZ);

                            gl.TexCoord2f(quad.D.texX, quad.D.texY);
                            gl.Vertex3f(quad.D.coordX, quad.D.coordY, quad.D.coordZ);
                        }
                        this.endOpenGLShape();
                    }
                }
            }

            private void initOpenGLShape(string tex, string shape)
            {
                gl.Enable(GL.TEXTURE_2D);
                gl.BindTexture(GL.TEXTURE_2D, Renderer.getHandleTexture(tex));
                if (shape == "triangles")
                    gl.Begin(GL.TRIANGLES);
                else if (shape == "quads")
                    gl.Begin(GL.QUADS);
                else if (shape == "triangle_fan")
                    gl.Begin(GL.TRIANGLE_FAN);
            }
            private void endOpenGLShape()
            {
                gl.End();
                gl.Disable(GL.TEXTURE_2D);
            }
        }
        
        /// <summary>
        /// Terain generation functions, texturing and drawing objects
        /// </summary>
        class TerainGen 
        {
            protected string rawData;
            // Constructor
            public TerainGen()
            {

            }
            
            // Load triangles from file
            public void Load(string fileName)
            {
                // file load
                StreamReader streamReader = new StreamReader(fileName);
                rawData = streamReader.ReadToEnd();
                streamReader.Close();
            }
            
            // parse RAW file
            public void parseData()
            {
                // parse RAW file
                float barva;
                Array rows = rawData.Split('\n');
                foreach (string row in rows)
                {

                    Int32 pozice = row.IndexOf("Object");
                    if (pozice == -1 && row.Length > 1)
                    {
                        string trim_row = row.Trim();
                        trim_row = trim_row.Replace('.', ',');
                        Array cell = trim_row.Split(' ');
                        barva = float.Parse(cell.GetValue(2).ToString()) / 400;
                        //gl.Color3f(0.5f, barva, 0.5f);

                        float x = float.Parse(cell.GetValue(0).ToString());
                        float y = float.Parse(cell.GetValue(1).ToString());
                        gl.TexCoord2f((x + 5000) / 10000, (y + 5000) / 10000);

                        gl.Vertex3f(float.Parse(cell.GetValue(0).ToString()),
                                    float.Parse(cell.GetValue(1).ToString()),
                                    float.Parse(cell.GetValue(2).ToString()));
                        // ************************************
                        barva = float.Parse(cell.GetValue(5).ToString()) / 400;
                        //gl.Color3f(0.5f, barva, 0.5f);

                        x = float.Parse(cell.GetValue(3).ToString());
                        y = float.Parse(cell.GetValue(4).ToString());
                        gl.TexCoord2f((x + 5000) / 10000, (y + 5000) / 10000);

                        gl.Vertex3f(float.Parse(cell.GetValue(3).ToString()),
                                    float.Parse(cell.GetValue(4).ToString()),
                                    float.Parse(cell.GetValue(5).ToString()));
                        // ************************************
                        barva = float.Parse(cell.GetValue(8).ToString()) / 400;
                        //gl.Color3f(0.5f, barva, 0.5f);

                        x = float.Parse(cell.GetValue(6).ToString());
                        y = float.Parse(cell.GetValue(7).ToString());
                        gl.TexCoord2f((x + 5000) / 10000, (y + 5000) / 10000);

                        gl.Vertex3f(float.Parse(cell.GetValue(6).ToString()),
                                    float.Parse(cell.GetValue(7).ToString()),
                                    float.Parse(cell.GetValue(8).ToString()));
                    }
                }
            }
        }
    }
}
