/*
*     ___                       ___
*    / _ \___ _    _____ ____  / _ \_______ __    __
*   / ___/ _ \ |/|/ / -_) __/ / // / __/ _ `/ |/|/ /
*  /_/   \___/__,__/\__/_/   /____/_/  \_,_/|__,__/
*
*
*   Original Code by John Garrison
*   Written June 8-11,17-18 August 6, 1999
*   To be released under the LGPL
*
*   Setting of graphics modes and buffers.
*
*   See LICENSE.TXT for more LGPL info
*/

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

#include "SDL/SDL.h"
#include "../IMG.h"
#include "../powerpak.h"
#include "powerdraw.h"
#include "gfx.h"

#define sign(x) ((x)>0 ? 1:-1)

/////////////////////////////////////////
//          INTERNAL ROUTINES          //
/////////////////////////////////////////

/* stretch code for 8bpp */
void _Stretch8(PD_Surface *Surface, PD_Surface *dst_surface, long x1,long x2,long y1,long y2,long yr,long yw)
{
   long dx,dy,e,d,dx2;
   Uint16 src_pitch, dst_pitch;
   Uint8 *src_pixels = 0,
         *dst_pixels = 0;

   if(yw >= dst_surface->h)
      return;
	
   dx=(x2-x1);
   dy=(y2-y1);

   dy<<=1;
   e=dy-dx;
   dx2=dx<<1;
	
   src_pitch = Surface->pitch;
   dst_pitch = dst_surface->pitch;
	
   src_pixels = (Uint8*)((long)Surface->pixels + yr*src_pitch + y1);
   dst_pixels = (Uint8*)((long)dst_surface->pixels + yw*dst_pitch + x1);

   for(d=0;d<=dx;d++)
   {
      *dst_pixels = *src_pixels;

      while(e>=0)
      {
         src_pixels++;
         e-=dx2;
      }
		
      dst_pixels++;
      e+=dy;
   }
}



/* stretch code for 16bpp */
void _Stretch16(PD_Surface *Surface, PD_Surface *dst_surface, long x1,long x2,long y1,long y2,long yr,long yw)
{
   long dx,dy,e,d,dx2;
   Uint16 src_pitch, dst_pitch;
   Uint16 *src_pixels = 0,
          *dst_pixels = 0;
	
   if(yw >= dst_surface->h)
      return;

   dx=(x2-x1);
   dy=(y2-y1);

   dy<<=1;
   e=dy-dx;
   dx2=dx<<1;
	
   src_pitch = Surface->pitch;
   dst_pitch = dst_surface->pitch;
	
   src_pixels = (Uint16*)((long)Surface->pixels + yr*src_pitch + y1*2);
   dst_pixels = (Uint16*)((long)dst_surface->pixels + yw*dst_pitch + x1*2);

   for(d=0;d<=dx;d++)
   {
      *dst_pixels = *src_pixels;

      while(e>=0)
      {
         src_pixels++;
         e-=dx2;
      }
		
      dst_pixels++;
      e+=dy;
   }
}



/* stretch code for 24bpp */
void _Stretch24(PD_Surface *Surface, PD_Surface *dst_surface, long x1,long x2,long y1,long y2,long yr,long yw)
{
   int i;
   long dx,dy,e,d,dx2;
   Uint16 src_pitch, dst_pitch;
   Uint8 *src_pixels = 0,
         *dst_pixels = 0;

		
   if(yw >= dst_surface->h)
      return;

   dx=(x2-x1);
   dy=(y2-y1);

   dy<<=1;
   e=dy-dx;
   dx2=dx<<1;

   src_pitch = Surface->pitch;
   dst_pitch = dst_surface->pitch;
	
   src_pixels = (Uint8*)((long)Surface->pixels + yr*src_pitch + y1*3);
   dst_pixels = (Uint8*)((long)dst_surface->pixels + yw*dst_pitch + x1*3);

   for(d=0;d<=dx;d++)
   {
      for(i=0; i<3; i++)
         *(dst_pixels+i) = *(src_pixels+i);

      while(e>=0)
      {
         src_pixels +=3;
         e-=dx2;
      }
		
      dst_pixels +=3;
      e+=dy;
   }
}



/* stretch code for 32bpp */
void _Stretch32(PD_Surface *Surface, PD_Surface *dst_surface, long x1,long x2,long y1,long y2,long yr,long yw)
{
   long dx,dy,e,d,dx2;
   Uint16 src_pitch, dst_pitch;
   Uint32 *src_pixels = 0,
          *dst_pixels = 0;

		
   if(yw >= dst_surface->h)
      return;

   dx=(x2-x1);
   dy=(y2-y1);

   dy<<=1;
   e=dy-dx;
   dx2=dx<<1;
	
   src_pitch = Surface->pitch;
   dst_pitch = dst_surface->pitch;
	
   src_pixels = (Uint32*)((long)Surface->pixels + yr*src_pitch + y1*4);
   dst_pixels = (Uint32*)((long)dst_surface->pixels + yw*dst_pitch + x1*4);

   for(d=0;d<=dx;d++)
   {
      *dst_pixels++ = *src_pixels++;

      while(e>=0)
      {
         src_pixels ++;
         e-=dx2;
      }

      dst_pixels ++;
      e+=dy;
   }
}


/////////////////////////////////////////////
//          MODE SETTING ROUTINES          //
/////////////////////////////////////////////

/* This pretty much comes straight from an SDL example */
int PD_SetGFXMode (int XRes, int YRes, int Bpp, Uint32 Mode)
{
   /* Initialize the display in a XRes x YRes x Bpp-bit mode */
   if (Mode == PD_DEFAULT)
      Screen = SDL_SetVideoMode(XRes, YRes, Bpp, SDL_HWSURFACE|SDL_HWPALETTE|SDL_DOUBLEBUF);
   else if (Mode == PD_FULLSCREEN)
      Screen = SDL_SetVideoMode(XRes, YRes, Bpp, SDL_HWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN|SDL_DOUBLEBUF);
   else if (Mode == PD_DEFAULTDIRECT)
      Screen = SDL_SetVideoMode(XRes, YRes, Bpp, SDL_HWSURFACE|SDL_HWPALETTE);
   else if (Mode == PD_FULLSCREENDIRECT	)
      Screen = SDL_SetVideoMode(XRes, YRes, Bpp, SDL_HWSURFACE|SDL_HWPALETTE|SDL_FULLSCREEN);

   if (Screen == NULL) {
      fprintf(stderr, "Couldn't set %dx%dx%d video mode: %s\n",XRes, YRes, Bpp, SDL_GetError());
   exit(1);
   }

   BPP = Bpp;
   SCREENWIDTH = XRes;
   SCREENHEIGHT = YRes;

  /* Figure out what putpixel routine to use */
   switch (Screen->format->BytesPerPixel)
   {
    case 1:
      _PutPixel = fast_putpixel1;
      break;
    case 2:
      _PutPixel = fast_putpixel2;
      break;
    case 3:
      _PutPixel = fast_putpixel3;
      break;
    case 4:
      _PutPixel = fast_putpixel4;
      break;
   }

   return 0;
}



//////////////////////////////////////////////
//          DOUBLE BUFFER ROUTINES          //
//////////////////////////////////////////////

/* Useless now, but still here for backward compatibility */
int PD_CreateDoubleBuffer()
{
   return 0;
}



/* Useless now, but still here for backward compatibility */
int PD_DestroyDoubleBuffer()
{
   return 0;
}



/* Blit the DoubleBuffer to the Screen */
int PD_ShowDoubleBuffer()
{
   SDL_Flip(Screen);

   return 0;
}



/* This is the same a flipping the double buffer, but for
non-double buffered displays */
int PD_UpdateWholeScreen()
{
   SDL_UpdateRect(Screen,0,0,SCREENWIDTH,SCREENHEIGHT);

   return 0;
}



/* This will update only part of the screen when speed is
important */
int PD_UpdateScreen(int X, int Y, int W, int H)
{
   if (X < 0)
      X = 0;
   if (Y < 0)
      Y = 0;
   if (X+W > SCREENWIDTH)
      W = SCREENWIDTH - X;
   if (Y+H > SCREENHEIGHT)
      H = SCREENHEIGHT - Y;

   if (X > SCREENWIDTH)
      return 0;
   if (Y > SCREENHEIGHT)
      return 0;

   SDL_UpdateRect(Screen, X, Y, W, H);

   return 0;
}



/* Tells the user whether or not a double buffer is available */
int PD_DoesDoubleBufferExist(void)
{
  return( (Screen->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF );
}



////////////////////////////////////////
//          SURFACE ROUTINES          //
////////////////////////////////////////

/* Create a new surface to match the screen*/
PD_Surface *PD_CreateSurface(int Width, int Height)
{
   PD_Surface *surface, *surface2;
   Uint32 rmask, gmask, bmask, amask;

   /* Make sure the Screen is allocated */
   if (Screen == NULL)
      return 0;

   /* Setup up our screen format variables */
   rmask = Screen->format->Rmask;
   gmask = Screen->format->Gmask;
   bmask = Screen->format->Bmask;
   amask = Screen->format->Amask;

   surface = SDL_AllocSurface (SDL_HWSURFACE|SDL_HWPALETTE, Width, Height, BPP, rmask, gmask, bmask, amask);

   if (surface == NULL) {
      fprintf(stderr, "Couldn't create %dx%dx%d surface: %s\n", Width, Height, BPP, SDL_GetError());
   exit(1);
   }

   if (BPP == 8)
      PD_SetPalette(surface, Screen->format->palette->colors);

   surface2 = SDL_DisplayFormat(surface);

   PD_DestroySurface(surface);

   return(surface2);
}



/* Create a new surface with the specified BPP */
PD_Surface *PD_CreateSurfaceBPP(int Width, int Height, int Bpp)
{
   PD_Surface *surface;

   surface = SDL_AllocSurface (SDL_HWPALETTE, Width, Height, Bpp, 0, 0, 0, 0);

   if (surface == NULL) {
      fprintf(stderr, "Couldn't create %dx%dx%d surface: %s\n", Width, Height, Bpp, SDL_GetError());
   exit(1);
   }

   return(surface);
}



/* Free a surface */
int PD_DestroySurface (PD_Surface *Surface)
{
   if (Surface == NULL)
      return -1;

   SDL_FreeSurface(Surface);

   return 0;
}



/* Blit from one surface to another */
int PD_Blit (PD_Surface *Src, PD_Surface *Dest, Sint32 SrcX, Sint32 SrcY, Sint32 DestX, Sint32 DestY, Uint32 W, Uint32 H)
{
   SDL_Rect src, dest;

   /* Initialize our rectangles */
   src.x = SrcX;
   src.y = SrcY;
   src.w = W;
   src.h = H;

   dest.x = DestX;
   dest.y = DestY;
   dest.w = W;
   dest.h = H;

   SDL_BlitSurface(Src, &src, Dest, &dest);

   return 0;
}



/* included for backward compatibilty */
int PD_BlitAsIs (PD_Surface *Src, PD_Surface *Dest, Sint32 SrcX, Sint32 SrcY, Sint32 DestX, Sint32 DestY, Uint32 W, Uint32 H)
{
   PD_Blit(Src, Dest, SrcX, SrcY, DestX, DestY, W, H);
   return 0;
}



 /* Blit from one surface to another */
int PD_BlitTransparent (PD_Surface *Src, PD_Surface *Dest, Sint32 SrcX, Sint32 SrcY, Sint32 DestX, Sint32 DestY, Uint32 W, Uint32 H, PD_Color Clear, Uint8 Alpha)
{
   SDL_Rect src, dest;

   /* Initialize our rectangles */
   src.x = SrcX;
   src.y = SrcY;
   src.w = W;
   src.h = H;

   dest.x = DestX;
   dest.y = DestY;
   dest.w = W;
   dest.h = H;

   /* Set the color to be transparent */
   SDL_SetColorKey(Src, (SDL_SRCCOLORKEY|SDL_RLEACCEL), Clear);
   /* Set the alpha value */
   SDL_SetAlpha(Src, SDL_SRCALPHA, Alpha);

   SDL_BlitSurface(Src, &src, Dest, &dest);

   return 0;
}



// blits the image to the screen/doublebuffer
int PD_EasyBlit(int Xpos,int Ypos,PD_Surface
*image)
{
  PD_Blit(image, Screen, 0, 0, Xpos, Ypos, image->w, image->h);

  return 0;
}



/* Clears a surface to Color */
int PD_ClearSurface (PD_Surface *Surface, PD_Color Color)
{
   PD_RectFilled(Surface, 0, 0, Surface->w, Surface->h, Color);

   return 0;
}



///////////////////////////////////////////////////
//          SURFACE PROPERTIES ROUTINES          //
///////////////////////////////////////////////////

/* Sets the clipping range for a surface */
int PD_SetClippingRect (PD_Surface *Surface, Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2)
{
	SDL_Rect rect;

   if (Surface == NULL)
      return -1;

   if (X1 < 0 || X2 > Surface->w)
      return -1;
   if (Y1 < 0 || Y2 > Surface->h)
      return -1;

   rect.x = X1;
   rect.y = Y1;
   rect.w = X2 - X1;
   rect.h = Y2 - Y1;

   SDL_SetClipRect(Surface, &rect);

   return 0;
}



///////////////////////////////////////
//          PALETTE ROUTINES         //
///////////////////////////////////////

/* Allocate Memory for the palette */
PD_Palette PD_InitPalette()
{
   return (PD_RGB *)malloc(256*sizeof(PD_RGB));
}

/* Set the palette */
int PD_SetPalette (PD_Surface *Surface, PD_Palette Palette)
{
   if (Surface == NULL)
      return -1;

   SDL_SetColors(Surface, Palette, 0, 256);

   /* Screen and DoubleBuffer should always be the same */
   if (Surface == Screen)
      SDL_SetColors(DoubleBuffer, Palette, 0, 256);
   if (Surface == DoubleBuffer)
      SDL_SetColors(Screen, Palette, 0, 256);
   return 0;
}



/* Used with BlitAsIs instead of BlitTransparent can really
speed up a loop when SetColorKeys is placed outside the loop */
int PD_SetColorKeys(PD_Surface *Surface, PD_Color Clear, int Alpha)
{
   /* Set the color to be transparent */
   SDL_SetColorKey(Surface, (SDL_SRCCOLORKEY|SDL_RLEACCEL), Clear);
   /* Set the alpha value */
   SDL_SetAlpha(Surface, SDL_SRCALPHA, Alpha);

   Surface = SDL_DisplayFormat(Surface);

   return 0;
}



/* Edit a palette */
int PD_EditPalette (PD_Surface *Surface, PD_RGB *Colors, Uint8 StartColor, Uint16 NumColors)
{
   if (Surface == NULL)
      return -1;

   SDL_SetColors(Surface, Colors, StartColor, NumColors);

   /* Screen and DoubleBuffer should always be the same */
   if (Surface == Screen)
      SDL_SetColors(DoubleBuffer, Colors, StartColor, NumColors);
   if (Surface == DoubleBuffer)
      SDL_SetColors(Screen, Colors, StartColor, NumColors);

   return 0;
}



/* Get a palette from a surface */
PD_Palette *PD_GetPalette (PD_Surface *Surface)
{
   if (Surface == NULL)
      return 0;

   return &Surface->format->palette->colors;
}



/* Fill in a palette entry with R, G, B componenets */
PD_RGB PD_FillPaletteEntry (Uint8 R, Uint8 G, Uint8 B)
{
   PD_RGB color;

   color.r = R;
   color.g = G;
   color.b = B;

   return color;
}



/* Map RGB values to a color */
PD_Color PD_CreateColor (PD_Surface *Surface, Uint8 R, Uint8 G, Uint8 B)
{
   return SDL_MapRGB(Surface->format, R, G, B);
}



/* Get the RGB of a color value. Note that I inlined SDL's GetRGB function because my linker
kept complaining the functin didn't exist */
PD_RGB PD_GetRGB (PD_Surface *Surface, PD_Color Color)
{
   SDL_PixelFormat *fmt;
   PD_RGB rgb;
   Uint8 r, g, b;

   fmt = Surface->format;

   if ( fmt->palette == NULL ) {
      r = (((Color&fmt->Rmask)>>fmt->Rshift)<<fmt->Rloss);
      g = (((Color&fmt->Gmask)>>fmt->Gshift)<<fmt->Gloss);
      b = (((Color&fmt->Bmask)>>fmt->Bshift)<<fmt->Bloss);
   } else {
      r = fmt->palette->colors[Color].r;
      g = fmt->palette->colors[Color].g;
      b = fmt->palette->colors[Color].b;
   }

   rgb = PD_FillPaletteEntry (r, g, b);

   return(rgb);
}



/* Free palette memory */
int PD_FreePalette(PD_Palette Pal)
{
   if (Pal == NULL)
      return 0;

   free(Pal);

   return 0;
}



//////////////////////////////////////////////
//          IMAGE LOADING ROUTINES          //
//////////////////////////////////////////////

PD_Surface *PD_LoadImage(const char *file)
{
   SDL_Surface *image, *image2;

   image = IMG_Load(file);
   if ( image == NULL ) {
      fprintf(stderr,"Couldn't load %s: %s\n", file, SDL_GetError());
      exit(2);
   }

   image2 = SDL_DisplayFormat(image);

   PD_DestroySurface(image);

   return (image2);
}



/////////////////////////////////////////////////
//          SURFACE FLIPPING ROUTINES          //
/////////////////////////////////////////////////

/* Returns Surface flipped vertically */
PD_Surface *PD_SurfaceVFlipped (PD_Surface *Surface)
{
   PD_Surface *surface;
   int i;

   surface = PD_CreateSurface(Surface->w, Surface->h);

   for (i = Surface->w; i >= 0; i--)
      PD_Blit(Surface, surface, i, 0, (Surface->w - i), 0, 1, Surface->h);

   return (surface);
}



/* Returns Surface flipped horizontally */
PD_Surface *PD_SurfaceHFlipped (PD_Surface *Surface)
{
   PD_Surface *surface;
   int i;

   surface = PD_CreateSurface(Surface->w, Surface->h);

   for (i = Surface->h; i >= 0; i--)
      PD_Blit(Surface, surface, 0, i, 0, (Surface->h - i), Surface->w, 1);

   return (surface);
}



/* Flips Surface both ways */
PD_Surface *PD_SurfaceVHFlipped (PD_Surface *Surface)
{
   PD_Surface *surface, *surface2;
   int i;

   surface = PD_CreateSurface(Surface->w, Surface->h);
   surface2 = PD_CreateSurface(Surface->w, Surface->h);

   for (i = Surface->w; i >= 0; i--)
      PD_Blit(Surface, surface2, i, 0, (Surface->w - i), 0, 1, Surface->h);
   for (i = Surface->h; i >= 0; i--)
      PD_Blit(surface2, surface, 0, i, 0, (Surface->h - i), Surface->w, 1);

   return (surface);
}


////////////////////////////////////////////////
//          SURFACE EFFECTS ROUTINES          //
////////////////////////////////////////////////

PD_Surface *PD_RotateSurface (PD_Surface *Surface, int Angle)
{
   PD_Surface *surface;
   int originx, originx2, newx, x, x2;
   int originy, originy2, newy, y, y2;

   surface = PD_CreateSurface((Surface->w + Surface->h),(Surface->w + Surface->h));

   originx = Surface->w>>1;
   originy = Surface->h>>1;

   originx2 = surface->w>>1;
   originy2 = surface->h>>1;

   for (y2=0; y2 < Surface->h; y2++)
      for (x2=0; x2 <Surface->w; x2++){
         x = x2 - originx;
         y = y2 - originy;
         newx = x*cos((float)Angle*0.017453292) - y*sin((float)Angle*0.017453292);
         newy = x*sin((float)Angle*0.017453292) + y*cos((float)Angle*0.017453292);
         PD_Blit(Surface, surface, x2, y2, newx + originx2, newy + originy2, 2, 2);
      }

   return (surface);
}



PD_Surface *PD_XShearSurface(PD_Surface *Surface, int XShear)
{
   PD_Surface *surface;
   int y2;
   float newx, add;

   surface = PD_CreateSurface((Surface->w + abs(XShear)), Surface->h);

   add = (float)((float)abs(XShear)/(float)Surface->h);
   newx = 0;

   if (XShear >= 0){
      for (y2=0; y2 < Surface->h; y2++){
         PD_Blit(Surface, surface, 0, y2, newx, y2, Surface->w, 1);
         newx += add;
      }
   }

   if (XShear < 0){
      for (y2=Surface->h; y2 >= 0; y2--){
         PD_Blit(Surface, surface, 0, y2, newx, y2, Surface->w, 1);
         newx += add;
      }
   }

   return (surface);
}



PD_Surface *PD_YShearSurface(PD_Surface *Surface, int YShear)
{
   PD_Surface *surface;
   int x2;
   float newy, add;

   surface = PD_CreateSurface(Surface->w, (Surface->h + abs(YShear)));

   add = (float)((float)abs(YShear)/(float)Surface->w);
   newy = 0;

   if (YShear >= 0){
      for (x2=0; x2 < Surface->w; x2++){
         PD_Blit(Surface, surface, x2, 0, x2, newy, 1, Surface->h);
         newy += add;
      }
   }

   if (YShear < 0){
      for (x2=Surface->w; x2 >= 0; x2--){
         PD_Blit(Surface, surface, x2, 0, x2, newy, 1, Surface->h);
         newy += add;
      }
   }

   return (surface);
}



/* Stretches a part of a surface */
PD_Surface *PD_ScaleSurfacePart(PD_Surface *Surface, long SrcX1, long SrcY1, long SrcX2, long SrcY2, long Width, long Height)
{
   PD_Surface *dst_surface;
	long dx,dy,e,d,dx2;
   long destx1, desty1;
	short sx,sy;

   destx1 = 0;
   desty1 = 0;
	dx=abs((int)(Height-desty1));
	dy=abs((int)(SrcY2-SrcY1));
	sx=sign(Height-desty1);
	sy=sign(SrcY2-SrcY1);
	e=(dy<<1)-dx;
	dx2=dx<<1;
	dy<<=1;

   dst_surface = PD_CreateSurfaceBPP(Width-destx1, Height-desty1, Surface->format->BitsPerPixel);
		
	if(dst_surface->format->BytesPerPixel == 1)
		SDL_SetColors(dst_surface, Surface->format->palette->colors, 0, 256);
	
	if ( SDL_MUSTLOCK(dst_surface) ) {
		if ( SDL_LockSurface(dst_surface) < 0 )
		return NULL;
	}
	
	for(d=0;d<dx;d++)
	{
		switch(Surface->format->BytesPerPixel){
			case 1:
		   	_Stretch8(Surface, dst_surface, destx1, Width, SrcX1, SrcX2, SrcY1, desty1);
				break;
				
			case 2:
				_Stretch16(Surface, dst_surface, destx1, Width, SrcX1, SrcX2, SrcY1, desty1);
				break;
				
			case 3:
				_Stretch24(Surface, dst_surface, destx1, Width, SrcX1, SrcX2, SrcY1, desty1);
				break;
			
			case 4:
				_Stretch32(Surface, dst_surface, destx1, Width, SrcX1, SrcX2, SrcY1, desty1);
				break;
		}

		while(e>=0)
		{
			SrcY1++;
			e-=dx2;
		}
		desty1++;
		e+=dy;
	}

	if ( SDL_MUSTLOCK(dst_surface) ) {
		SDL_UnlockSurface(dst_surface);
	}

	return(dst_surface);
}



// Stretches a part of a surface by a certain factor
PD_Surface *PD_ScaleSurfacePartBy(PD_Surface *Surface, long SrcX1, long SrcY1, long SrcX2, long SrcY2, float XFactor, float YFactor)
{
   int x, y;

   x = (SrcX2 - SrcX1);
   y = (SrcY2 - SrcY1);

   if (XFactor >= 0)
      x *= XFactor;
   else
      x /= -(XFactor);

   if (YFactor >= 0)
      y *= YFactor;
   else
      y /= -(YFactor);

   return(PD_ScaleSurfacePart(Surface, SrcX1, SrcY1, SrcX2, SrcY2, x, y));
}



// Stretches a whole surface
PD_Surface *PD_ScaleSurface(PD_Surface *Surface, long Width, long Height)
{
   return PD_ScaleSurfacePart(Surface, 0, 0, Surface->w, Surface->h, Width, Height);
}



// Stretches a whole surface by a certain factor
PD_Surface *PD_ScaleSurfaceBy(PD_Surface *Surface, float XFactor, float YFactor)
{
   return PD_ScaleSurfacePartBy(Surface, 0, 0, Surface->w, Surface->h, XFactor, YFactor);
}