#include "main.h"
#include "img.h"

#define PATHNUM 6
#define BPP 0
#define RECTS_NUM 80000

#ifndef DATADIR
#define DATADIR "."
#endif
char DATAPATH[200]=DATADIR;
const char PATH[PATHNUM][200]={DATADIR,".","data","../data","Penguin-Command.app/Contents/Resources/data",DATADIR};
SDL_Surface *Screen,*BackBuffer,*FadeBuffer;
SDL_Rect blitrect,blitrects[RECTS_NUM];
int blitrects_num=0,HWAccel=0;

#ifdef DATADIR
void ComplainAndExit(void)
{
        fprintf(stderr, "Problem: %s\n", SDL_GetError());
        exit(1);
}
#endif
int abrand(int a,int b)  //random number between a and b (inclusive)
{
  return(a+(rand() % (b-a+1)));
}

int (*_PutPixel)(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color);

int fast_putpixel1(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
  if (X < 0 || X > Surface->w || Y < 0 || Y > Surface->h) 
    return -1;

  *((Uint8 *)Surface->pixels + Y * Surface->pitch + X) = Color;

  return 0;
}

int fast_putpixel2(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
  if (X < 0 || X > Surface->w || Y < 0 || Y > Surface->h) 
    return -1;

 *((Uint16 *)Surface->pixels + Y * Surface->pitch/2 + X) = Color;

  return 0;
}

int fast_putpixel3(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
  Uint8 *pix;
  int shift;

  if (X < 0 || X > Surface->w || Y < 0 || Y > Surface->h) 
    return -1;

  /* Gack - slow, but endian correct */
  pix = (Uint8 *)Surface->pixels + Y * Surface->pitch + X*3;
  shift = Surface->format->Rshift;
  *(pix+shift/8) = Color>>shift;
  shift = Surface->format->Gshift;
  *(pix+shift/8) = Color>>shift;
  shift = Surface->format->Bshift;
  *(pix+shift/8) = Color>>shift;

  return 0;
}

int fast_putpixel4(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
  if (X < 0 || X > Surface->w || Y < 0 || Y > Surface->h) 
    return -1;

  *((Uint32 *)Surface->pixels + Y * Surface->pitch/4 + X) = Color;

  return 0;
}

void init_SDL()  // sets the video mode
{
  int bpp=BPP,flags=0;
  const SDL_VideoInfo *info;

//  if ( SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0 ) {ComplainAndExit();}
  puts("** Init video **");
  if ( SDL_Init(SDL_INIT_VIDEO ) < 0 ) ComplainAndExit();
  puts("** Init timer **");
  if ( SDL_Init(SDL_INIT_TIMER ) < 0 ) ComplainAndExit();
  info = SDL_GetVideoInfo();
  if (info->vfmt->BitsPerPixel==8) bpp=16;
  atexit(SDL_Quit);
  if (HWAccel) {
    SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );                    
    SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );                  
    SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
    SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
    flags=flags | SDL_OPENGLBLIT | SDL_OPENGL;
  }
// Set the video mode (800x600 at 16-bit depth)  
  if (fullscreen)  
    Screen = SDL_SetVideoMode(800, 600, bpp, SDL_FULLSCREEN | flags);
  else 
    { Screen = SDL_SetVideoMode(800, 600, bpp, flags); }
  if ( Screen == NULL ) {ComplainAndExit();}
// create BackBuffer
  BackBuffer = SDL_AllocSurface(Screen->flags,
                               800,
                               600,
                               Screen->format->BitsPerPixel,
                               Screen->format->Rmask,
                               Screen->format->Gmask,
                               Screen->format->Bmask, 0);
  if (BackBuffer == NULL)
  printf("ERROR: Couldn't create BackBuffer: %s\n", SDL_GetError());
  FadeBuffer = SDL_AllocSurface(Screen->flags,
                               800,
                               600,
                               Screen->format->BitsPerPixel,
                               Screen->format->Rmask,
                               Screen->format->Gmask,
                               Screen->format->Bmask, 0);
  if (FadeBuffer == NULL)
  printf("ERROR: Couldn't create FadeBuffer: %s\n", SDL_GetError());
// 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;
   }
}

void lock()
{
        if ( SDL_MUSTLOCK(Screen) ) {
                if ( SDL_LockSurface(Screen) < 0 )
		return;        }                                                                       
}

void unlock()
{
        if ( SDL_MUSTLOCK(Screen) ) {                                           
                SDL_UnlockSurface(Screen); }                                                                       
}

// Performs Callback at each line point. This came straight from the
// asphyxia vga trainers
int DoLine (SDL_Surface *Surface, Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2, Uint32 Color, int Callback (SDL_Surface *Surf, Sint32 X, Sint32 Y, Uint32 Color))
{ 
  Sint32 dx, dy, sdx, sdy, x, y, px, py; 
 
  dx = X2 - X1; 
  dy = Y2 - Y1; 
 
  sdx = (dx < 0) ? -1 : 1; 
  sdy = (dy < 0) ? -1 : 1; 
 
  dx = sdx * dx + 1; 
  dy = sdy * dy + 1; 
 
  x = y = 0; 
 
  px = X1; 
  py = Y1; 
 
  if (dx >= dy) 
    { 
      for (x = 0; x < dx; x++) 
	{ 
          Callback(Surface, px, py, Color);
	 
	  y += dy; 
	  if (y >= dx) 
	    { 
	      y -= dx; 
	      py += sdy; 
	    } 
	  px += sdx; 
	} 
    } 
  else 
    { 
      for (y = 0; y < dy; y++) 
	{ 
 
          Callback(Surface, px, py, Color);
 
	  x += dx; 
	  if (x >= dy) 
	    { 
	      x -= dy; 
	      px += sdx; 
	    } 
	  py += sdy; 
	} 
    } 
   return 0;
}

// The user's line drawing function
void Line(Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2, Uint32 Color)
{
   lock(Screen);

   /* Draw the line */
   DoLine(Screen, X1, Y1, X2, Y2, Color, &PutPixel);
   unlock(Screen);
}

int DoLinePart (SDL_Surface *Surface, Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2, Sint32 lower, Sint32 upper, Uint32 Color, int Callback (SDL_Surface *Surf, Sint32 X, Sint32 Y, Uint32 Color))
{ 
  Sint32 dx, dy, sdx, sdy, x, y, px, py; 
 
  dx = X2 - X1; 
  dy = Y2 - Y1; 
 
  sdx = (dx < 0) ? -1 : 1; 
  sdy = (dy < 0) ? -1 : 1; 
 
  dx = sdx * dx + 1; 
  dy = sdy * dy + 1; 
 
  x = y = 0; 
 
  px = X1; 
  py = Y1; 
 
  if (dx >= dy) 
    { 
      for (x = 0; x < dx; x++) 
	{ 
	  if ( (py<=upper)&&(py>=lower) )
          Callback(Surface, px, py, Color);
	 
	  y += dy; 
	  if (y >= dx) 
	    { 
	      y -= dx; 
	      py += sdy; 
	    } 
	  px += sdx; 
	} 
    } 
  else 
    { 
      for (y = 0; y < dy; y++) 
	{ 
 
	  if ( (py<=upper)&&(py>=lower) )
          Callback(Surface, px, py, Color);
 
	  x += dx; 
	  if (x >= dy) 
	    { 
	      x -= dy; 
	      px += sdx; 
	    } 
	  py += sdy; 
	} 
    } 
   return 0;
}

void LinePart(Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2, Sint32 lower, Sint32 upper, Uint32 Color)
{
   lock(Screen);

   /* Draw the line */
   DoLinePart(Screen, X1, Y1, X2, Y2, lower, upper, Color, &PutPixel);
   unlock(Screen);
}

void Update()
{
  SDL_UpdateRects(Screen,blitrects_num,blitrects);
  blitrects_num=0;
//  SDL_UpdateRect(Screen,0,0,0,0);
}

void FullUpdate()
{
  blitrects_num=0;
  SDL_UpdateRect(Screen,0,0,0,0);
}

void AddRect(int x1, int y1, int x2, int y2)
{
  int temp;
   /* Make sure X1 is before X2 */
   if (x2 < x1){
      temp = x2;
      x2 = x1;
      x1 = temp;
   }
   /* Make sure Y1 is before Y2 */
   if (y2 < y1){
      temp = y2;
      y2 = y1;
      y1 = temp;
   }
  blitrect.x = x1;
  blitrect.y = y1;
  blitrect.w = x2-x1+1;
  blitrect.h = y2-y1+1;
  if (x1<0) printf("x is too small in function AddRect!\n");else
  if (y1<0) printf("y is too small in function AddRect!\n");else
  if (x2>=800) printf("x is too big in function AddRect!\n");else
  if (y2>=600) printf("y is too big in function AddRect!\n");else {
      blitrects[blitrects_num]=blitrect;
      if (++blitrects_num>=RECTS_NUM-2)
      {printf("Too many blits!\n");blitrects_num--;Update();}
  }
}

void AddThisRect(SDL_Rect blitrect)
{
    blitrects[blitrects_num]=blitrect;
    if (++blitrects_num>=RECTS_NUM-2)
        {printf("Too many blits!\n");blitrects_num--;Update();}
}

void Blit(int Xpos,int Ypos,SDL_Surface *image)  //blits one GIF or BMP from the memory to the screen
{
  blitrect.x = Xpos;
  blitrect.y = Ypos;
  blitrect.w = image->w;
  blitrect.h = image->h;

  if (image==NULL) printf("WRONG BLIT: surface pointer is NULL!\n");
  if (Xpos<-image->w) printf("WRONG BLIT: Xpos is too small! - %d\n",Xpos); else
  if (Xpos>=800) printf("WRONG BLIT: Xpos is too big! - %d\n",Xpos); else
  if (Ypos<-image->h) printf("WRONG BLIT: Ypos is too small!\n - %d",Ypos); else
  if (Ypos>=600) printf("WRONG BLIT: Ypos is too big! - %d\n",Ypos); else
  if ( SDL_BlitSurface(image, NULL, Screen, &blitrect) < 0 ) 
  {
    SDL_FreeSurface(image);
    ComplainAndExit();
  }
  blitrects[blitrects_num]=blitrect;
  blitrects_num++;
}

SDL_Surface *LoadImage(char *datafile, int transparent)   // reads one png into the memory
{
  SDL_Surface *pic=NULL,*pic2=NULL;
  char filename[200];
  int i=0;

  sprintf(filename,"%s/gfx/%s",DATAPATH,datafile);

  pic=IMG_Load(filename);
  while ( pic == NULL ) {
    strcpy(DATAPATH,PATH[i]);
    sprintf(filename,"%s/gfx/%s",DATAPATH,datafile);
    pic=IMG_Load(filename);
    i++;
    
    if (i>=PATHNUM)
    {
      fprintf(stderr,"Couldn't load %s: %s\n", filename, SDL_GetError());
      exit(2);
    }
  }
  if (transparent>=3) {
      if ((NoAlpha)&&(transparent!=4)) {
//	  SDL_SetColorKey(pic,SDL_SRCCOLORKEY|SDL_RLEACCEL, GetPixel(pic,0,0));
	  SDL_SetColorKey(pic,SDL_SRCCOLORKEY, GetPixel(pic,0,0));
          pic2 = SDL_DisplayFormat(pic); 
          SDL_FreeSurface(pic);
          return (pic2);
      }
      else {
//	  SDL_SetColorKey(pic,SDL_RLEACCEL, 0);
          pic2 = SDL_DisplayFormatAlpha(pic); 
          SDL_FreeSurface(pic);
          return (pic2);
      }
  }
  if (transparent==1)
    SDL_SetColorKey(pic,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(pic2->format,0xFF,0xFF,0xFF));
  if (transparent==2)
    SDL_SetColorKey(pic,SDL_SRCCOLORKEY|SDL_RLEACCEL,0);
  if (transparent>-1) {
      pic2 = SDL_DisplayFormat(pic);
      return (pic2);
  } else
      return pic;
}

void BlitToBB(int Xpos,int Ypos,SDL_Surface *image)  //blits one GIF or BMP from the memory to the screen
{
  blitrect.x = Xpos;
  blitrect.y = Ypos;
  blitrect.w = image->w;
  blitrect.h = image->h;
  if ( SDL_BlitSurface(image, NULL, BackBuffer, &blitrect) < 0 )
  {
    SDL_FreeSurface(image);
    ComplainAndExit();
  }
}

void BlitPart(int Xpos,int Ypos,SDL_Surface *image, SDL_Rect srcrect)
{
  blitrect.x = srcrect.x;											
  blitrect.y = srcrect.y;											
  blitrect.w = srcrect.w;											
  blitrect.h = srcrect.h;
  if ( SDL_BlitSurface(image, &srcrect , Screen, &blitrect) < 0 )
  {
    SDL_FreeSurface(image);
    ComplainAndExit();
  }
  blitrects[blitrects_num]=blitrect;
  blitrects_num++;
}

void FadeScreen(float speed)
{
  Sint32 now,i;

  SDL_BlitSurface(Screen,NULL,FadeBuffer,NULL);
  now=SDL_GetTicks();
  for (i=255*speed;i>=0;i-=SDL_GetTicks()-now)
  {
    now=SDL_GetTicks();
    SDL_BlitSurface(FadeBuffer,&blitrect,Screen,&blitrect);
    SDL_SetAlpha(BackBuffer,SDL_SRCALPHA,255-(int)(i/speed));
    Blit(0,0,BackBuffer);
    SDL_UpdateRects(Screen,1,&blitrect);
  }
  SDL_SetAlpha(BackBuffer,0,0);
  Blit(0,0,BackBuffer);
  SDL_UpdateRects(Screen,1,&blitrect);
}

int PutPixel(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
    if (X<0) printf("X < 0 in function PutPixel! - %d\n",X); else
    if (X>=800) printf("X >= 800 in function PutPixel! - %d\n",X); else
    if (Y<0) printf("Y < 0 in function PutPixel! - %d\n",Y); else
    if (Y>=600) printf("Y >= 600 in function PutPixel! - %d\n",Y); else
    {
        _PutPixel(Surface,X,Y,Color);
        AddRect(X,Y,X,Y);
    }
    return 0;
}

/* This is a Callback for DoLine, so return type and arguments must match */
int PutBackPixel(SDL_Surface *Surface, Sint32 X, Sint32 Y, Uint32 Color)
{
    SDL_Rect rect;
    
    if ( (X<0) || (X>=800) || (Y<0) || (Y>=600) ) return -1;
    
    rect.w=1;
    rect.h=1;
    rect.x=X;
    rect.y=Y;
    SDL_BlitSurface(BackBuffer, &rect, Surface, &rect);
    AddThisRect(rect);

    return 0;
}

void UndrawLine(Sint32 X1, Sint32 Y1, Sint32 X2, Sint32 Y2, Uint32 Color)
{
   /* Draw the line */
   DoLine(Screen, X1, Y1, X2, Y2, Color, &PutBackPixel);
}
