//+--------------------------------------------------------------------------+
//|                                                                          |
//|                                Planet3D                                  |
//|                              Version 1.02                                |
//|                                                                          |
//|      OpenGL project using the GDImage flat API to create on the fly      |
//|     planet texture from files with smooth shading and ambient light.     |
//|                                                                          |
//|          The GDImage.dll unregistered version has no limitation,         |
//|             and all features of the API are fully functional.            |
//|                                                                          |
//|       Buying a commercial license removes the copyright nag screen.      |
//|                                                                          |
//+--------------------------------------------------------------------------+
//|                                                                          |
//|                         Author Patrice TERRIER                           |
//|                           copyright (c) 2006                             |
//|                                                                          |
//|                        pterrier@zapsolution.com                          |
//|                                                                          |
//|                          www.zapsolution.com                             |
//|                                                                          |
//+--------------------------------------------------------------------------+
//|                  Project started on : 10-26-2006 (MM-DD-YYYY)            |
//|                        Last revised : 11-14-2006 (MM-DD-YYYY)            |
//+--------------------------------------------------------------------------+

using System;
//using System.Collections.Generic;
//using System.ComponentModel;
//using System.Data;
using System.Drawing;
//using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;
//using System.Runtime;
//using System.IO;
//using Microsoft.Win32;

using Win32;
using GDImage;
using OpenGL;
using SkinEngine;

namespace Planet3D
{
    //Version 1.01
    //___________________________________________________________
    public delegate int CallBack(IntPtr hWnd, int wMsg, uint wParam, uint lParam);
    //___________________________________________________________

    public partial class MAIN_Form : Form
    {
        const int GLOBE = 0;                           // GLOBE texture
        const int STARS = 1;                           // STARS texture
        const int ID_CTRL  = 100;                      // GDImage Zoom control
        const int ID_GLOBE = 101;
        const int ID_STARS = 102;
        const int TIMER_MAX = 2;                       // To perform smooth animation

        static GI.ZGLTEXTURE[] mt = new GI.ZGLTEXTURE[2];

        static public IntPtr hFORM_Main = IntPtr.Zero; // Default to Zero
        static public IntPtr hGLcontrol = IntPtr.Zero; // Default to Zero

        static GI.ZGLFONT UseFont = new GI.ZGLFONT();
        static bool Animate = false;

        static int WasZoom = 0;
        static bool Zooming = false;

        //Version 1.01
        //____________________________________________________________
        //static float grDistance, grSpinX, grSpinY, grYincr, grXincr;
        [DllImport("USER32", EntryPoint = "SetWindowLongA")]
        public static extern int SetCallBack(IntPtr hWnd, int nIndex, CallBack lNewLong);
        static CallBack UseCallBack = null;
        static int GLwasX, GLwasY;
        static bool bMousing;
        static int ptLastMousePositX, ptLastMousePositY, ptCurrentMousePositX, ptCurrentMousePositY;
        static int ghGLproc;                             // OpenGL subclassing
        //____________________________________________________________

        static float grSpinY, grSpinX;
        static float grYincr, grXincr;

        public MAIN_Form()
        {
            InitializeComponent();

            // Create the OpenGL control
            hGLcontrol = GI.CreateGLcontrol(GL_Template, ID_CTRL);

            hFORM_Main = this.Handle;
            StartAnimation();

        }

        private void FORM_Load(object sender, EventArgs e)
        {
            // Set default image folder
            GI.LoadSavePath(Api.ExePath(), true);

            // We need this to retrieve bitmap from resource
            SK.UseNameSpace = this.GetType().Namespace;

            //Version 1.02
            //____________________________________________________________
            //// Use GetPixel(0, 0) for transparent color
            //SK.UseTransparencyColorTopLeft = true;
            //// Create buttons region and init button (to show enabled/disabled mode)
            //SK.CreateButtonRegion(BTN_Up);
            //SK.CreateButtonRegion(BTN_Left); 
            //SK.CreateButtonRegion(BTN_Right);
            //SK.CreateButtonRegion(BTN_Down);
            //SK.CreateButtonRegion(BTN_Reset);
            //SK.CreateButtonRegion(BTN_Pause);
            //SK.CreateButtonRegion(BTN_SelectFile);
            //SK.CreateButtonRegion(BTN_Start);
            //____________________________________________________________

            if (hGLcontrol != IntPtr.Zero)
            {
                //Version 1.01
                //______________________________________________________________
                UseCallBack = new CallBack(glWndProc);
                ghGLproc = SetCallBack(hGLcontrol, Api.GWL_WNDPROC, UseCallBack);
                //______________________________________________________________

                OnOff.Checked = true;

                // Do not fit the texture into a square shape
                GI.DoNotSquareTexture();

                // This is our background image texture
                mt[GLOBE].FullName = Api.ExePath() + "Earth.jpg"; mt[GLOBE].ID = ID_GLOBE;
                // This is our default moving sprite texture
                mt[STARS].FullName = Api.ExePath() + "StarsNight.jpg"; mt[STARS].ID = ID_STARS;

                // Assign textures to mt[]
                SetGenTextures(2);

                // I am using a temporary ZGLTEXTURE structure 
                // to protect mt[] from being erased during 
                // call to CreateMultipleGLTextureFrom32Bit.
                GI.ZGLTEXTURE[] temp = new GI.ZGLTEXTURE[mt.Length];
                for (int k = 0; k < mt.Length; k++) { temp[k] = mt[k]; }
                if (GI.CreateMultipleGLtextures(ref temp, mt.Length) == 0)
                {
                    ResetGlobe();

                    float[] LightAmbient = { 0.05f, 0.05f, 0.05f, 1.0f };
                    float[] LightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
                    float[] LightPosition = { 20.0f, 0.0f, 30.0f, 1.0f };
                    GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, LightAmbient);   // Setup The Ambient Light
                    GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, LightDiffuse);   // Setup The Diffuse Light
                    GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, LightPosition); // Position The Light

                    GL.glShadeModel(GL.GL_SMOOTH); // Enable Smooth Shading
                    GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST); // Do nicest perspective
                    GL.glEnable(GL.GL_COLOR_MATERIAL); // Enable Coloring Of Material	

                    GL.glEnable(GL.GL_TEXTURE_2D);

                    UseFont.fontName = "Arial";       // A True Type font
                    UseFont.fontHeight = 10;          // Size of the font
                    UseFont.fontWeight = Api.FW_BOLD; // Font style
                    GI.BuildGLfont(GI.GetGLDC(hGLcontrol), ref UseFont);

                    // When using the GDImage file selector
                    // start looking for pictures into the EXE folder.
                    GI.LoadSavePath(Api.ExePath(), true);
                }
            }
        }

        private void SetGenTextures(int n)
        {
            int[] textures = new int[n];
            GL.glGenTextures(n, textures);
            for (int k = 0; k < mt.Length; k++) { mt[k].Texture = textures[k]; }
        }

        private void StartAnimation()
        {
            // Use API WM_TIMER to avoid aggressive garbage collection 
            // to occur before Main ends.
            for (int T = 0; T < TIMER_MAX; T++)
            {
                Api.SetTimer(hFORM_Main, T + 1, 0, null);
            }
        }

        private void SetZoom(int zoom)
        {
            GI.SetGLzoom(hGLcontrol, zoom);
            GI.ResizeGLWindow(hGLcontrol);
        }

        private void UpdateZoom(int z)
        {
            SetZoom(z); RenderOpenGL(hGLcontrol);
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case Api.WM_TIMER:
                    RenderOpenGL(hGLcontrol);
                    m.Result = (IntPtr)0;
                    return;

                case Api.WM_COMMAND:
                    //  Zooming part
                    if (m.LParam == ZOOM_Track.Handle)
                    {// So much easier than trying to raise an event
                        // Warning, we are using polar coordinates there
                        // thus convert it to Degree
                        if (Zooming == false)
                        {
                            Zooming = true;
                            double range = ZOOM_Track.Maximum - ZOOM_Track.Minimum;
                            double increment = range / 90.0f;
                            int ZoomIs = (int)Math.Max(range - (ZOOM_Track.Value * increment), 1);

                            if (WasZoom < ZoomIs)
                            { for (int z = WasZoom; z < ZoomIs + 1; z++) UpdateZoom(z); }
                            else if (WasZoom > ZoomIs)
                            { for (int z = WasZoom; z > ZoomIs - 1; z--) UpdateZoom(z); }

                            WasZoom = ZoomIs;
                            Zooming = false;
                        }
                    }
                    break;

                case Api.WM_DESTROY:
                    DisposeGL(); // Free up OpenGL resources
                    break;
            }

            base.WndProc(ref m);

            if (m.Msg == Api.WM_COMMAND) Api.SetFocus(m.HWnd);            
        }

        //private void Form_KeyDown(object sender, KeyEventArgs e)
        //{
        //    bool Eat = false;
        //    switch ((int)e.KeyCode)
        //    {
        //        case Api.VK_LEFT:
        //        case Api.VK_RIGHT:
        //        case Api.VK_UP:
        //        case Api.VK_DOWN:
        //        case Api.VK_PGUP:
        //        case Api.VK_PGDN:
        //            Eat = true;
        //            break;
        //        default: break;
        //    }
        //    e.Handled = Eat;
        //}

        private void Form_PreviewKeydown(object sender, PreviewKeyDownEventArgs e)
        {
            int ZoomIs;
            switch ((int)e.KeyCode)
            {
                case Api.VK_LEFT: grXincr -= 0.01f; Animate = true; break;
                case Api.VK_RIGHT: grXincr += 0.01f; Animate = true; break;
                case Api.VK_UP: grYincr -= 0.01f; Animate = true; break;
                case Api.VK_DOWN: grYincr += 0.01f; Animate = true; break;
                case Api.VK_PGUP:
                    ZoomIs = GI.GetGLzoom(hGLcontrol);
                    if (ZoomIs > 1)
                    {
                        GI.SetGLzoom(hGLcontrol, ZoomIs - 1);
                        GI.ResizeGLWindow(hGLcontrol);
                    }
                    Animate = true;
                    break;
                case Api.VK_PGDN:
                    ZoomIs = GI.GetGLzoom(hGLcontrol);
                    if (ZoomIs < 100)
                    {
                        GI.SetGLzoom(hGLcontrol, ZoomIs + 1);
                        GI.ResizeGLWindow(hGLcontrol);
                    }
                    Animate = true;
                    break;
            }
        }

        // Free up resources
        private void DisposeGL()
        {
            for (int T = 0; T < TIMER_MAX; T++)
            {
                Api.KillTimer(hFORM_Main, T + 1);
            }
            // Delete OpenGL font.
            GI.DeleteGLFont(ref UseFont);
        }

        private void RenderOpenGL(IntPtr hContainer)
        {
            // Save the active planet zoom, 
            // then switch to a fixed zoom for the stellar background
            int ZoomIs = GI.GetGLzoom(hGLcontrol);
            SetZoom(45);

            GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            GL.glLoadIdentity();

            // ***********************
            // Draw the Sky background
            // ***********************
            GL.glDisable(GL.GL_LIGHT0);         // Disable Light ZERO
            GL.glDisable(GL.GL_LIGHTING);       // Disable Lighting
            GL.glDisable(GL.GL_COLOR_MATERIAL); // Disable Coloring Of Material
            GL.glBindTexture(GL.GL_TEXTURE_2D, mt[STARS].Texture);

            // Disable Depth Test
            GL.glDisable(GL.GL_DEPTH_TEST);
            
            GL.glBegin(GL.GL_QUADS);
            GL.glTexCoord2f(0.0f, 0.0f); GL.glVertex3f(-60.0f, -50.0f, -80.0f); // Bottom left of texture and quad
            GL.glTexCoord2f(1.0f, 0.0f); GL.glVertex3f(60.0f, -50.0f, -80.0f); // Bottom right
            GL.glTexCoord2f(1.0f, 1.0f); GL.glVertex3f(60.0f, 50.0f, -80.0f); // Top right
            GL.glTexCoord2f(0.0f, 1.0f); GL.glVertex3f(-60.0f, 50.0f, -80.0f); // Top left
            GL.glEnd();
            // ***********************

            // Restore the previously saved Zoom to display the planet
            SetZoom(ZoomIs);

            // ***********************
            // Draw the Globe texture
            // ***********************
            GL.glBindTexture(GL.GL_TEXTURE_2D, mt[0].Texture);

            // Enable Depth Test
            GL.glEnable(GL.GL_DEPTH_TEST);

            GL.glEnable(GL.GL_LIGHT0);          // Enable Light ZERO
            GL.glEnable(GL.GL_LIGHTING);        // Enable Lighting
            GL.glEnable(GL.GL_COLOR_MATERIAL);  // Enable Coloring Of Material

            GL.glPushMatrix();
            double Radius = 4.5;
            double Twist = 0.0;
            GI.UseGLPolarView(Radius, Twist, grSpinY, grSpinX);
            int quadObj = GL.gluNewQuadric(); // Pointer to the Quadric Object (Return 0 If No Memory))
            if (quadObj != 0)
            {
                GL.gluQuadricNormals(quadObj, GL.GLU_SMOOTH);  // Create Smooth Normals
                GL.gluQuadricTexture(quadObj, GL.GL_TRUE);     // Create Texture Coords
                GL.gluSphere(quadObj, 1.5, 48, 48);
                //GL.gluCylinder(quadObj, 2, 2, 1.5, 48, 48);

                GL.gluDeleteQuadric(quadObj);
            }
            GL.glPopMatrix();
            // ***********************

            // ***********************
            // Display coordinates
            // ***********************
            if (OnOff.Checked)
            {
                int UseColor = Api.ARGB(255, 128, 0, 0);
                int V = (int)grSpinY % 360;
                int H = Math.Abs((int)grSpinX % 360); if (H > 180) H = 360 - H;
                string MyStr = "Longitude " + H.ToString();
                GI.DrawGLText(hContainer, ref UseFont, 10, 10, MyStr, UseColor);
                int aLat = Math.Abs(V); V = 90 - (aLat % 90); if ((aLat % 180) > 90) V = (aLat % 180) - 90;
                MyStr = "Latitude " + V.ToString();
                GI.DrawGLText(hContainer, ref UseFont, 22, 10 + UseFont.fontHeight, MyStr, UseColor);
                MyStr = "Zoom " + ((int)ZOOM_Track.Value).ToString();
                GI.DrawGLText(hContainer, ref UseFont, 39, 10 + UseFont.fontHeight * 2, MyStr, UseColor);
            }
            // ***********************

            // ***********************
            // Draw the scene
            // ***********************
            GI.UpdateGLWindow(hContainer);
            // ***********************

            if (Animate)
            {
                grSpinY += grYincr;
                grSpinX += grXincr;
            }
        }

        private void ResetGlobe()
        {
            grSpinY = 75;
            grSpinX = 0;
            grYincr = 0f;
            grXincr = 0f;
            ZOOM_Track.Value = WasZoom = 50;
        }

        // ****************************************
        private void FORM_Close(object sender, EventArgs e)
        {
        }

        //***********************************************************
        // We use the .NET template to resize the GDImage control
        private void FORM_Resize(object sender, EventArgs e)
        {
            // Use the template to resize the GDImage OpenGL control
            Api.MoveWindow(hGLcontrol, GL_Template.Location.X, GL_Template.Location.Y, GL_Template.Width, GL_Template.Height, true);
            GI.ResizeGLWindow(hGLcontrol);
            RenderOpenGL(hGLcontrol);
        }

        private void BTN_Start_Click(object sender, EventArgs e)
        {
            Animate = true;
            grXincr = 0.05f;
        }

        private void BTN_Select_Click(object sender, EventArgs e)
        {
            string sFileName = GI.LoadDialog(hFORM_Main);
            if (sFileName != null)
            {
                GI.DoNotSquareTexture();
                int NamedTexture = mt[GLOBE].Texture;
                GI.UpdateNamedGLTextureFromFile(sFileName, NamedTexture);
            }
        }

        private void BTN_Pause_Click(object sender, EventArgs e)
        {
            Animate = false;
        }

        private void BTN_Reset_Click(object sender, EventArgs e)
        {
            Animate = false;
            ResetGlobe();
            GI.SetGLzoom(hGLcontrol, 0); GI.ResizeGLWindow(hGLcontrol);
            RenderOpenGL(hGLcontrol);

        }

        private void BTN_Up_Click(object sender, EventArgs e)
        {
            Animate = true; grYincr -= 0.05f;
        }

        private void BTN_Down_Click(object sender, EventArgs e)
        {
            Animate = true; grYincr += 0.05f;
        }

        private void BTN_Left_Click(object sender, EventArgs e)
        {
            Animate = true; grXincr -= 0.05f;
        }

        private void BTN_Right_Click(object sender, EventArgs e)
        {
            Animate = true; grXincr += 0.05f;
        }
        
        //private void BTN_MouseEnter(object sender, EventArgs e)
        //{
        //    Button btn = ((Button)sender);
        //    SK.USE_BTN_Image(btn, 5);
        //}

        //private void BTN_MouseLeave(object sender, EventArgs e)
        //{
        //    Button btn = ((Button)sender);
        //    SK.USE_BTN_Image(btn, 1);
        //}

        //private void BTN_MouseUp(object sender, MouseEventArgs e)
        //{
        //    Button btn = ((Button)sender);
        //    SK.USE_BTN_Image(btn, 1);
        //}

        //private void BTN_MouseDown(object sender, MouseEventArgs e)
        //{
        //    Button btn = ((Button)sender);
        //    SK.USE_BTN_Image(btn, 2);
        //}

        //private void BTN_EnabledChanged(object sender, EventArgs e)
        //{
        //    Button btn = ((Button)sender);
        //    SK.InitButton(btn);
        //}

        //Version 1.01
        //_________________________________________________________________________
        public static int glWndProc(IntPtr hWnd, int wMsg, uint wParam, uint lParam)
        {
            int x, y;
            switch (wMsg)
            {
                case Api.WM_LBUTTONDOWN:
                    if (bMousing == false)
                    {
                        x = Api.LoWrd(lParam); y = Api.HiWrd(lParam);
                        ptLastMousePositX = x;
                        ptCurrentMousePositX = x;
                        ptLastMousePositY = y;
                        ptCurrentMousePositY = y;
                        bMousing = true;
                    }
                    if (Api.GetFocus() != hWnd) Api.SetFocus(hWnd);
                    break;
                case Api.WM_LBUTTONUP:
                    bMousing = false;
                    break;
                case Api.WM_MOUSEMOVE:
                    x = Api.LoWrd(lParam); y = Api.HiWrd(lParam);
                    if (GI.IsLButtonDown() == false)
                    {
                        bMousing = false;
                    }
                    else
                    {
                        if ((GLwasX != x) || (GLwasY != y))
                        {
                            ptCurrentMousePositX = x;
                            ptCurrentMousePositY = y;
                            if (bMousing)
                            {
                                grSpinX += (ptCurrentMousePositX - ptLastMousePositX);
                                grSpinY -= (ptCurrentMousePositY - ptLastMousePositY);
                            }
                            ptLastMousePositX = ptCurrentMousePositX;
                            ptLastMousePositY = ptCurrentMousePositY;
                        }
                        GLwasX = x; GLwasY = y;
                    }
                    break;
            }
            return Api.CallWindowProc(ghGLproc, hWnd, wMsg, wParam, lParam);
        }

        //_________________________________________________________________________

    }
}