/* gfx-stuff ... Daniel Hiepler, 2007 */

#include <stdio.h>
#include <getopt.h>
#include <SDL/SDL.h>
#include "include/palette.h"
#include "include/brush.h"

#ifndef TRUE
 #define TRUE 1
#endif

#ifndef FALSE
 #define FALSE 0
#endif

struct drip {
 int x,y;
 int lifetime;
 int smegginess;
 struct drip *previous_drip;
 struct drip *next_drip;
};

struct graphics {
 int width, height, depth;
 int old_x, old_y;
 int mouse_x, mouse_y, mouse_button;
 int sdl_flags;
 SDL_Surface *display;
 Uint8 *pixelbuf;
 Uint8 *doublebuf;
 int	drip_smeg_max, drip_smeg_min;
 int	drip_life_max, drip_life_min;
 int	drip_threshold;
 struct drip *drips;
};



	 
/* ************************************************************** */
void usage(char *program_name)
{
 printf("Usage: %s [OPTION]\n" \
	"\t-r <wdith>x<height>\t--resolution\tResolution\n" \
	"\t-f\t\t\t--fullscreen\tRun fullscreen\n" \
	"\t-h\t\t\t--help\t\tThis help\n\n" \
	"\t-t <threshold>\t--threshold\tSet threshold (0 - RAND_MAX)\n" \
	"\t-l <min>-<max>\t--life\t\tPixel life of drips\n" \
	"\t-s <min>-<max>\t--smegginess\tSpeed of drips\n\n" \
	"While active press key to clear screen, ESC to exit.\n", program_name);
}
/* ************************************************************** */
int parse_arguments(int argc, char **argv, struct graphics *gfx) {
 int further = TRUE;
 int argument_index = 0;
 int argument;
 static struct option loptions[] = {
		{"help",0,0,'h'},
		{"resolution",1,0,'r'},
		{"fullscreen",0,0,'f'},
		{"threshold",1,0,'t'},
		{"life",1,0,'l'},
		{"smegginess",1,0,'s'},
		{0,0,0,0}
 };
 
 while(TRUE) {
  argument = getopt_long(argc, argv, "-hr:ft:l:s:", loptions, &argument_index);
  if (argument == -1) break;

  switch(argument) {	 
   /* display help text */
   case 'h':
	usage(argv[0]);   
    further = FALSE;
    break;

   /* drip lifetime */
   case 'l':
    if(optarg)
    {
     sscanf(optarg, "%d-%d", &gfx->drip_life_min, &gfx->drip_life_max);	
    }
    else
    {
	 printf("Give value: min-max\n");
     further = FALSE;		
    }
	break;
	
   /* smegginess */
   case 's':
	if(optarg)
	{
	 sscanf(optarg, "%d-%d", &gfx->drip_smeg_min, &gfx->drip_smeg_max);	
	}
	else
	{
	 printf("Give value: min-max\n");
     further = FALSE;		
	}
    break;
	
   /* drip threshold */	
   case 't':
	if(optarg)
	{
	 gfx->drip_threshold = atoi(optarg);
	}
	else
	{
	 printf("Please give threshold value\n");
     further = FALSE;		
	}
	break;
	
   /* fullscreen */
   case 'f':
	gfx->sdl_flags |= SDL_FULLSCREEN;
	break;
	
   case 'r':
	if (!optarg) { 
	 printf("** Please provide resolution (<WIDTH>x<HEIGHT>)\n");
	 further = FALSE;
	}
	else {
	 sscanf(optarg, "%dx%d", &gfx->width, &gfx->height);
	 if(gfx->width <= 0 || gfx->height <= 0)
	 {
	  printf("Invalid resolution.\n");
      further = FALSE;		 
	 }
	}
	break;
	
   /* eek! this may never happen */
   default:
    fprintf(stderr,"**FATAL ERROR\n getopt returned character code 0%o\n",argument);
    further = FALSE;
    break;
  }
 }
 return further;
}
/* ************************************************************** */
int gfx_init(struct graphics *gfx) 
{
 int error = 0;
	
 /* init SDL */
 if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) 
 {
  fprintf(stderr, "SDL-init error: \"%s\"\n", SDL_GetError());
  error = -1;
 }
 /* sdl-init successful */
 else 
 {
  /* Quit SDL when we exit */
  atexit(SDL_Quit);

  /* Set video mode */
  if(!(gfx->display = SDL_SetVideoMode( gfx->width, gfx->height, gfx->depth, gfx->sdl_flags)))
  {
   fprintf(stderr, "sdl-display open error: \"%s\"\n", SDL_GetError());
   error = -2;
  }
  else
  {
   /* Ignore key events */
   //SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
   SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE);

   /* get screen-memory */
   gfx->pixelbuf = (void *) gfx->display->pixels;
   
   /* hide mouse-pointer */
   //SDL_ShowCursor(SDL_DISABLE);
	  
  
   gfx->mouse_button = 0;
   error = 0;
	  
  }
 }
 return error;
}

/* ************************************************************** */
void blur_rect(Uint8 *buffer, int width, int x1, int y1, int x2, int y2)
{
 int x_count, y_count;
 int tmp;
	
 for(y_count=1; y_count < y2-1; y_count++) 
 {
  for(x_count=2; x_count < x2-2; x_count++) 
  {
   tmp = (int) buffer[y_count*width+x_count-2];
   tmp += (int) buffer[y_count*width+x_count+2];
   tmp += (int) buffer[y_count*width+x_count-1];
   tmp += (int) buffer[y_count*width+x_count+1];
   tmp += (int) buffer[(y_count-1)*width+x_count];
   tmp += (int) buffer[(y_count+1)*width+x_count];
   tmp /= 6;
   buffer[y_count*width+x_count] = (Uint8) tmp;
  }
 }	 
}
/* ************************************************************** */
int myrand(int min, int max)
{
 int val = rand();
 if(val > max) val %= max;
 if(val % 2 == 0) val = -val;
 if(val < min) val += abs(min);
 return val;
}
/* ************************************************************** */
void drip_free(struct graphics *gfx, struct drip *my_drip)
{
 if(my_drip)
 {	 
  if(my_drip->previous_drip)
   my_drip->previous_drip->next_drip = my_drip->next_drip;
  else
   gfx->drips = my_drip->next_drip;
  
  if(my_drip->next_drip)
   my_drip->next_drip->previous_drip = my_drip->previous_drip;
  else
   if(my_drip->previous_drip) 
	 my_drip->previous_drip->next_drip = NULL;
  
  free(my_drip);
 }
}
/* ************************************************************** */
void drip_new(struct graphics *gfx, int x, int y, int smegginess, int lifetime)
{
 struct drip *current_drip = NULL, *new_drip;
 
 if((new_drip = calloc(1, sizeof(struct drip))))
 {
  /* seek to last drip */
  if(gfx->drips) {
   for(current_drip = gfx->drips;
	   current_drip->next_drip;
       current_drip = current_drip->next_drip);
   current_drip->next_drip = new_drip;
  }
  else gfx->drips = new_drip;
	  
  /* init drip */
  new_drip->x = x;
  new_drip->y = y;
  new_drip->smegginess = smegginess;
  new_drip->lifetime = lifetime;
  new_drip->previous_drip = current_drip;
 }
}
/* ************************************************************** */
void draw_pixel(struct graphics *gfx, int x, int y)
{
 gfx->doublebuf[y*gfx->width+x] |= 255;
 //if(rand() % 5 == 0) gfx->doublebuf[y*gfx->width+x+1] = 255;
}
/* ************************************************************** */
void draw_brush(struct graphics *gfx, int x, int y)
{
#define BRUSH_WIDTH 16
#define BRUSH_HEIGHT 16
	
 int lines,cols,x_n,y_n;
  
 x_n = x-BRUSH_WIDTH/2;
 y_n = y-BRUSH_HEIGHT/2;
 
 /* clipping */
 if(x_n > 1 && 
	y_n > 1 && 
	(x_n+BRUSH_WIDTH) < gfx->width-1 && 
    (y_n+BRUSH_HEIGHT) < gfx->height-1)
 {
	 
  Uint8 *buffer = &gfx->doublebuf[y_n*(gfx->width)+x_n];
  for(lines = 0; lines < BRUSH_HEIGHT; lines++) {
   for(cols = 0; cols < BRUSH_WIDTH; cols++) {
    buffer[cols] |= header_data[lines*BRUSH_WIDTH+cols];
   }	  
   buffer += gfx->width;
  }
  
  /* make randomness for drips */
  if(rand() % gfx->drip_threshold == gfx->drip_threshold-1)
  {
   drip_new(gfx, x_n+BRUSH_WIDTH/2, y_n+BRUSH_HEIGHT/2, 
	  myrand(gfx->drip_smeg_min,gfx->drip_smeg_max),
	  myrand(gfx->drip_life_min,gfx->drip_life_max));
  }
 }
}
/* ************************************************************** */
void drawLine(struct graphics *gfx, register int x1, register int y1, register int x2, register int y2, int brush) {
 register int	deltaX, deltaY;	
 register int	addX, addY;
 register int	counter, error;
 	
 // default step right	
 addX		= 1;
 // ...and down
 addY	 	= 1;
 // first calculate delta values
 deltaX 	= x2 - x1;
 deltaY 	= y2 - y1;
 
 // check for negative delta-value
 if(deltaX < 0) {
  addX = -addX;
  deltaX = -deltaX;
 }	 
 if(deltaY < 0) {
  addY = -addY;
  deltaY = -deltaY;
 }
 
 
 // special case for horizontal and vertical lines...
 // this will speed up things a bit in these cases
 if(deltaX == 0) {  
  for(counter = deltaY; counter > 0; counter--) {
   if(y1 >= 0 && y1 <= gfx->height) {
	   if(brush) draw_brush(gfx, x1, y1);
	   else draw_pixel(gfx, x1, y1);
   }
   y1 += addY;
  }
  return;
 }
 // horizontal line
 if(deltaY == 0) {  
  for(counter = deltaX; counter > 0; counter--) {
   if(x1 <= gfx->width && x1 >= 0) 
   {
	   if(brush) draw_brush(gfx, x1, y1);
	   else draw_pixel(gfx, x1, y1);
   }
   x1 += addX;
  }
  return;
 } 
 if(deltaX > deltaY) {
  // calc initial error-value
  error = deltaX>>1;
  for(counter = deltaX; counter > 0; counter--) {	 
   // adjust error-value
   error += deltaY;
   // set pixel with clipping
   if(x1 <= gfx->width && x1 >= 0 && y1 <= gfx->height && y1 >= 0) 
   {
	   if(brush) draw_brush(gfx, x1, y1);
	   else draw_pixel(gfx, x1, y1);
   }
   // if error is out of range, go one line further up or down
   if(error >= deltaX) {
	error -= deltaX;
	y1 += addY;	   
   }
   x1 += addX;
  }
 }
 else {
  // calc initial error-value
  error = deltaY>>1;
  for(counter = deltaY; counter >= 0; counter--) {
   // adjust error-value
   error += deltaX;
   // set pixel with clipping
   if(x1 <= gfx->width && x1 >= 0 && y1 <= gfx->height && y1 >= 0)
   {
	   if(brush) draw_brush(gfx, x1, y1);
	   else draw_pixel(gfx, x1, y1);
   }
   // if error is out of range, go one line further left or right
   if(error >= deltaY) {
	error -= deltaY;
    x1 += addX;	   
   }
   y1 += addY;
  }	  
 }
}
/* ************************************************************** */
void drip_do(struct graphics *gfx)
{
 struct drip *next, *current_drip;
 int xold, yold;

 if(gfx->drips) current_drip = gfx->drips;
 else return;
	 
 while(current_drip)
 {
  /* save next drip */
  next = current_drip->next_drip;
	 
  /* drip still alive? */
  if(current_drip->lifetime > 0 &&
	  current_drip->y < gfx->height)
  {
   xold = current_drip->x;
   yold = current_drip->y;
   current_drip->lifetime--;
   current_drip->y += current_drip->smegginess;
   current_drip->smegginess += myrand(-5,5);
   if(current_drip->smegginess < 0) 
	   current_drip->smegginess = -current_drip->smegginess;
   drawLine(gfx, xold, yold, current_drip->x, current_drip->y, FALSE);
  }
  else
  {
   drip_free(gfx, current_drip);
  }
  
  /* next one */
  current_drip = next;  
 }	 
}
/* ************************************************************** */
void draw_frame(struct graphics *gfx)
{
 
 if(SDL_MUSTLOCK(gfx->display))
 {
    if(SDL_LockSurface(gfx->display) < 0)
	return;
 }
 
 /* draw brush if button is down */
 if(gfx->mouse_button) 
 {
  drawLine(gfx, gfx->old_x, gfx->old_y, gfx->mouse_x, gfx->mouse_y, TRUE);
  gfx->old_x = gfx->mouse_x;
  gfx->old_y = gfx->mouse_y;
  draw_brush(gfx, gfx->mouse_x, gfx->mouse_y);
 }	 
 
 /* drip */
 drip_do(gfx);
 
 /* copy frame */
 memcpy(gfx->pixelbuf, gfx->doublebuf, gfx->width*gfx->height);
 
 /* blur */
 blur_rect(gfx->pixelbuf, gfx->width, 0,0, gfx->width, gfx->height);
 
 /* unlock memory area */
 if(SDL_MUSTLOCK(gfx->display))
 {
    SDL_UnlockSurface(gfx->display);
 }
 
 //SDL_UpdateRect(gfx->display, 0, 0, 0, 0);	
 SDL_Flip(gfx->display);
}
/* **************************************************************
   ************************************************************** */
int main(int argc, char *argv[])
{
 int error;
 struct graphics gfx;
 struct drip *tmp_drip;
 SDL_Event event;
 SDL_Rect rect;
 
 /* initialize default gfx-structure */
 memset(&gfx, 0, sizeof(struct graphics));
 gfx.width = 800;
 gfx.height = 600;
 gfx.depth = 8;
 gfx.drip_smeg_max = 1;
 gfx.drip_smeg_min = 4;
 gfx.drip_life_max = 10;
 gfx.drip_life_min = 100;
 gfx.drip_threshold = 30;
 
 gfx.sdl_flags = SDL_HWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF;
 rect.x = 0;
 rect.y = 0;
 rect.w = gfx.width;
 rect.h = gfx.height;
 
 /* parse arguments */
 if(parse_arguments(argc, argv, &gfx))
 {
	 
  /* allocate doublebuffer */
  if(!(gfx.doublebuf = malloc(gfx.width*gfx.height)))
  {
   printf("no more memory...\n");
   error = -2;
   goto m_error;	 
  }
 
  /* initialize sdl graphic stuff */ 
  if(gfx_init(&gfx)) 
  {
   printf("failed to init gfx-stuff...\n");
   error = -1;
   goto m_error;	 
  }
 
  
  /* set gimp-palette */
  SDL_SetColors(gfx.display, (SDL_Color *) &header_data_cmap, 0, 256);
 
  /* main loop */
  while(1)
  {
  
   SDL_Delay(1);
   
   /* draw frame */
   draw_frame(&gfx);
	 
   /* poll events */
   while(SDL_PollEvent(&event))
   {
    switch(event.type) 
    {
	 /* quit */
	 case SDL_QUIT:
	  printf("Quit...\n");
	  goto m_exit;
	  break;
	  
     /* keypress */
     case SDL_KEYDOWN:
	  if(event.key.keysym.sym == SDLK_ESCAPE) goto m_exit;
	  else memset(gfx.doublebuf, 0, gfx.width*gfx.height);
 	  break; 
      
     /* mouse-motion */
     case SDL_MOUSEMOTION:
	  gfx.mouse_x = (int) event.motion.x;
          gfx.mouse_y = (int) event.motion.y;
          if(!gfx.mouse_button)
          {
	      gfx.old_x = gfx.mouse_x;
              gfx.old_y = gfx.mouse_y;
          }		 
          break;
    
	 /* mouse-up */
	 case SDL_MOUSEBUTTONUP:
	  gfx.mouse_button = 0;
	  break;
	
 	 /* mouse-down */
	 case SDL_MOUSEBUTTONDOWN:
	  gfx.mouse_button = 1;
	  break;
	
     /* oOops! */
     default:
      printf("Unknown SDL event received?! No one knows what happens from here...\n");
      break;
    }
   }
  }
 }
 
m_exit:
 for(tmp_drip = gfx.drips; tmp_drip; tmp_drip = tmp_drip->next_drip)
 {
  drip_free(&gfx, tmp_drip); 
 }
 
 if(gfx.doublebuf) free(gfx.doublebuf);
 SDL_ShowCursor(SDL_ENABLE);
 error = 0;
 
m_error:
 
 return (error);
}
