/*     DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                    Version 2, December 2004

 Copyright (C) 2009 Adinpsz contributors
  7 chemin Villebois 38100 Grenoble
 Everyone is permitted to copy and distribute verbatim or modified
 copies of this license document, and changing it is allowed as long
 as the name is changed.

            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. You just DO WHAT THE FUCK YOU WANT TO. */
/* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */ 
module sdl.app;

import derelict.sdl.sdl;	
import derelict.opengl.gl, derelict.opengl.glu, derelict.opengl.gl20;
//import derelict.devil.il, derelict.devil.ilu;
import std.gc, sdl.state, std.stdio, std.string;
import util.common, util.framecounter, util.logger, util.geometry.all;
import gl.state;
public import derelict.opengl.gl : GLVersion;

enum FSAA { OFF, FSAA2X, FSAA4X, FSAA8X, FSAA16X };

class SDLApp
{

    protected
    {
	    bool m_fullscreen;
        bool m_finished;
        bool m_resizable;
        int m_width, m_height;
        
        SDL m_sdl = null;

        SDL_Surface* m_screen;
        FrameCounter m_frameCounter = null;
        
        int m_mousex, m_mousey, m_ancmousex, m_ancmousey;
        
    }
    
    private
    {	
	    static const GLint[FSAA.max + 1] FSAA_toInt = [-1,2,4,8,16];
	    int m_delayBetweenFrames;
    }

    public
    {
		auto const AUTO_DETECT = -1; // a utiliser a la place de width et height pour utiliser la taille courante de l'ecran

        this(int width,  // desired window width, could be AUTO_DETECT to match current resolution
             int height, // desired window height, could be AUTO_DETECT to match current resolution
             bool fullscreen, // fullscreen or not
             bool resizable, // resizeable or not
          //   bool border, // frame or not
             char[] initTitle, 
             char[] iconFile, // can be null
             FSAA fsaa, 
             int wantedFrameRate, // if wantedFrameRate == 0 then the frame counter will count frames to find the frame rate
             GLVersion requiredGLVersion,
             bool verticalSync,
             int delayBetweenFrames = 0) // in ms
        {
//	        trace(">SDLApp.this");
	        m_sdl = SDL.instance; // init SDL if required
            m_fullscreen = fullscreen; 
            m_resizable = resizable & (! fullscreen);
            
            
            SDL_VideoInfo* videoInfo = SDL_GetVideoInfo();
            
            m_width = (width == AUTO_DETECT) ? videoInfo.current_w : width;
            m_height = (height == AUTO_DETECT) ? videoInfo.current_h : height;
          
            uint flags = SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_OPENGL;
            if (resizable) flags |= SDL_RESIZABLE;
            if (fullscreen) flags |= SDL_FULLSCREEN;
         //   if (!border) flags |= SDL_NOFRAME;
            
            
            if (iconFile !is null)
            {
            	SDL_Surface* icon = SDL_LoadBMP(toCString(iconFile));
            	uint colorkey = SDL_MapRGB(icon.format, 255, 0, 255); // magenta is transparent
				SDL_SetColorKey(icon, SDL_SRCCOLORKEY, colorkey);   
				SDL_WM_SetIcon(icon, null);
			}
			
			
			SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
			SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
		//	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
			SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
			
			SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, verticalSync ? 1 : 0);

			if (fsaa != FSAA.OFF)
			{
				SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, FSAA_toInt[fsaa] );
				SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS,1);
			}

			debug(3) trace(">creating the window");
            // Create the screen surface (ie window)
            m_screen = SDL_SetVideoMode(m_width,
                                        m_height,
                                        SDL_GetVideoInfo().vfmt.BitsPerPixel,
                                        flags);
			if (m_screen is null) 
			{
            	char[] msg = format("Unable to create screen surface : %s", 
            	                    std.string.toString(SDL_GetError()));
				throw new SDLError(msg);			
			}
			debug(3) trace("<creating the window");
			
			SDL_EnableUNICODE(1);
            
			title = initTitle;                                      

			debug(3) trace(">init openGL");
            DerelictGL.load();
            DerelictGL.loadVersions(requiredGLVersion);
            DerelictGL.loadExtensions();
            
            DerelictGLU.load();
            debug(3) trace("<init openGL");
            GL.check();
            
            // bug with my LCD
    /*        if (fsaa != FSAA.OFF) glEnable(GL_MULTISAMPLE);
            else glDisable(GL_MULTISAMPLE);
      */    GL.check();
  	    
		    if (wantedFrameRate > 0)
		    {
			    m_frameCounter = new FixedFrameCounter(wantedFrameRate);
			}
		    else 
		    {
			    m_frameCounter = new VariableFrameCounter(1.0);
			}
            m_finished = false;   
            m_delayBetweenFrames = delayBetweenFrames;         			
            debug(3) trace("<SDLApp.this");
        }
        
		~this()
		{
			debug(3) trace(">SDLApp.~this()");			
			debug(3) trace("<SDLApp.~this()");
		}
        
        final int width()
        {
	     	return m_width;   
        }
        
        final int height()
        {
	     	return m_height;   
        }        

        final char[] title(char[] s)
        {
            SDL_WM_SetCaption(toCString(s), null);
            return s;
        }

        final void terminate()
        {
            m_finished = true;
        }
        
        final void swapBuffers()
        {
	        SDL_GL_SwapBuffers();
        }
        
        final void frame(bool checkEvents)
        {
	        debug(3) trace(">SDLApp.frame()");
	        if (checkEvents) processEvents();
	        
	        if (!m_finished)
	        {		
		        if (m_frameCounter.tick())
	            {
	            	onFrameRateChanged(m_frameCounter.frameRate);                    
	        	}
	        		
		        final double time = m_frameCounter.elapsedTime;
		
		       	onMove(time, m_frameCounter.deltaTime);		       	
	        	onRender(time);
	
	           
        	}  
        	debug(3) trace("<SDLApp.frame()");      	
        }

        final void run()
        {
	        onRun();
	        
	        m_frameCounter.reset();	        
	        
	        debug(3) trace(">SDLApp.run()");
 			while (! m_finished)
            {
	            frame(true);
	            if (m_delayBetweenFrames > 0) SDL_Delay(5);
            }            
            debug(3) trace("<SDLApp.run()");
        }


        final void processEvents()
        {
			SDL_Event event;
            while (SDL_PollEvent(&event))
            {
                switch (event.type)
                {
                    // handle keyboard

	                case SDL_KEYUP:
	                {
	                    SDLKey key = event.key.keysym.sym;
		                SDLMod mod = event.key.keysym.mod;
		                wchar ch = event.key.keysym.unicode;
	                    SDL.instance.keyboard.markAsReleased( key );
	                    onKeyUp( key, mod, ch );
	                    break;
                    }
	
	                case SDL_KEYDOWN:
	                {
		                SDLKey key = event.key.keysym.sym;
		                SDLMod mod = event.key.keysym.mod;
		                wchar ch = event.key.keysym.unicode;
	                    SDL.instance.keyboard.markAsPressed( key );
	                    onKeyDown( key, mod, ch );
	                    break;	                    
                    }
                    
                    // handle mouse
	
	                case SDL_MOUSEMOTION:
	                	m_ancmousex = m_mousex;
	                    m_ancmousey = m_mousey;
	                    m_mousex = event.motion.x;
	                    m_mousey = event.motion.y;	                   
	                    
	                    onMouseMove(m_mousex, m_mousey, m_mousex - m_ancmousex, m_mousey - m_ancmousey);
	                    break;
	                    
					case SDL_MOUSEBUTTONDOWN:
						onMouseDown(event.button.button);
	                    break;
	                    
					case SDL_MOUSEBUTTONUP:
						onMouseUp(event.button.button);
						break;
						
					case SDL_JOYAXISMOTION:
						int joy_index = event.jaxis.which;
						int axis_index = event.jaxis.axis;
						float value = event.jaxis.value / cast(float)(short.max);
						SDL.instance.joystick(joy_index).setAxis(axis_index, value);
						break;
					
					case SDL_JOYBUTTONUP:	
					case SDL_JOYBUTTONDOWN:
					{
						int joy_index = event.jbutton.which;
						int button_index = event.jbutton.button;
						bool value = (event.jbutton.state == SDL_PRESSED);
						SDL.instance.joystick(joy_index).setButton(button_index, value);
						break;
					}
	
	                case SDL_QUIT:
	                    terminate();
	                    break;
	                    
					case SDL_VIDEORESIZE:
						m_width = event.resize.w;
						m_height = event.resize.h;
						onReshape(event.resize.w,event.resize.h);	
						break;
						
					default:
	                    break;
            	}
            }
        }

        // events handlers
        
        abstract void onRender(double elapsedTime);
        abstract void onMove(double elapsedTime, double dt);
        abstract void onKeyUp(int key, int mod, wchar ch);        
        abstract void onKeyDown(int key, int mod, wchar ch);                
        
        abstract void onMouseMove(int x, int y, int dx, int dy);
        abstract void onMouseDown(int button);
        abstract void onMouseUp(int button);        
        abstract void onReshape(int width, int height);
        abstract void onFrameRateChanged(float frameRate);
        
        abstract void onRun(); 
        
        
        final vec2i mousePos()
        {
	     	return vec2i(m_mousex , m_mousey);
        }
        
        final float FPS()
        {
	    	return m_frameCounter.frameRate();
        }
        
     
    }
}


