// Author: Juan Carlos De Abreu (jcabreur@gmail.com)

#include "OpenGLRenderer.h"

#include "../Includes/OpenGL.h"
#include "../Base/LogManagement.h"

#include "../Camera/Camera.h"
#include "../Scene/scene.h"
#include "../Scene/scene_inl.h"

#include "../gui/window_management.h"

BEGIN_TEG_NAMESPACE();

OpenGLRenderer::OpenGLRenderer(HWND _hWnd, bool _bFullscreen) { 
  bFullscreen = _bFullscreen;

  hDC = NULL;
  hRC = NULL;
  hWnd = _hWnd;

  int bits = 32;
		
  GLuint PixelFormat;			// Holds The Results After Searching For A Match

  // Attempt Fullscreen Mode?
  if (_bFullscreen) {
    ShowCursor(FALSE);

    DEVMODE dmScreenSettings;								// Device Mode
    memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));	// Makes Sure Memory's Cleared
    dmScreenSettings.dmSize = sizeof(dmScreenSettings);		// Size Of The Devmode Structure
    dmScreenSettings.dmPelsWidth	= WIDTH_SCREEN;			// Selected Screen Width
    dmScreenSettings.dmPelsHeight	= HEIGHT_SCREEN;		// Selected Screen Height
    dmScreenSettings.dmBitsPerPel	= bits;					// Selected Bits Per Pixel
    dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

    // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
    if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
      // If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
      PRINT_LOG(TYPE_ERROR, _T("Error: The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Will be use Windowed Mode!"));
      _bFullscreen = FALSE;		// Windowed Mode Selected.

     // Pop Up A Message Box Letting User Know The Program Is Closing.
     //	PRINT_LOG(_T("Error: Program Will Now Close.\n"));
     //	return FALSE;
    }
  }
	
  static PIXELFORMATDESCRIPTOR pfd = {        // pfd Tells Windows How We Want Things To Be.
      sizeof(PIXELFORMATDESCRIPTOR),          // Size Of This Pixel Format Descriptor.
      1,                                      // Version Number.
      PFD_DRAW_TO_WINDOW |                    // Format Must Support Window.
      PFD_SUPPORT_OPENGL |                    // Format Must Support OpenGL.
      PFD_DOUBLEBUFFER,                       // Must Support Double Buffering.
      PFD_TYPE_RGBA,                          // Request An RGBA Format.
      bits,                                   // Select Our Color Depth.
      0, 0, 0, 0, 0, 0,                       // Color Bits Ignored.
      0,                                      // No Alpha Buffer.
      0,                                      // Shift Bit Ignored.
      0,                                      // No Accumulation Buffer.
      0, 0, 0, 0,                             // Accumulation Bits Ignored.
      16,                                     // 16Bit Z-Buffer (Depth Buffer).
      1,                                      // Number of bits for the Stencil Buffer.
      0,                                      // No Auxiliary Buffer.
      PFD_MAIN_PLANE,                         // Main Drawing Layer.
      0,                                      // Reserved.
      0, 0, 0                                 // Layer Masks Ignored.
  };

  // Did We Get A Device Context?
  if (!(hDC = GetDC(hWnd))) {
	PRINT_LOG(TYPE_ERROR, _T("Error: Can't Create A GL Device Context."));
  }

  // Did Windows Find A Matching Pixel Format?
  if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) {
    PRINT_LOG(TYPE_ERROR, _T("Error: Can't Find A Suitable PixelFormat."));
  }

  // Are We Able To Set The Pixel Format?
  if (!SetPixelFormat(hDC, PixelFormat, &pfd)) {
    PRINT_LOG(TYPE_ERROR, _T("Error: Can't Set The PixelFormat."));
  }

  // Are We Able To Get A Rendering Context?
  if (!(hRC=wglCreateContext(hDC))) {
    PRINT_LOG(TYPE_ERROR, _T("Error: Can't Create A GL Rendering Context."));
  }

  // Try To Activate The Rendering Context
  if (!wglMakeCurrent(hDC,hRC)) {
    PRINT_LOG(TYPE_ERROR, _T("Error: Can't Activate The GL Rendering Context."));
  }

  glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
  glClearColor(1, 0, 0, 0.5f);						// Black Background
  glClearDepth(1.0f);									// Depth Buffer Setup
  glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
  glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glEnable(GL_DEPTH_TEST);
	
  //glClearColor(0,0,0,1);
  glClearColor(0,0,0,0.5);

  glEnable(GL_NORMALIZE); 
  glShadeModel(GL_SMOOTH);

  glClearDepth(1.0f);									// Depth Buffer Setup
  glClearStencil(0);									// Stencil Buffer Setup
  glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
  glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
  glCullFace(GL_BACK);								// Set Culling Face To Back Face
  glEnable(GL_CULL_FACE);								// Enable Culling
  glClearColor(0,0,0,1);
}

OpenGLRenderer::~OpenGLRenderer() { 
  // Are we in fullscreen mode?
  if (bFullscreen) {
    ChangeDisplaySettings(NULL, 0);		// If so switch back to the desktop
  }
  ShowCursor(TRUE);					// Show mouse pointer

  // Do we have a rendering context?
  if (hRC) {
    // Are we able to release the DC and RC contexts?
    if (!wglMakeCurrent(NULL, NULL))
      PRINT_LOG(TYPE_ERROR, _T("Shutdown Error: Release Of DC And RC Failed."));

    // Are we able to delete the RC?
    if (!wglDeleteContext(hRC))
      PRINT_LOG(TYPE_ERROR, _T("Shutdown Error: Release Rendering Context Failed."));
	
    hRC = NULL;			// Set RC to NULL
  }

  // Are we able to release the DC
  if (hDC && !ReleaseDC(hWnd, hDC)) {
    PRINT_LOG(TYPE_ERROR, _T("Shutdown Error: Release Device Context Failed."));
    hDC = NULL;			// Set DC To NULL
  }

  // Are we able to destroy the window?
  if (hWnd && !DestroyWindow(hWnd)) {
    PRINT_LOG(TYPE_ERROR, _T("Shutdown Error: Could Not Release hWnd."));
    hWnd = NULL;		// Set hWnd To NULL
  }
}

void OpenGLRenderer::render() { 
  Scene::GetInstance().camera()->CalculateTime();

  POINT mousePos; GetCursorPos(&mousePos);
  MOUSE_X = mousePos.x;
  MOUSE_Y = mousePos.y;

  setProjection();	

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glDepthFunc(GL_LEQUAL);

  if (AppWindow::GetInstance().gameState == AppWindow::GAME_RUN) {
	GLfloat fogColor[4]= {0.5f, 0.5f, 0.5f, 1.0f};		// Fog Color
	GLuint fogMode[]= { GL_EXP, GL_EXP2, GL_LINEAR };	// Storage For Three Types Of Fog

	glFogi(GL_FOG_MODE, fogMode[2]);		// Fog Mode
	glFogfv(GL_FOG_COLOR, fogColor);			// Set Fog Color
	glFogf(GL_FOG_DENSITY, 0.2f);				// How Dense Will The Fog Be
	glHint(GL_FOG_HINT, GL_DONT_CARE);			// Fog Hint Value
	glFogf(GL_FOG_START, 1.0f);				// Fog Start Depth
	glFogf(GL_FOG_END, 1.7f);			// Fog End Depth
	glEnable(GL_FOG);					// Enables GL_FOG
  } else {
	glDisable(GL_FOG);					// Enables GL_FOG
  }

	glDrawBuffer(GL_BACK);

  Scene::GetInstance().Render();
	
	glReadBuffer(GL_BACK);
	glAccum(GL_LOAD, 0.2);
	glReadBuffer(GL_FRONT);
	glAccum(GL_ACCUM, 0.8);
	glAccum(GL_RETURN, 1.0f);

  glDepthFunc(GL_LESS);

  // Render the windowManagement.
  glDepthFunc(GL_LESS);	
  gui::WindowManagement* pWManagement = gui::WindowManagement::GetInstancePtr();
  pWManagement->render();
 

  glFlush();
  // Swap Buffers (Double Buffering)
  SwapBuffers(hDC);
}

void OpenGLRenderer::setProjection() {
  // Prevent A Divide By Zero By.
  if (HEIGHT_SCREEN == 0) HEIGHT_SCREEN = 1;

  // Reset the current viewport.
  glViewport(0, 0, WIDTH_SCREEN, HEIGHT_SCREEN);

  // Select and reset the projection matrix.
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // Calculate The Aspect Ratio Of The Window.
  const float ratio = 1.0f * WIDTH_SCREEN / HEIGHT_SCREEN;
  gluPerspective(45.f, ratio, 0.01f, 100.f);

  // Select and reset the Modelview matrix.
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void OpenGLRenderer::resize(int width, int height) {
  // Update the window size and set up the projection.
  WIDTH_SCREEN = width;	HEIGHT_SCREEN = height;
  setProjection();
}

END_TEG_NAMESPACE();
