/* Mednafen - Multi-system Emulator
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <string.h>
#include <stdlib.h>

#include "main.h"
#include "opengl.h"

#define GL_GLEXT_LEGACY
#ifdef HAVE_APPLE_OPENGL_FRAMEWORK
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glu.h>
#endif

#ifndef APIENTRY
#define APIENTRY
#endif

static SDL_Surface *gl_screen;
static GLuint textures[3]={0, 0, 0}; // emulated fb, scanlines, osd
static GLuint rgb_mask;

static int using_scanlines = 0;
static int last_w, last_h;

extern uint32 uppow2(uint32);

static bool needglclear = 0;
static bool OSDTextureCreated;

void BlitOpenGLOSD(uint32 *OSDBuffer)
{
  int multiplierx = gl_screen->w / 256;
  int multipliery = gl_screen->h / 224;
  int multiplier;

  multiplier = multiplierx;
  if(multipliery < multiplier)
   multiplier = multipliery;

  glBindTexture(GL_TEXTURE_2D, textures[2]);
  glPixelStorei(GL_UNPACK_ROW_LENGTH, 256);
  if(!OSDTextureCreated)
  {
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
   OSDTextureCreated = 1;
  }
  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 224, GL_RGBA, GL_UNSIGNED_BYTE, OSDBuffer);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  if(!multiplier || ((double)gl_screen->w / (256 * multiplier) >= 1.75) || ((double)gl_screen->h / (224 * multiplier) >= 1.75) )
  {
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

   glBegin(GL_QUADS);
   glTexCoord2f(0.0f, 1.0f * 224 / 256);  // Bottom left of our picture.
   glVertex2f(0, gl_screen->h);

   glTexCoord2f(1.0f, 1.0f * 224 / 256); // Bottom right of our picture.
   glVertex2f(gl_screen->w, gl_screen->h);

   glTexCoord2f(1.0f, 0.0f);    // Top right of our picture.
   glVertex2f(gl_screen->w,  0);

   glTexCoord2f(0.0f, 0.0f);     // Top left of our picture.
   glVertex2f(0, 0);
  }
  else
  {
   int dest_left = (gl_screen->w - (256 * multiplier)) / 2;
   int dest_right = dest_left + 256 * multiplier;
   int dest_top = (gl_screen->h - (224 * multiplier)) / 2;
   int dest_bottom = dest_top + 224 * multiplier;

   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

   glBegin(GL_QUADS);
   glTexCoord2f(0.0f, 1.0f * 224 / 256);  // Bottom left of our picture.
   glVertex2f(dest_left, dest_bottom);

   glTexCoord2f(1.0f, 1.0f * 224 / 256); // Bottom right of our picture.
   glVertex2f(dest_right, dest_bottom);

   glTexCoord2f(1.0f, 0.0f);    // Top right of our picture.
   glVertex2f(dest_right,  dest_top);

   glTexCoord2f(0.0f, 0.0f);     // Top left of our picture.
   glVertex2f(dest_left, dest_top);
  }
  glEnd();
  glDisable(GL_BLEND);

  needglclear = 1;
}

void BlitOpenGL(SDL_Surface *src_surface, const SDL_Rect *src_rect, const SDL_Rect *dest_rect, const SDL_Rect *original_src_rect)
{
 int left = src_rect->x;
 int right = src_rect->x + src_rect->w;	// First nonincluded pixel
 int top = src_rect->y;
 int bottom = src_rect->y + src_rect->h; // ditto

 //int sl_left = original_src_rect->x;
 //int sl_right = original_src_rect->x + original_src_rect->w; // First nonincluded pixel
 int sl_top = original_src_rect->y;
 int sl_bottom = original_src_rect->y + original_src_rect->h; // ditto

 if(needglclear)
 {
  needglclear = 0;
  glClear(GL_COLOR_BUFFER_BIT);  
 }

 glBindTexture(GL_TEXTURE_2D, textures[0]);
 glPixelStorei(GL_UNPACK_ROW_LENGTH, src_surface->pitch >> 2);

 int tmpwidth = uppow2(src_rect->w + src_rect->x);
 uint32 *src_pixies = (uint32 *)src_surface->pixels;

 if(src_rect->w == uppow2(src_rect->w)) // Specifically for PC Engine games
 {
  tmpwidth = src_rect->w;
  src_pixies += left;
  right -= left;
  left = 0;
  //printf("%d\n",tmpwidth);
 }

 if(last_w != tmpwidth || last_h != src_surface->h)
 {
  //puts("Redo");
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tmpwidth, src_surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, src_pixies); 
  last_w = tmpwidth;
  last_h = src_surface->h;
 }
 else
 {
  src_pixies += left + top * (src_surface->pitch >> 2);
  glTexSubImage2D(GL_TEXTURE_2D, 0, left, top, right - left, bottom - top, GL_RGBA, GL_UNSIGNED_BYTE, src_pixies);
 }
 glBegin(GL_QUADS);

 if(CurGame)
 {
  if(CurGame->rotated)
  {
   if(CurGame->rotated == 1)
   {
    glTexCoord2f(1.0f*left / tmpwidth, 1.0f * src_rect->y / src_surface->h);
     glVertex2f(dest_rect->x, dest_rect->y + dest_rect->h);
    glTexCoord2f(1.0f*left / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
     glVertex2f(dest_rect->x + dest_rect->w, dest_rect->y + dest_rect->h);
    glTexCoord2f(1.0f * right / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
     glVertex2f(dest_rect->x + dest_rect->w,  dest_rect->y);
    glTexCoord2f(1.0f * right / tmpwidth, 1.0f * src_rect->y / src_surface->h);
     glVertex2f(dest_rect->x,  dest_rect->y);
   }
   else
   {
    glTexCoord2f(1.0f * right / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
     glVertex2f(dest_rect->x, dest_rect->y + dest_rect->h);
    glTexCoord2f(1.0f * right / tmpwidth, 1.0f * src_rect->y / src_surface->h);
     glVertex2f(dest_rect->x + dest_rect->w, dest_rect->y + dest_rect->h);
    glTexCoord2f(1.0f*left / tmpwidth, 1.0f * src_rect->y / src_surface->h);
     glVertex2f(dest_rect->x + dest_rect->w,  dest_rect->y);
    glTexCoord2f(1.0f*left / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
     glVertex2f(dest_rect->x,  dest_rect->y);
   }
  }
  else
  {
   glTexCoord2f(1.0f*left / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
    glVertex2f(dest_rect->x, dest_rect->y + dest_rect->h);

   glTexCoord2f(1.0f * right / tmpwidth, (1.0f * src_rect->h + src_rect->y) / src_surface->h);
    glVertex2f(dest_rect->x + dest_rect->w, dest_rect->y + dest_rect->h);

   glTexCoord2f(1.0f * right / tmpwidth, 1.0f * src_rect->y / src_surface->h);
    glVertex2f(dest_rect->x + dest_rect->w,  dest_rect->y);

   glTexCoord2f(1.0f*left / tmpwidth, 1.0f * src_rect->y / src_surface->h);
    glVertex2f(dest_rect->x,  dest_rect->y);
  }
 }
 glEnd();

 if(using_scanlines)
 {
  glEnable(GL_BLEND);

  glBindTexture(GL_TEXTURE_2D, textures[1]);
  glBlendFunc(GL_DST_COLOR, GL_SRC_ALPHA);

  glBegin(GL_QUADS);

  glTexCoord2f(0.0f, 1.0f * sl_bottom/256);  // Bottom left of our picture.
  glVertex2f(dest_rect->x, dest_rect->y + dest_rect->h);

  glTexCoord2f(1.0f, 1.0f * sl_bottom/256); // Bottom right of our picture.
  glVertex2f(dest_rect->x + dest_rect->w, dest_rect->y + dest_rect->h);

  glTexCoord2f(1.0f, 1.0f * sl_top / 256);    // Top right of our picture.
  glVertex2f(dest_rect->x + dest_rect->w,  dest_rect->y);

  glTexCoord2f(0.0f, 1.0f * sl_top / 256);     // Top left of our picture.
  glVertex2f(dest_rect->x,  dest_rect->y);

  glEnd();
  glDisable(GL_BLEND);
 }
}

void FlipOpenGL(void)
{
 glFinish();
 PumpWrap();
 SDL_GL_SwapBuffers();
}

void KillOpenGL(void)
{
 if(textures[0])
  glDeleteTextures(3, &textures[0]);
 textures[0] = textures[1] = textures[2] = 0;
}
/* Rectangle, left, right(not inclusive), top, bottom(not inclusive). */

int InitOpenGL(int ipolate, int scanlines, SDL_Surface *screen)
{
 const char *extensions;

 gl_screen = screen;

 extensions=(const char*)glGetString(GL_EXTENSIONS);

 glViewport(0, 0, screen->w, screen->h);

 glGenTextures(3, &textures[0]);
 glGenTextures(1, &rgb_mask);
 using_scanlines = 0;

 if(scanlines)	// Check for scanlines, and disable them if vertical scaling isn't large enough.
 {
  int slcount;
  uint8 *buf;
  int x,y;

  using_scanlines = scanlines;

  glBindTexture(GL_TEXTURE_2D, textures[1]);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

  buf=(uint8*)malloc(64 * (256 * 2) * 4);

  slcount = 0;
  for(y=0;y<(256 * 2);y++)
  {
   for(x=0;x<64;x++)
   {
    buf[y*64*4+x*4]=0;
    buf[y*64*4+x*4+1]=0;
    buf[y*64*4+x*4+2]=0;
    buf[y*64*4+x*4+3]=slcount?0xFF:0xFF - (0xFF / (scanlines));
    //buf[y*256+x]=(y&1)?0x00:0xFF;
   }
   slcount ^= 1;
  }

  glPixelStorei(GL_UNPACK_ROW_LENGTH, 64);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 256 * 2, 0, GL_RGBA,GL_UNSIGNED_BYTE,buf);
  free(buf);
 }

 glBindTexture(GL_TEXTURE_2D, textures[0]);
     
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,ipolate?GL_LINEAR:GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,ipolate?GL_LINEAR:GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

 glBindTexture(GL_TEXTURE_2D, textures[2]);

 // Hmm...on my card, if I create the texture here(even when I specify non-NULL pixel data),
 // there is an annoying flash of white when the OSD texture is shown, so delay it
 // until right before the glTexImage2D() call.
 OSDTextureCreated = 0; 
 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

 glEnable(GL_TEXTURE_2D);
 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);	// Background color to black.
 glMatrixMode(GL_MODELVIEW);

 //p_glViewport(0, 0, screen->w, screen->h);
 //p_glClear(GL_COLOR_BUFFER_BIT);
 glLoadIdentity();
 glFinish();

 glDisable(GL_TEXTURE_1D);
 glDisable(GL_FOG);
 glDisable(GL_LIGHTING);
 glDisable(GL_LOGIC_OP);
 glDisable(GL_DITHER);
 glDisable(GL_COLOR_MATERIAL);
 glDisable(GL_NORMALIZE);
 glDisable(GL_SCISSOR_TEST);
 glDisable(GL_STENCIL_TEST);
 glDisable(GL_ALPHA_TEST);
 glDisable(GL_DEPTH_TEST);

 glPixelTransferf(GL_RED_BIAS, 0);
 glPixelTransferf(GL_GREEN_BIAS, 0);
 glPixelTransferf(GL_BLUE_BIAS, 0);
 glPixelTransferf(GL_ALPHA_BIAS, 0);

 glPixelTransferf(GL_RED_SCALE, 1);
 glPixelTransferf(GL_GREEN_SCALE, 1);
 glPixelTransferf(GL_BLUE_SCALE, 1);
 glPixelTransferf(GL_ALPHA_SCALE, 1);

 glPixelTransferf(GL_MAP_COLOR, GL_FALSE);

 glOrtho(0.0, screen->w, screen->h, 0.0, -1.0, 1.0);

 last_w = 0;
 last_h = 0;

 return(1);
}

void ClearGLSurfaces(SDL_Surface *screen)
{
 int x;
 for(x=0;x<2;x++)
 {
  glClear(GL_COLOR_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
  glFinish();
  SDL_GL_SwapBuffers();
 }
}

