/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * 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 <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "sdl.h"
#include "sdl-opengl.h"
#include "../common/vidblit.h"

#include "sdl-icon.h"
#include "dface.h"

SDL_Surface *screen;
SDL_Surface *BlitBuf;		// Used as a buffer when using hardware-accelerated blits.
SDL_Surface *IconSurface=NULL;

static int curbpp;
static int srendline,erendline;
static int tlines;
static int inited=0;

#ifdef OPENGL
extern int sdlhaveogl;
static int usingogl;
static double exs,eys;
#else
static double exs,eys;
#endif
static int eefx;

#define NWIDTH	(256-((eoptions&EO_CLIPSIDES)?16:0))
#define NOFFSET	(eoptions&EO_CLIPSIDES?8:0)


static int paletterefresh;

/* Return 1 if video was killed, 0 otherwise(video wasn't initialized). */
int KillVideo(void)
{
 if(IconSurface)
 {
  SDL_FreeSurface(IconSurface);
  IconSurface=0;
 }

 if(inited&1)
 {
  #ifdef OPENGL
  if(usingogl)
   KillOpenGL();
  else
  #endif
  if(curbpp>8)
   KillBlitToHigh();
  SDL_QuitSubSystem(SDL_INIT_VIDEO);
  inited&=~1;
  return(1);
 }
 inited=0;
 return(0);
}

static int sponge;

int InitVideo(FCEUGI *gi)
{
 const SDL_VideoInfo *vinf;
 int flags=0;

 FCEUI_printf("Initializing video...");

 FCEUI_GetCurrentVidSystem(&srendline,&erendline);

 if(_fullscreen) sponge=Settings.specialfs;
 else sponge=Settings.special;


 #ifdef OPENGL
 usingogl=0;
 if(_opengl && sdlhaveogl && !sponge)
 {
  flags=SDL_OPENGL;
  usingogl=1;
 }
 #endif

 #ifdef EXTGUI
 GUI_SetVideo(_fullscreen, 0, 0);
 #endif

 if(!(SDL_WasInit(SDL_INIT_VIDEO)&SDL_INIT_VIDEO))
  if(SDL_InitSubSystem(SDL_INIT_VIDEO)==-1)
  {
   FCEUD_PrintError(SDL_GetError());
   return(0);
  }
 inited|=1;

 SDL_ShowCursor(0);
 tlines=erendline-srendline+1;

 vinf=SDL_GetVideoInfo();

 if(vinf->hw_available)
  flags|=SDL_HWSURFACE;

 if(_fullscreen)
  flags|=SDL_FULLSCREEN;

 flags|=SDL_HWPALETTE;

 //flags|=SDL_DOUBLEBUF;
 #ifdef OPENGL
 if(usingogl)
 {
  FCEU_printf("\n Initializing with OpenGL(Use \"-opengl 0\" to disable).\n");
  if(_doublebuf)
   SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
 }
 else
 #endif
  if(_doublebuf && (flags&SDL_HWSURFACE))
   flags|=SDL_DOUBLEBUF;

 if(_fullscreen)
 {
  int desbpp=_bpp;

  exs=_xscalefs;
  eys=_yscalefs;
  eefx=_efxfs;

  #ifdef OPENGL
  if(!usingogl) {exs=(int)exs;eys=(int)eys;}
  else desbpp=0;

  if(sponge)
  {
   exs=eys=2;
   if(sponge == 3 || sponge == 4) exs = eys = 3;
   eefx=0;
   if(sponge == 1 || sponge == 3) desbpp = 32;
  }


  if( (usingogl && !_stretchx) || !usingogl)
  #endif
   if(_xres<NWIDTH*exs || exs <= 0.01)
   {
    FCEUD_PrintError("xscale out of bounds.");
    KillVideo();
    return(0);
   }

  #ifdef OPENGL
  if( (usingogl && !_stretchy) || !usingogl)
  #endif
   if(_yres<tlines*eys || eys <= 0.01)
   {
    FCEUD_PrintError("yscale out of bounds.");
    KillVideo();
    return(0);
   }

  #ifdef EXTGUI
  GUI_SetVideo(_fullscreen, _xres, _yres);
  #endif

  #ifdef OPENGL
  if(!(screen = SDL_SetVideoMode(_xres, _yres, desbpp, flags)))
  #else
  if(!(screen = SDL_SetVideoMode(_xres, _yres, desbpp, flags)))
  #endif
  {
   FCEUD_PrintError(SDL_GetError());
   return(0);
  }
 }
 else
 {
  int desbpp=0;

  exs=_xscale;
  eys=_yscale;
  eefx=_efx;

  if(sponge) 
  {
   exs=eys=2;
   if(sponge >= 3) exs=eys=3;
   eefx=0;
   // SDL's 32bpp->16bpp code is slighty faster than mine, at least :/
   if(sponge == 1 || sponge == 3) desbpp=32;
  }

  #ifdef OPENGL
  if(!usingogl) {exs=(int)exs;eys=(int)eys;}
  if(exs <= 0.01) 
  {
   FCEUD_PrintError("xscale out of bounds.");
   KillVideo();
   return(0);
  }
  if(eys <= 0.01)
  {
   FCEUD_PrintError("yscale out of bounds.");
   KillVideo();
   return(0);
  }
  #endif

  #ifdef EXTGUI
  GUI_SetVideo(_fullscreen, (NWIDTH*exs), tlines*eys);
  #endif

  screen = SDL_SetVideoMode((int)(NWIDTH*exs), (int)(tlines*eys),
                            desbpp, flags);
 }
 curbpp=screen->format->BitsPerPixel;
 if(!screen)
 {
  FCEUD_PrintError(SDL_GetError());
  KillVideo();
  return(0);
 }
 //BlitBuf=SDL_CreateRGBSurface(SDL_HWSURFACE,256,240,screen->format->BitsPerPixel,screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,0);

 inited=1;

 FCEU_printf(" Video Mode: %d x %d x %d bpp %s\n",screen->w,screen->h,screen->format->BitsPerPixel,_fullscreen?"full screen":"");
 if(curbpp!=16 && curbpp!=24 && curbpp!=8 && curbpp!=32)
 {
  FCEU_printf("  Sorry, %dbpp modes are not supported by FCE Ultra.  Supported bit depths are 8bpp, 16bpp, and 32bpp.\n",curbpp);
  KillVideo();
  return(0);
 }

 if(gi->name)
  SDL_WM_SetCaption((const char *)gi->name, (const char *)gi->name);
 else
  SDL_WM_SetCaption("FCE Ultra","FCE Ultra");

 #ifdef LSB_FIRST
 IconSurface=SDL_CreateRGBSurfaceFrom((void *)fceu_playicon.pixel_data,32,32,24,32*3,0xFF,0xFF00,0xFF0000,0x00);
 #else
 IconSurface=SDL_CreateRGBSurfaceFrom((void *)fceu_playicon.pixel_data,32,32,24,32*3,0xFF0000,0xFF00,0xFF,0x00);
 #endif

 SDL_WM_SetIcon(IconSurface,0);

 paletterefresh=1;

 if(curbpp>8)
 #ifdef OPENGL
  if(!usingogl)
 #endif
  InitBlitToHigh(curbpp>>3,screen->format->Rmask,screen->format->Gmask,screen->format->Bmask,eefx,sponge);
 #ifdef OPENGL
 if(usingogl)
  if(!InitOpenGL((eoptions&EO_CLIPSIDES)?8:0,256-((eoptions&EO_CLIPSIDES)?8:0),srendline,erendline+1,exs,eys,eefx,_openglip,_stretchx,_stretchy,screen))
  {
   FCEUD_PrintError("Error initializing OpenGL.");
   KillVideo();
   return(0);
  }
 #endif
 return 1;
}

void ToggleFS(void)
{
 extern FCEUGI *CurGame;
 KillVideo();
 _fullscreen=!_fullscreen;

 if(!InitVideo(CurGame))
 {
  _fullscreen=!_fullscreen;
  InitVideo(CurGame);
 }
}

static SDL_Color psdl[256];
void FCEUD_SetPalette(uint8 index, uint8 r, uint8 g, uint8 b)
{
 psdl[index].r=r;
 psdl[index].g=g;
 psdl[index].b=b;

 paletterefresh=1;
}

void FCEUD_GetPalette(uint8 index, uint8 *r, uint8 *g, uint8 *b)
{
 *r=psdl[index].r;
 *g=psdl[index].g;
 *b=psdl[index].b;
}

static void RedoPalette(void)
{
 #ifdef OPENGL
 if(usingogl)
  SetOpenGLPalette((uint8*)psdl);
 else
 #endif
 {
  if(curbpp>8)
   SetPaletteBlitToHigh((uint8*)psdl); 
  else
  {
   SDL_SetPalette(screen,SDL_PHYSPAL,psdl,0,256);
  }
 }
}

void LockConsole(){}
void UnlockConsole(){}
void BlitScreen(uint8 *XBuf)
{
 SDL_Surface *TmpScreen;
 uint8 *dest;
 int xo=0,yo=0;

 if(!screen) return;

 if(paletterefresh)
 {
  RedoPalette();
  paletterefresh=0;
 }

 #ifdef OPENGL
 if(usingogl)
 {
  BlitOpenGL(XBuf);
  return;
 }
 #endif

 XBuf+=srendline*256;

 if(BlitBuf) TmpScreen=BlitBuf;
 else TmpScreen=screen;

 if(SDL_MUSTLOCK(TmpScreen))
  if(SDL_LockSurface(TmpScreen))
  {   
   return;
  }

 dest=(uint8*)TmpScreen->pixels;

 if(_fullscreen)
 {
  xo=(int)(((TmpScreen->w-NWIDTH*exs))/2);
  dest+=xo*(curbpp>>3);
  if(TmpScreen->h>(tlines*eys))
  {
   yo=(int)((TmpScreen->h-tlines*eys)/2);
   dest+=yo*TmpScreen->pitch;
  }
 }

 if(curbpp>8)
 {
     if(BlitBuf) {
         Blit8ToHigh(XBuf+NOFFSET,dest, NWIDTH, tlines, TmpScreen->pitch,1,1);
     } else {
         Blit8ToHigh(XBuf+NOFFSET,dest, NWIDTH, tlines, TmpScreen->pitch,
                     (int)exs,(int)eys);
     }
 }
 else
 {
     if(BlitBuf) {
         Blit8To8(XBuf+NOFFSET,dest, NWIDTH, tlines,
                  TmpScreen->pitch, 1, 1, 0, sponge);
     } else {
         Blit8To8(XBuf+NOFFSET, dest, NWIDTH, tlines,
                  TmpScreen->pitch, (int)exs, (int)eys, eefx, sponge);
     }
 }
 if(SDL_MUSTLOCK(TmpScreen))
  SDL_UnlockSurface(TmpScreen);

 if(BlitBuf)
 {
  SDL_Rect srect;
  SDL_Rect drect;

  srect.x=0;
  srect.y=0;
  srect.w=NWIDTH;
  srect.h=tlines;

  drect.x=0;
  drect.y=0;
  drect.w=(Uint16)(exs*NWIDTH);
  drect.h=(Uint16)(eys*tlines);

  SDL_BlitSurface(BlitBuf, &srect,screen,&drect);
 }

 SDL_UpdateRect(screen, xo, yo, (Uint32)(NWIDTH*exs), (Uint32)(tlines*eys));

 if(screen->flags&SDL_DOUBLEBUF)
  SDL_Flip(screen);
}

uint32 PtoV(uint16 x, uint16 y)
{
 y=(uint16)((double)y/eys);
 x=(uint16)((double)x/exs);
 if(eoptions&EO_CLIPSIDES)
  x+=8;
 y+=srendline;
 return(x|(y<<16));
}