#include "generic.h"

int ZOOM_FACTOR = 4;
int NO_SHADES = 2;

enum mode_type   {MODE_FLOYDT_GRAY = 0,
                  MODE_FLOYDT_COLOR = 1,
                  MODE_ORDERED_GRAY = 2,
		  MODE_ORDERED_COLOR = 3};

mode_type mode = MODE_FLOYDT_GRAY;

int ConvertShade(int v)
{
  int size_interval = 256/NO_SHADES;
  int multiplier    = 255/(NO_SHADES-1);

  v = v / size_interval;
  v = v * multiplier;

  return v;
} // Convert Shade

int ConvertShadeLowerLimit(int v)
{
  int size_interval = 256/(NO_SHADES-1);

  v = v / size_interval;
  v = v * size_interval;

  return v;
} // Convert ShadeLowerLimit

int DitherOrdered(int x, int y, int v)
{
  int size_interval = 256/(NO_SHADES-1);
  /*
  const int NO_DOTS = 3;
  
  char dot_matrix[NO_DOTS][NO_DOTS] = {{4,8,1},
			               {7,0,6},
                		       {3,5,2}};
  */
  
  const int NO_DOTS = 4;

  int dot_matrix[NO_DOTS][NO_DOTS] = {{ 0, 8, 2,10},
			               {12, 4,14, 6},
			               { 3,11, 1, 9},
				       {15, 7,13, 5}};
  
  /*  			      
  const int NO_DOTS = 4;

  char dot_matrix[NO_DOTS][NO_DOTS] = {{15, 4, 5, 6},
			               {14, 3, 0, 7},
			               {13, 2, 1, 8},
				       {12,11,10, 9}};
  
  */

  if((v*NO_DOTS*NO_DOTS)/size_interval > dot_matrix[x % NO_DOTS][y % NO_DOTS]) return size_interval-1;
  else return 0;
  //  return v;
} // DitherOrdered

int GetGray(SDL_Surface *p_source_image, int x, int y)
{
  return ((int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+0]) +
	  (int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+1]) +
	  (int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+2]))/3;
} // GetGray

void GetColor(SDL_Surface *p_source_image, int x, int y, int &ar, int &ag, int &ab)
{
  ar = (int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+0]);
  ag = (int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+1]);
  ab = (int)(((unsigned char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+2]);
} // GetColor

void SetGray(SDL_Surface *p_source_image, int x, int y, int gray)
{
  if(gray < 0) gray = 0;
  if(gray > 255) gray = 255;

  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+0] = gray;
  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+1] = gray;
  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+2] = gray;
} // SetGray

void SetColor(SDL_Surface *p_source_image, int x, int y, int ar, int ag, int ab)
{
  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+0] = ar;
  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+1] = ag;
  ((char*)(p_source_image->pixels))[(x*3+y*p_source_image->pitch)+2] = ab;
} // SetColor

void AddGray(SDL_Surface *p_source_image, int x, int y, int diff)
{
  int gray = GetGray(p_source_image, x, y);

  gray += diff;
  
  SetGray(p_source_image, x, y, gray);
} // AddGray

void AddColor(SDL_Surface *p_source_image, int x, int y, int dr, int dg, int db)
{
  int ar, ag, ab;
  int tr, tg, tb;

  GetColor(p_source_image, x, y, ar, ag, ab);

  tr = ar+dr; if(tr > 255) tr = 255; if(tr < 0) tr = 0;
  tg = ag+dg; if(tg > 255) tg = 255; if(tg < 0) tg = 0;
  tb = ab+db; if(tb > 255) tb = 255; if(tb < 0) tb = 0;

  SetColor(p_source_image, x, y, tr, tg, tb);
} // AddColor

void RenderImage(SDL_Surface* p_screen, SDL_Surface* p_source_image)
{
  int r, g, b;

  long sum_orig, sum_dithered;

  for(int y = 1; y < p_source_image->h-1; y++) {
    for(int x = 1; x < p_source_image->w-1; x++) {

      /* use the following function:

	 Returns the gray-value at position x,y: int gray = GetGray(p_source_image, x, y);
	 Sets gray-value at x,y: SetGray(p_source_image, x, y, gray);
	 Adds grayPlus to image: AddGray(p_source_image, x, y, grayPlus);

	 Returns the color-value r,g,b (integers) at position x,y: GetColor(p_source_image, x, y, r, g, b);
	 Sets color-value at x,y: SetColor(p_source_image, x, y, r, g, b);
	 Adds color to image: AddColor(p_source_image, x, y, redPlus, greenPlus, bluePlus);

	 Quantizes grayvalue: int new_gray = ConvertShade(old_gray);

       */

      int orig_gray = GetGray(p_source_image, x, y);
      
      int orig_r, orig_g, orig_b;
      GetColor(p_source_image, x, y, orig_r, orig_g, orig_b);
	
      if(mode == MODE_FLOYDT_GRAY) {
	int quantized_gray = ConvertShade(orig_gray);
	
	int error = quantized_gray - orig_gray; // too bright by error
	
	AddGray(p_source_image, x+1, y, -error*7/16);
	AddGray(p_source_image, x+1, y+1, -error*1/16);
	AddGray(p_source_image, x , y+1, -error*5/16);
	AddGray(p_source_image, x-1, y+1, -error*3/16);
	
	SetGray(p_source_image, x, y, quantized_gray);
      } // if
       
      if(mode == MODE_FLOYDT_COLOR) {
      
	int quant_r = ConvertShade(orig_r);
	int quant_g = ConvertShade(orig_g);
	int quant_b = ConvertShade(orig_b);
	
	int error_r = quant_r - orig_r;
	int error_g = quant_g - orig_g;
	int error_b = quant_b - orig_b;
	
	AddColor(p_source_image, x+1, y, -error_r*7/16, -error_g*7/16, -error_b*7/16);
	AddColor(p_source_image, x+1, y+1, -error_r*1/16, -error_g*1/16, -error_b*1/16);
	AddColor(p_source_image, x , y+1, -error_r*5/16, -error_g*5/16, -error_b*5/16);
	AddColor(p_source_image, x-1, y+1, -error_r*3/16, -error_g*3/16, -error_b*3/16);
      
	SetColor(p_source_image, x, y, quant_r, quant_g, quant_b);
      } // if

      if(mode == MODE_ORDERED_GRAY) {
	int quantized_gray = ConvertShadeLowerLimit(orig_gray); // wir quantisieren *immer* auf die Untergrenze
       	int dithered_error = DitherOrdered(x, y, orig_gray - quantized_gray); // nur der Fehler geht ins Dithering
	SetGray(p_source_image, x, y, quantized_gray+dithered_error);
      } // if
	  
      if(mode == MODE_ORDERED_COLOR) {      
	int quant_r = ConvertShadeLowerLimit(orig_r);
	int quant_g = ConvertShadeLowerLimit(orig_g);
	int quant_b = ConvertShadeLowerLimit(orig_b);
	
	int dithered_red = DitherOrdered(x, y, orig_r-quant_r);
	int dithered_green = DitherOrdered(x, y, orig_g-quant_g);
	int dithered_blue = DitherOrdered(x, y, orig_b-quant_b);
	
	SetColor(p_source_image, x, y, quant_r+dithered_red, quant_g+dithered_green, quant_b+dithered_blue);
      } // if
      
      
    } // for
  } // for
  
} // RenderImage

void MagnifyCorner(SDL_Surface* p_source_image, int cursor_x, int cursor_y)
{
  if(cursor_x < 0) cursor_x = 0;
  if(cursor_y < 0) cursor_y = 0;
  if(cursor_x >= p_source_image->w*(3*ZOOM_FACTOR-1)/(3*ZOOM_FACTOR)) cursor_x = p_source_image->w*(3*ZOOM_FACTOR-1)/(3*ZOOM_FACTOR)-1;
  if(cursor_y >= p_source_image->h*(3*ZOOM_FACTOR-1)/(3*ZOOM_FACTOR)) cursor_y = p_source_image->h*(3*ZOOM_FACTOR-1)/(3*ZOOM_FACTOR)-1;
  
  for(int y = p_source_image->h*2/3; y < p_source_image->h-1; y++) {
    for(int x = p_source_image->w*2/3; x < p_source_image->w-1; x++) {
      
      int r, g, b;
      GetColor(p_source_image, ((x-p_source_image->w*2/3)/ZOOM_FACTOR)+cursor_x, ((y-p_source_image->h*2/3)/ZOOM_FACTOR)+cursor_y, r, g, b);
      SetColor(p_source_image, x, y, r, g, b);
    } // for
  } // for
  
} // MagnifyCorner


int main(int argc, char** argv)
{
  const int NO_SOURCE_IMAGES = 1;

  SDL_Surface *screen;
  SDL_Surface *p_source_image;
  SDL_Event   event;
  SDL_Rect    rect;
  int         window_width, window_height;
  int         current_x, current_y;
  bool        fullscreen = false, redraw_image = true;
  Uint32      flags = SDL_HWSURFACE;

  if(argv[1] == 0) {
    cerr << "usage: " << argv[0] << " merkel.jpg" << endl;
      exit(-1);
  } // if                  
  
  p_source_image=IMG_Load(argv[1]);

  window_width = p_source_image->w; window_height = p_source_image->h;
  
  if(SDL_Init(SDL_INIT_VIDEO) < 0) {
    cerr << "Could not initialize SDL video" << endl;
    exit(-1);
  } // if
  atexit(SDL_Quit);
  
  if(fullscreen) flags |= SDL_FULLSCREEN;
  
  screen = SDL_SetVideoMode(window_width, window_height, 24, flags);
  
  if(!screen) {
    cerr << argv[0] << "could not SetVideoMode() - abort" << endl;
    exit(-1);
  } // if
  
  for(;;) {
	
    if(redraw_image) {
     
      if(p_source_image) {
	SDL_LockSurface(p_source_image);
	SDL_LockSurface(screen);
	RenderImage(screen, p_source_image);
	MagnifyCorner(p_source_image, current_x, current_y);
	
	SDL_UnlockSurface(p_source_image);
	SDL_UnlockSurface(screen);
	SDL_BlitSurface(p_source_image, NULL, screen, NULL);
	SDL_UpdateRect(screen, 0, 0, screen->w, screen->h);

	SDL_FreeSurface(p_source_image);
	p_source_image=IMG_Load(argv[1]);
      } // if
    } // if

    if((SDL_WaitEvent(&event) != 0)) {
      
      switch(event.type) {
	
      case SDL_MOUSEMOTION:
	current_x = event.motion.x;	
	current_y = event.motion.y;
	break;
	
      case SDL_MOUSEBUTTONDOWN:
	break;
	
      case SDL_MOUSEBUTTONUP:
	break;
	
      case SDL_KEYDOWN:
	
	switch(event.key.keysym.sym) {

	case '1':
	  mode = MODE_FLOYDT_GRAY;
	  break;

	case '2':
	  mode = MODE_FLOYDT_COLOR;
	  break;

	case '3':
	  mode = MODE_ORDERED_GRAY;
	  break;

	case '4':
	  mode = MODE_ORDERED_COLOR;
	  break;

	  
	case '+':
	  NO_SHADES++;
	  redraw_image = true;
	  cout << "NO_SHADES = " << NO_SHADES << endl;
	  break;

	case '-':
	  if(NO_SHADES > 2) {
	    NO_SHADES--;
	    redraw_image = true;
	    cout << "NO_SHADES = " << NO_SHADES << endl;
	  } // if
	  break;

        case 280:
          ZOOM_FACTOR++;
          redraw_image = true;
          break;

        case 281:
          if(ZOOM_FACTOR > 2) {
            ZOOM_FACTOR--;
            redraw_image = true;
          } // if                                                                                    
          break;
	  
	case 27:
	case 'q':
	case 'Q':
	  goto exit_app;
			    
	default:
	  cout << "key: " << (int)event.key.keysym.sym << endl;
	  break;
	} // switch
      } // switch
    } // if
    
  } // for
    
 exit_app:
  
  SDL_UnlockSurface(p_source_image);	    
  SDL_FreeSurface(p_source_image);

  SDL_UnlockSurface(screen);	    
  SDL_FreeSurface(screen);
} // main
