/************************************************************************
* Copyright (c) 2005-2007 tok@openlinux.org.uk                          *
*                                                                       *
* This software is provided as-is, without any express or implied       *
* warranty. In no event will the authors be held liable for any         *
* damages arising from the use of this software.                        *
*                                                                       *
* Permission is granted to anyone to use this software for any purpose, *
* including commercial applications, and to alter it and redistribute   *
* it freely, subject to the following restrictions:                     *
*                                                                       *
* 1. The origin of this software must not be misrepresented; you must   *
* not claim that you wrote the original software. If you use this       *
* software in a product, an acknowledgment in the product documentation *
* would be appreciated but is not required.                             *
*                                                                       *
* 2. Altered source versions must be plainly marked as such, and must   *
* not be misrepresented as being the original software.                 *
*                                                                       *
* 3. This notice may not be removed or altered from any source          *
* distribution.                                                         *
************************************************************************/
#include <string>
#include "gl_screen.h"
#include "log.h"
#include "buffercache.h"
#include "m_exceptions.h"
#include "image_loader.h"

#include "OpenGTA-win/frgbridge.h"

//Xperimental -- Vladislav Khorev vladislav.khorev@fishrungames.com
namespace OpenGL {
#ifndef DEFAULT_SCREEN_WIDTH
#define DEFAULT_SCREEN_WIDTH (640*2)
#endif
#ifndef DEFAULT_SCREEN_HEIGHT
#define DEFAULT_SCREEN_HEIGHT (480*2)
#endif
#ifndef DEFAULT_SCREEN_VSYNC
#define DEFAULT_SCREEN_VSYNC 0
#endif

  Screen::Screen() {
    surface = NULL;
    videoFlags = defaultVideoFlags; 
    width = DEFAULT_SCREEN_WIDTH;
    height = DEFAULT_SCREEN_HEIGHT;
    bpp = 32;
    fieldOfView = 60.0f;
    nearPlane = 0.1f;
    farPlane = 250.0f;
    // 0: no vsync, 1: sdl, 2 native
    useVsync = DEFAULT_SCREEN_VSYNC;
  }

  void Screen::activate(Uint32 w, Uint32 h) {
    if (w)
      width = w;
    if (h)
      height = h;
    initSDL();
    resize(width, height);
    INFO << "activating screen: " << width << "x" << height << std::endl;
    initGL();
    setSystemMouseCursor(false);
  }

  void Screen::setupGlVars( float fov, float near_p, float far_p) {
    fieldOfView = fov;
    nearPlane = near_p;
    farPlane = far_p;
  }

  void Screen::setupVsync(size_t mode) {
    useVsync = mode;
  }

  void Screen::setSystemMouseCursor(bool visible) {
    SDL_ShowCursor((visible ? SDL_ENABLE : SDL_DISABLE));
  }

  Uint32 Screen::getWidth() {
    return width;
  }

  Uint32 Screen::getHeight() {
    return height;
  }

  bool Screen::getFullscreen() {
    return (videoFlags & SDL_WINDOW_FULLSCREEN);
  }

  void Screen::setFullScreenFlag(bool v) {
    if (v && getFullscreen())
      return;
    else if (!v && !getFullscreen())
      return;
    if (v)
      videoFlags |= SDL_WINDOW_FULLSCREEN;
    else
      videoFlags ^= SDL_WINDOW_FULLSCREEN;
  }

  Screen::~Screen() {
    setSystemMouseCursor(true);
    if (SDL_WasInit(SDL_INIT_VIDEO))
      SDL_Quit();
    surface = NULL;
  }

  void Screen::toggleFullscreen() {
    if (videoFlags & SDL_WINDOW_FULLSCREEN)
      videoFlags ^= SDL_WINDOW_FULLSCREEN;
    else
      videoFlags |= SDL_WINDOW_FULLSCREEN;
    resize(width, height);
  }

  void Screen::initSDL() {

	  //Xperimental -- Vladislav Khorev vladislav.khorev@fishrungames.com

	  size_t color_depth_triple[3];

	  for (int i = 0; i < 3; ++i)
		  color_depth_triple[i] = 8;


	  SDL_GL_SetAttribute(SDL_GL_RED_SIZE, color_depth_triple[0]);
	  SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, color_depth_triple[1]);
	  SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, color_depth_triple[2]);
	  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#ifdef HAVE_SDL_VSYNC
	  if (useVsync == 1) {
		  SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
		  INFO << "enabling vertical sync:" << " SDL" << std::endl;
	  }
#else
	  if (useVsync == 1)
		  WARN << "Cannot use SDL vsync - option disabled while compiling" << std::endl;
#endif


	  /*
    int err = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
    if (err)
      //throw "SDL_Init failed: " + std::string(SDL_GetError());
      throw E_INVALIDFORMAT("SDL_Init failed: " + std::string(SDL_GetError()));

    const char* sdl_err = SDL_GetError();
    if (strlen(sdl_err) > 0)
      WARN << "SDL_Init complained: " << sdl_err << std::endl;
    SDL_ClearError();

    const SDL_VideoInfo *vInfo = SDL_GetVideoInfo();

    if (vInfo == NULL)
      throw E_NOTSUPPORTED("SDL_GetVideoInfo failed: " + std::string(SDL_GetError()));

    if (vInfo->hw_available == 1)
      videoFlags |= SDL_HWSURFACE;
    else
      videoFlags |= SDL_SWSURFACE;

    if (vInfo->blit_hw)
      videoFlags |= SDL_HWACCEL;

    bpp = vInfo->vfmt->BitsPerPixel;
 
    INFO << "video-probe:" << std::endl <<
     " hw-surface: " << (vInfo->hw_available == 1 ? "on" : "off") << std::endl <<
     " hw-blit: " << (vInfo->blit_hw ? "on" : "off") << std::endl <<
     " bpp: " << int (bpp) << std::endl;

    size_t color_depth_triple[3];
    switch(bpp) {
      case 32:
      case 24:
        for (int i=0; i < 3; ++i)
          color_depth_triple[i] = 8;
        break;
      case 16:
        color_depth_triple[0] = 5;
        color_depth_triple[1] = 6;
        color_depth_triple[2] = 5;
        break;
      case 15:
        for (int i=0; i < 3; ++i)
          color_depth_triple[i] = 5;
        break;
      case 8:
        color_depth_triple[0] = 2;
        color_depth_triple[1] = 3;
        color_depth_triple[2] = 3;
        break;
      default:
        throw E_NOTSUPPORTED("Invalid bit-per-pixel setting");
    }

    SDL_GL_SetAttribute( SDL_GL_RED_SIZE,   color_depth_triple[0]);
    SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, color_depth_triple[1]);
    SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE,  color_depth_triple[2]);
    SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1);
#ifdef HAVE_SDL_VSYNC
    if (useVsync == 1) {
      SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1);
      INFO << "enabling vertical sync:" << " SDL" << std::endl;
    }
#else
    if (useVsync == 1)
      WARN << "Cannot use SDL vsync - option disabled while compiling" << std::endl;
#endif

    sdl_err = SDL_GetError();
    if (strlen(sdl_err) > 0)
      ERROR << "setting sdl_gl attributes: " << sdl_err << std::endl;
	  */
  }

  void Screen::initGL() {
    GL_CHECKERROR;
    if (useVsync == 2) {
#ifdef LINUX
      int (*fp)(int) = (int(*)(int)) SDL_GL_GetProcAddress("glXSwapIntervalMESA");
      if (fp) {
        fp(1);
        INFO << "enabling vertical sync:" << " GLX" << std::endl;
      }
      else
        ERROR << "No symbol 'glXSwapIntervalMESA' found - cannot use GLX vsync" << std::endl;
#else
      typedef void (APIENTRY * WGLSWAPINTERVALEXT) (int);
      WGLSWAPINTERVALEXT wglSwapIntervalEXT = 
        (WGLSWAPINTERVALEXT) wglGetProcAddress("wglSwapIntervalEXT");
      if (wglSwapIntervalEXT)
      {
        wglSwapIntervalEXT(1); // set vertical synchronisation
        INFO << "enabling vertical sync:" << " WGL" << std::endl;
      }
      else
        ERROR << "No symbol 'wglSwapIntervalEXT' found - cannot use WGL vsync" << std::endl;
#endif
    }
    
    GLfloat LightAmbient[]  = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat LightDiffuse[]  = { 0.0f, 0.0f, 0.0f, 1.0f };
    GLfloat LightPosition[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    
    //glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    glEnable( GL_DEPTH_TEST );
    
    glEnable( GL_LIGHTING );
	glEnable(GL_LIGHT0);
	//glDisable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	//glDisable(GL_LIGHT1);
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    glLightfv( GL_LIGHT0, GL_AMBIENT, LightAmbient );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, LightDiffuse );
    glLightfv( GL_LIGHT0, GL_POSITION, LightPosition );

	GLfloat LightAmbient2[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    

	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient2);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);

    
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);

	
	//glColorMaterial(GL_BACK, GL_AMBIENT_AND_DIFFUSE);
    glCullFace(GL_BACK);
    //glPolygonMode(GL_FRONT, GL_FILL);
    //glPolygonMode(GL_BACK, GL_LINE);
    glEnable(GL_TEXTURE_2D);

	GLfloat MaterialAmbient[] = { 0.0f, 0.0f, 1.0f, 1.0f };

	glEnable(GL_COLOR_MATERIAL);


	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, MaterialAmbient);
	

	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, MaterialAmbient);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, MaterialAmbient);
	glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, MaterialAmbient);


	//glEnable(GL_COLOR_MATERIAL);

    if (queryExtension("GL_EXT_texture_filter_anisotropic")) {
      GLfloat maxAniso = 1.0f;
      glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
      //if (maxAniso >= 2.0f)
      ImageUtil::supportedMaxAnisoDegree = maxAniso;
      INFO << "GL supports anisotropic filtering with degree: " << maxAniso << std::endl;
    }

    GL_CHECKERROR;
	
	GetShaderManager().AddShader("simple", "assets\\gl1SimpleVertexShader.txt", "assets\\gl1SimpleFragmentShader.txt");

	GetShaderManager().PushShader("simple");
  }

  void Screen::resize(Uint32 w, Uint32 h) {

	  //Xperimental -- Vladislav Khorev vladislav.khorev@fishrungames.com

	  if (h == 0)
		  h = 1;

	  surface = SDL_CreateWindow(
		  "OpenGTA", 40, 40, w, h,
		  SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);

	  // Create an OpenGL context associated with the window.
	  glcontext = SDL_GL_CreateContext(surface);

	  glViewport(0, 0, w, h);
	  width = w;
	  height = h;
	  GL_CHECKERROR;

	  /*
    if (h == 0)
      h = 1;
    surface = SDL_SetVideoMode(w, h, bpp, videoFlags);
    if (surface == NULL) {
      ERROR << "vide-mode: " << w << ", " << h << " bpp: " << bpp << 
      " hw-surface: " << (videoFlags & SDL_HWSURFACE == SDL_HWSURFACE ? "on" : "off") << 
      " hw-blit: " << (videoFlags & SDL_HWACCEL == SDL_HWACCEL ? "on" : "off") << std::endl;
      throw E_NOTSUPPORTED(SDL_GetError());
    }
	
    glViewport(0, 0, w, h);
    width = w;
    height = h;
    GL_CHECKERROR;*/
  }

  void Screen::set3DProjection() {
    float ratio = float(width) / float(height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective( fieldOfView, ratio, nearPlane, farPlane);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

  void Screen::setFlatProjection() {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

  void Screen::makeScreenshot(const char* filename) {
    INFO << "saving screen as: " << filename << std::endl;
    uint8_t *pixels = Util::BufferCacheHolder::Instance().requestBuffer(width * height * 3);

    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, reinterpret_cast<GLvoid*>(pixels));

    SDL_Surface* image = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 24,
        255U << (0),
        255U << (8),
        255U << (16),
        0);
    SDL_LockSurface(image);

    uint8_t *imagepixels = reinterpret_cast<uint8_t*>(image->pixels);
    for (int y = (height - 1); y >= 0; --y) {
      uint8_t *row_begin = pixels + y * width * 3;
      uint8_t *row_end = row_begin + width * 3;

      std::copy(row_begin, row_end, imagepixels);
      imagepixels += image->pitch;
    }
    SDL_UnlockSurface(image);
    SDL_SaveBMP(image, filename);
    SDL_FreeSurface( image );
  }

  GLboolean Screen::queryExtension(const char *extName) {
    // from the 'Red Book'
    char *p = (char *) glGetString(GL_EXTENSIONS);
    char *end = p + strlen(p); 
    while (p < end) {
      size_t n = strcspn(p, " ");
      if ((strlen(extName)==n) && (strncmp(extName,p,n)==0)) {
        return GL_TRUE;
      }
      p += (n + 1);
    }
    return GL_FALSE;
  }
}