/* a mandelbrot program adapted from prog5.4 in 
   LaMothe's Windows game programming for dummies */

// the basic includes
#define WIN32_LEAN_AND_MEAN  
#include <windows.h>  
#include <windowsx.h>  
#include <stdio.h> // for sprintf()
// #include <stdlib.h>
// #include <math.h>

// these are the size of the plotted region
#define IMAGE_WIDTH 640
#define IMAGE_HEIGHT 400
int displayed=0; // flag when picture done 
  /* displayed = 0 until picture drawn */
int iy=0; // current line; update one line at a time

// initial x and y ranges
double xmin=-2.1;
double xmax=.8;
double ymin=-1.2;             
double ymax=1.2;
double dx, dy; // size of pixels
const int MAXITER=64; // maximum mandelbrot iterations

// give a little border around the image
#define BORDER 10
// the 8 allows for the windows border too
#define WINDOW_WIDTH  (IMAGE_WIDTH+2*BORDER+8)
#define WINDOW_HEIGHT (IMAGE_HEIGHT+2*BORDER+96)
char stringbuffer[256];

// the next stuff is mosty copied from the dummies book
#define WINDOW_CLASS_NAME "WINCLASS1"
// global variables
HWND main_window_handle = NULL; // the window handle
// FUNCTIONS //////////////////////////////////////////////
// this is called when windows has an event, such as a mouse press
LRESULT CALLBACK WindowProc(HWND hwnd, 
			    UINT msg, 
                            WPARAM wparam, 
                            LPARAM lparam)
{
  // this is the main system message handler
  PAINTSTRUCT		ps;	// used in WM_PAINT
  HDC			hdc;	// handle to a device context
  // what is the message 
  switch(msg)
    {	
    case WM_CREATE: 
      {
	// do initialization stuff here
	dy=(ymax-ymin)/IMAGE_HEIGHT;
	dx=(xmax-xmin)/IMAGE_WIDTH;
	return(0);
      } break;

    case WM_PAINT: 
      {
	// the window is ready to be drawn or was exposed
	displayed=0; // to start the drawing again
	iy=0;
	hdc = BeginPaint(hwnd,&ps);	 
	EndPaint(hwnd,&ps);
	return(0);
      } break;

    case WM_DESTROY: 
      {
	// kill the application			
	PostQuitMessage(0);
	return(0);
      } break;

    case WM_LBUTTONDOWN:
      {
	// double scale on left button press
	int mx= (int) LOWORD(lparam);
	int my= (int) HIWORD(lparam);
	double mid = xmin+dx*mx; 
	xmin = mid -(xmax-xmin)/4;
	xmax = 2*mid - xmin;
	mid = ymin+dy*my;
	ymin= mid-(ymax-ymin)/4;
	ymax = 2*mid - ymin;
	dy=(ymax-ymin)/IMAGE_HEIGHT;
	dx=(xmax-xmin)/IMAGE_WIDTH;
	iy=0;
	displayed=0;
      } break;

    case WM_RBUTTONDOWN:
      {
	// zoom out on right button
	int mx= (int) LOWORD(lparam);
	int my= (int) HIWORD(lparam);
	double mid = xmin+dx*mx; 
	xmin = mid -(xmax-xmin);
	xmax = 2*mid - xmin;
	mid = ymin+dy*my;
	ymin= mid-(ymax-ymin);
	ymax = 2*mid - ymin;
	dy=(ymax-ymin)/IMAGE_HEIGHT;
	dx=(xmax-xmin)/IMAGE_WIDTH;
	iy=0;
	displayed=0;
      } break;
    case WM_KEYDOWN:
      {// quit if escape or Q key pressed
       int code = (int) wparam;
       switch (code)
        {case VK_ESCAPE:          
         case VK_Q:
          PostQuitMessage(0);
          return(0);
         default:
           break;
        }
      } 
    default:
     break;
    } // end switch

  // process any messages that we didn't take care of 
  return (DefWindowProc(hwnd, msg, wparam, lparam));
} // end WinProc

// WINMAIN ////////////////////////////////////////////////
int WINAPI WinMain(	HINSTANCE hinstance,
				HINSTANCE hprevinstance,
				LPSTR lpcmdline,
				int ncmdshow)
{
  WNDCLASS winclass;	// this will hold the class we create
  HWND	 hwnd;		// generic window handle
  MSG	 msg;		// generic message
  HDC      hdc;           // generic dc
  // PAINTSTRUCT ps;     // generic paintstruct

  // first fill in the window class stucture
  winclass.style			= CS_DBLCLKS | CS_OWNDC | 
    CS_HREDRAW | CS_VREDRAW;
  winclass.lpfnWndProc    	= WindowProc;
  winclass.cbClsExtra		= 0;
  winclass.cbWndExtra		= 0;
  winclass.hInstance		= hinstance;
  winclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
  winclass.hCursor		      = LoadCursor(NULL, IDC_ARROW);
  winclass.hbrBackground  	= GetStockObject(BLACK_BRUSH);
  winclass.lpszMenuName   	= NULL;
  winclass.lpszClassName  	= WINDOW_CLASS_NAME;

  // register the window class
  if (!RegisterClass(&winclass))
    return(0);

  // create the window
  if (!(hwnd = CreateWindow(WINDOW_CLASS_NAME, // class
			    "Mikey's mandel",	 // title
			    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
			    0,0,	   // x,y
			    WINDOW_WIDTH,  // width
			    WINDOW_HEIGHT, // height
			    NULL,	   // handle to parent 
			    NULL,	   // handle to menu
			    hinstance,// instance
			    NULL)))	// creation parms
    return(0);

  // save the window handle in a global
  main_window_handle = hwnd;

  // make a table of colors
  // RGB(red,green,blue), each intensity runs from 0 to 255
#define NCOLORS 8
  COLORREF mycolors[NCOLORS];
  HPEN mypen[NCOLORS],mybrush[NCOLORS];
  mycolors[0]=RGB(0,0,0);
  mycolors[1]=RGB(0,0,255);
  mycolors[2]=RGB(0,255,0);
  mycolors[3]=RGB(0,255,255);
  mycolors[4]=RGB(255,0,0);
  mycolors[5]=RGB(255,0,255);
  mycolors[6]=RGB(255,255,0);
  mycolors[7]=RGB(255,255,255);
  int i;
  for (i=0; i<NCOLORS; i++){ 
    mypen[i]=CreatePen(PS_SOLID, 0, mycolors[i]);
    mybrush[i]=CreateSolidBrush(mycolors[i]);
  }
  int colors[WINDOW_WIDTH];
    
  // get the graphics device context 
  hdc = GetDC(hwnd);

  // main loop
  while(1)
    {
      int handlemessage=0;
      if (displayed) { // wait for a new message
	GetMessage(&msg,NULL,0,0);
	handlemessage=1;
      }
      else  //check for a new message
	handlemessage=PeekMessage(&msg,NULL,0,0,PM_REMOVE);
      if (handlemessage) { 
	// if a quit message get out of while(1)
	if (msg.message == WM_QUIT)
	  break;
	// translate any accelerator keys
	TranslateMessage(&msg);
	// send the message to the window proc
	DispatchMessage(&msg);
      } // end message handling

      // main processing goes here /////////////////////

      if (!displayed) {   // draw one line of the mandelbrot set
	if (iy==0) {
          // make background
          SelectObject(hdc,mypen[7]);
          SelectObject(hdc,mybrush[7]);
          Rectangle(hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
          SelectObject(hdc,mypen[0]);
          SelectObject(hdc,mybrush[0]);
          Rectangle(hdc,BORDER-3,BORDER-3,
                BORDER+IMAGE_WIDTH+2,BORDER+IMAGE_HEIGHT+3);

          // display text messages
          SetBkMode(hdc,OPAQUE);
          SetTextColor(hdc,RGB(255,0,0));
          SetBkColor(hdc,RGB(255,255,255));
          int length = sprintf(stringbuffer,
            "Left mouse button zooms in by factor of 2, centered on mouse");
          TextOut(hdc,20,2*BORDER+IMAGE_HEIGHT,stringbuffer,length);
          length = sprintf(stringbuffer,
            "    Right button zooms out");
          TextOut(hdc,20,2*BORDER+IMAGE_HEIGHT+16,stringbuffer,length);
          length = sprintf(stringbuffer,
            "xmin = %g ,  xmax-xmin = %g       ",xmin,xmax-xmin);
          TextOut(hdc,20,2*BORDER+IMAGE_HEIGHT+32,stringbuffer,length);
          length = sprintf(stringbuffer,
            "ymin = %g ,  ymax-ymin = %g       ",ymin,ymax-ymin);
          TextOut(hdc,20,2*BORDER+IMAGE_HEIGHT+48,stringbuffer,length);
	}

        /* calculate colors for current row */
	double x, y;
	y=ymin + iy*dy;
        int ix,k;
	for (ix=0; ix<WINDOW_WIDTH; ix++) {
	  x=xmin+ix*dx;
	  double zr=0., zi=0.;
	  for (k=0;k<MAXITER;k++) {
	    double temp=zr;
            zr=zr*zr-zi*zi+x;
	    zi=2*temp*zi+y;
	    if (fabs(zr)+fabs(zi)>=8.) break; 
	  }
	  if (k==MAXITER) k=0; // black center
	  colors[ix]=k%NCOLORS;
        // SetPixel(hdc, ix, iy, mycolors[k%NCOLORS]);
	}

	/* now display the row */
        /* I draw line segments instead of individual pixels
           with the hope that it is somewhat faster */
        MoveToEx(hdc,BORDER,BORDER+iy,NULL); // move pen to start of row
	int currentcolor=colors[0];
	SelectObject(hdc,mypen[currentcolor]);
	for (ix=0; ix<IMAGE_WIDTH; ix++) {
	  if (colors[ix] != currentcolor) {
	    LineTo(hdc,BORDER+ix-1,BORDER+iy);
	    currentcolor=colors[ix];
	    SelectObject(hdc,mypen[currentcolor]);
	  }
	}
	LineTo(hdc,BORDER+IMAGE_WIDTH-1,BORDER+iy);

	/* increment row */
	iy++;
        if (iy>=IMAGE_HEIGHT){ // we are done drawing
          displayed=1;
	  iy=0;
	}    
      }
    } // end while

  // release the device context and the pens
  ReleaseDC(hwnd,hdc);
  for (i=0; i<NCOLORS; i++) {
    DeleteObject(mypen[i]);
    DeleteObject(mybrush[i]);
  }

  // return to Windows 
  return(msg.wParam);
} // end WinMain