// /home/epsilon/Projects/Asteroid/ABot/Framework1/GLUtils/GLScreen.cs created with MonoDevelop
// User: epsilon at 18:26 20.04.2008
//

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Tao.OpenGl;
using Tao.FreeGlut;

namespace GLUtils
{
	public class GLScreen
	{
		// fields
		//--------
		private const int theWidth = 640;
		private const int theHeight = 480;
		private const int theColorDepth = 16;
		private int theWindowId = -1;

		// textures
        private static int[] theTextureAddresses; // Array of IDs of used textures
        private static GLTexture[] theTextures;
		private static int theEmptyTextureAdress = -1;

		// objects
		private System.Collections.Generic.LinkedList<GLObject> theObjects
			= new LinkedList<GLObject>(); // objects to be drawn
		private Random rnd = new Random(); // needed to animate GLObjects
		

		// constructor
		public GLScreen()
		{
			LogOutput.LogDebug("GLScreen", "Creating GLScreen");	
		}
		
        /// <summary>
        /// Returns the address of the first texture with the given name.
        /// If the requested texture is not found, 0 is returned.
        /// </summary>
        /// <param name="textureName"></param>
        /// <returns></returns>
        public static int GetTextureAddress(string textureName)
        {
			if (textureName == "" || textureName == "empty")
				return theEmptyTextureAdress;
			
            int index = -1;

            if (theTextures != null)
            {
                // get index of given texture
                for (int i = 0; i < theTextures.Length; i++)
                {
                    if (textureName == theTextures[i].Name)
                        index = i;
                }
            }

            // return address of given texture
            if (index == -1)
                return -1;
            else
                return theTextureAddresses[index];
        }

        public static void LoadTextures(GLTexture[] textures)
        {
			// TODO: modify this routine to allow adding of textures

			// create empty texture for basic coloring
			//-----------------------------------------
			{
				LogOutput.LogDebug("GLScreen", "Creating empty texture");
				Gl.glGenTextures(1, out theEmptyTextureAdress);
				System.Drawing.Bitmap image = new Bitmap(128,128);

				// fill with white
				System.Drawing.Graphics graph = Graphics.FromImage(image);
				graph.FillRectangle(new SolidBrush(Color.White), 0, 0, image.Width, image.Height);
 
				System.Drawing.Imaging.BitmapData bitmapdata;
				System.Drawing.Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
				
				bitmapdata = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
			                            System.Drawing.Imaging.PixelFormat.Format24bppRgb);
				
				// load bitmap into texture memory space
				Gl.glBindTexture(Gl.GL_TEXTURE_2D, theEmptyTextureAdress);
				Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, (int)Gl.GL_RGB8, image.Width, image.Height,
				                0, Gl.GL_BGR_EXT, Gl.GL_UNSIGNED_BYTE, bitmapdata.Scan0);
				Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR); // Linear Filtering
				Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR); // Linear Filtering
				
				// kill image
				image.UnlockBits(bitmapdata);
				image.Dispose();
			}
			
			// create textures
			//-----------------
			LogOutput.LogDebug("GLScreen", "Loading " + textures.Length.ToString() + " textures");	
            theTextures = (GLTexture[])textures.Clone();
            theTextureAddresses = new int[theTextures.Length];
            Gl.glEnable(Gl.GL_TEXTURE_2D);

            // create textures
            Gl.glGenTextures(theTextureAddresses.Length, theTextureAddresses);
			LogOutput.LogDebug("GLScreen", "Texture1 index: " + theTextureAddresses[0]);	
			LogOutput.LogDebug("GLScreen", "Texture2 index: " + theTextureAddresses[1]);	

            for (int i = 0; i < theTextureAddresses.Length; i++)
            {
                // get image
                System.Drawing.Bitmap image = theTextures[i].Texture;

                image.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipY);
                System.Drawing.Imaging.BitmapData bitmapdata;
                System.Drawing.Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);

                bitmapdata = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
                    System.Drawing.Imaging.PixelFormat.Format24bppRgb);

                // load bitmap into texture memory space
                Gl.glBindTexture(Gl.GL_TEXTURE_2D, theTextureAddresses[i]);
                Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, (int)Gl.GL_RGB8, image.Width, image.Height,
                    0, Gl.GL_BGR_EXT, Gl.GL_UNSIGNED_BYTE, bitmapdata.Scan0);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR); // Linear Filtering
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR); // Linear Filtering

                // kill image
                image.UnlockBits(bitmapdata);
                image.Dispose();
            }
        }
		
		// if textures defined, use empty texture
		public static void UnbindTexture()
		{
			if (theEmptyTextureAdress != -1)
			{
				Gl.glBindTexture(Gl.GL_TEXTURE_2D, theEmptyTextureAdress);
                Gl.glColor3f(1.0f, 1.0f, 1.0f);
			}
		}

		// add object to draw list
        public void AddGLObject(GLObject obj)
        {
            theObjects.AddLast(obj);
        }

        // add object array to draw list
        public void AddGLObject(GLObject[] objects)
        {
            foreach (GLObject obj in objects)
            {
                theObjects.AddLast(obj);
            }
        }

        // remove object from draw list
        public void RemoveGLObject(GLObject obj)
        {
            theObjects.Remove(obj);
        }
		
		// clear object list
		public void RemoveAllGLObjects()
		{
			theObjects.Clear();
		}

		// create opengl window
		public void CreateScreen()
		{
			LogOutput.LogInfo("GLScreen", "Activating");

			LogOutput.LogDebug("GLScreen", "Initializing GLUT");
			Glut.glutInit();
			
			LogOutput.LogDebug("GLScreen", "Initializing display mode");
			Glut.glutInitDisplayMode(Glut.GLUT_DOUBLE | Glut.GLUT_RGBA | Glut.GLUT_DEPTH);

			LogOutput.LogDebug("GLScreen", "Initializing window size");
			Glut.glutInitWindowSize(640, 480);

			LogOutput.LogDebug("GLScreen", "Initializing window position");
			Glut.glutInitWindowPosition(0,0);

			LogOutput.LogDebug("GLScreen", "Creating window");
			theWindowId = Glut.glutCreateWindow("OpenGL");

			LogOutput.LogDebug("GLScreen", "Initializing OpenGL screen");
			if (theWindowId != -1)
				this.InitializeGl();

			LogOutput.LogDebug("GLScreen", "Registering display callback");
			if (theWindowId != -1)
				Glut.glutDisplayFunc(this.DrawScene);

			LogOutput.LogDebug("GLScreen", "Registering resizing callback");
			if (theWindowId != -1)
				Glut.glutReshapeFunc(this.ResizeScene);
		}
		
		public void ActivateScreen()
		{
			// thread will be locked here
			if (theWindowId != -1)
			{
				LogOutput.LogDebug("GLScreen", "Entering main loop");
				Glut.glutMainLoop();
			}
			else
				LogOutput.LogWarning("GLScreen", "No window active, cannot enter main loop");
		}
		
		public void Invalidate()
		{
			if (theWindowId != -1)
				Glut.glutPostRedisplay();
			else
				LogOutput.LogDebug("GLScreen", "No window active, cannot invalidate");
		}
		
		// resize screen
		private void ResizeScene(int width, int height)
		{
			LogOutput.LogDebug("GLScreen", "Resizing");

			if(height == 0)
				height = 1;
            
			double ratio = (double)width / (double)height;
            
			Gl.glViewport(0, 0, width, height);           // Den Viewport zurücksetzten
			Gl.glMatrixMode(Gl.GL_PROJECTION);            // Die Projektionsmatrix auswählen
			Gl.glLoadIdentity();                          // Die Projektionsmatrix zurücksetzen
			Glu.gluPerspective(45.0, ratio, 0.1, 100.0);  // DieEvents aus dem Eventqueue auselesen Aspect Ratio des Fensters festlegen
			Gl.glMatrixMode(Gl.GL_MODELVIEW);             // Auswählen der Modelview Matrix
			Gl.glLoadIdentity();                          // Die Projektionsmatrix zurücksetzen
		}
				

		// initialize OpenGL
		private void InitializeGl()
		{
            Gl.glCullFace(Gl.GL_BACK);

			Gl.glShadeModel(Gl.GL_SMOOTH);          // Shading Modell auf Smooth Shading festlegen
            Gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // init with black background
            
			Gl.glClearDepth(1);                     // Depth Buffer initialisieren
			Gl.glEnable(Gl.GL_DEPTH_TEST);          // Depth Test aktivieren
			Gl.glDepthFunc(Gl.GL_LEQUAL);           // Typ des Depth Test festlegen
			Gl.glEnable(Gl.GL_TEXTURE_2D);
            
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE);

			Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);        // Perpective Correction festlegen
		}
		

		// draw scene
		private void DrawScene()
		{
			//LogOutput.LogDebug("GLScreen", "Drawing scene");
			Gl.glLoadIdentity(); // reset projection matrix

            // enable write mode for depth buffer
            Gl.glDepthMask((byte)Gl.GL_TRUE);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer

            
            if (theObjects.Count > 0)
            {
                // draw background - use depth testing
                //-------------------------------------
				Gl.glEnable(Gl.GL_DEPTH_TEST);
                Gl.glDepthMask((byte)Gl.GL_TRUE);
                LinkedListNode<GLObject> obj = theObjects.First;
                do
                {
                    if (obj.Value.ObjectType==GLObject.ObjectTypes.Background)
                    {
                        obj.Value.Animate(rnd);
						if (obj.Value.IsVisible)
							obj.Value.Draw();
                    }

                    obj = obj.Next;
                } while (obj != null);


                // draw opaque objects - use depth testing
                //-----------------------------------------
				Gl.glEnable(Gl.GL_DEPTH_TEST);
                Gl.glDepthMask((byte)Gl.GL_TRUE);
                obj = theObjects.First;
                do
                {
                    if (obj.Value.ObjectType == GLObject.ObjectTypes.Normal
                        && !obj.Value.UseAlphaBlending)
                    {
                        obj.Value.Collide(theObjects);
                        obj.Value.Animate(rnd);
						if (obj.Value.IsVisible)
							obj.Value.Draw();
                    }

                    obj = obj.Next;
                } while (obj != null);


                // draw translucent objects - use depth testing in read only mode
                //----------------------------------------------------------------
				Gl.glEnable(Gl.GL_DEPTH_TEST);
                Gl.glDepthMask((byte)Gl.GL_FALSE);
                obj = theObjects.First;
                do
                {
                    if (obj.Value.ObjectType == GLObject.ObjectTypes.Normal
                        && obj.Value.UseAlphaBlending)
                    {
                        obj.Value.Collide(theObjects);
                        obj.Value.Animate(rnd);
						if (obj.Value.IsVisible)
							obj.Value.Draw();
                    }

                    obj = obj.Next;
                } while (obj != null);

                // Draw HUD - do not use depth testing
                //---------------------------------------------------
				Gl.glDisable(Gl.GL_DEPTH_TEST);
                obj = theObjects.First;
                do
                {
                    if (obj.Value.ObjectType == GLObject.ObjectTypes.HUD)
                    {
                        obj.Value.Animate(rnd);
						if (obj.Value.IsVisible)
							obj.Value.Draw();
                    }

                    obj = obj.Next;
                } while (obj != null);
            }
			
			
            Gl.glFlush();
			Glut.glutSwapBuffers();
		}
		
		public void Kill()
		{
			// TODO: delete texture data
			
			if (theWindowId == -1)
			{
				LogOutput.LogDebug("GLScreen", "No window active, cannot destroy");
			}
			else
			{
				LogOutput.LogDebug("GLScreen", "Destroying OpenGL window");
				Glut.glutDestroyWindow(theWindowId);
				theWindowId = -1;
			}
		}

	}
}
