/*   Copyright 2006 Owen Piette
 *   See license.txt for terms.
 */


#include <stdio.h>
#include <iostream>
#include <string.h>

#include "Sand-4.h"
#include <wx/wx.h>
#include <wx/dcscreen.h>
#include <wx/dcbuffer.h>
#include <wx/datetime.h>
#include <wx/file.h>
#include <wx/image.h>
#include <wx/thread.h>
#include <wx/event.h>
#include <wx/menuitem.h>

#define TIMERINTERVAL 33
#define LEFTRIGHTPROB 0.9

#define MAXNUMBEROFELEMENTS 128

#define found(center, other, cx, cy, ox, oy){\
  char t = data[center];\
  data[center] = data[other];\
  data[other] = t;\
  draw_x[numberToDraw] = cx;\
  draw_y[numberToDraw] = cy;\
  ++numberToDraw;\
  draw_x[numberToDraw] = ox;\
  draw_y[numberToDraw] = oy;\
  ++numberToDraw;\
  calc[center] = true;\
  if (t != 0)\
    calc[other] = true;\
};

BEGIN_EVENT_TABLE(Canvas, wxWindow)
  //EVT_PAINT(Canvas::OnPaint)
  EVT_ERASE_BACKGROUND(Canvas::OnEraseBG)
  EVT_RIGHT_DOWN(Canvas::OnMouseRightDown)
  EVT_LEFT_DOWN(Canvas::OnMouseLeftDown)
  EVT_LEFT_UP(Canvas::OnMouseLeftUp)
  EVT_MOTION(Canvas::OnMouseMove)
  EVT_TIMER(999, Canvas::OnTimer)
  EVT_TIMER(998, Canvas::OnSecondTimer)
  EVT_IDLE(Canvas::OnIdle)
END_EVENT_TABLE()


BEGIN_EVENT_TABLE(MainFrame, wxFrame)
  EVT_SET_FOCUS(MainFrame::OnMove)
  EVT_SIZE(MainFrame::OnSize)
  EVT_LISTBOX(1000, MainFrame::OnChoice)
  EVT_LISTBOX(1001, MainFrame::OnChoice)

  EVT_BUTTON(1025, MainFrame::OnButton)

  EVT_MENU(1002, MainFrame::OnMenu)
  EVT_MENU(1003, MainFrame::OnMenu)
  EVT_MENU(1004, MainFrame::OnMenu)
  EVT_MENU(1005, MainFrame::OnMenu)
  EVT_MENU(1006, MainFrame::OnMenu)
  EVT_MENU(1007, MainFrame::OnMenu)

  EVT_MENU(1050, MainFrame::OnMenu)
  EVT_MENU(1051, MainFrame::OnMenu)
  EVT_MENU(1052, MainFrame::OnMenu)
  EVT_MENU(1053, MainFrame::OnMenu)
  EVT_MENU(1054, MainFrame::OnMenu)
  EVT_MENU(1055, MainFrame::OnMenu)
  EVT_MENU(1056, MainFrame::OnMenu)
END_EVENT_TABLE()

IMPLEMENT_APP(Sand)

#define EMPTY 0
#define WALL 1
#define FIRE 2
#define WATER 3
#define PLANT 4
#define SAND 5
#define SPOUT 6
#define CERA 7
#define Q 8
#define OIL 9
#define SALT 10
#define EMBER 11
#define FCERA 12
#define STEAM 13
#define SALTWATER 14
#define TORCH 15
#define PASTQ 16

double deathrate[MAXNUMBEROFELEMENTS]= {0,     0,     0.05,   
				     0,     0,     0, 
				     0,     0,     0.1,   
				     0,     0,     0.002,   
				     0,     0.001, 0, 
				     0,     0.005};

double gravity[MAXNUMBEROFELEMENTS] =  {0,     0,     -1.0, 
				     0.7,   0,     0.9, 
				     0,     0,     0, 
				     0.7,   0.9,   0, 
				     0.8,   -1.0,  0.7, 
				     0,     0};

double slip[MAXNUMBEROFELEMENTS] =     {1,     0,     1,   
				     1,     0,     0.5,
				     0,     0,     1,   
				     1,     0.4,   0, 
				     0.2,   1,     1,
				     0,     1};

double density[MAXNUMBEROFELEMENTS] =  {0,     1,     0,
				     0.5,   1,     0.9,
				     1,     1,     1,
				     0.2,   0.9,   1,
				     1,     0.01,  0.5,
				     1,     1};

wxString names[MAXNUMBEROFELEMENTS] = {_("Eraser"), _("Wall"),   _("Fire"),   
				       _("Water"),  _("Plant"),  _("Sand"),  
				       _("Spout"),  _("Cera"),   _("???"), 
				       _("Oil"),    _("Salt"),   _("Ember"), 
				       _("MoltenCera"), _("Steam"), _("Saltwater"), 
				       _("Torch"), _("Leftover???")};

wxColor colors[MAXNUMBEROFELEMENTS] = {wxColor(0,0,0),       wxColor(128,128,128), wxColor(247,63,63),
				    wxColor(32,32,255),   wxColor(32,204,32),   wxColor(238,204,128),
				    wxColor(10,100,10),   wxColor(238,221,204), wxColor(231,7,231),
				    wxColor(128,64,64),   wxColor(255,255,255), wxColor(200,50,50), 
				    wxColor(255,220,200), wxColor(85,85,255),   wxColor(0,0,150), 
                                    wxColor(100,0,0),     wxColor(200,0,0)};

wxPen pens[MAXNUMBEROFELEMENTS] = {
  wxPen(wxColor(0,0,0), 1, wxSOLID),       wxPen(wxColor(128,128,128), 1, wxSOLID), wxPen(wxColor(247,63,63), 1, wxSOLID),
  wxPen(wxColor(32,32,255), 1, wxSOLID),   wxPen(wxColor(32,204,32), 1, wxSOLID),   wxPen(wxColor(238,204,128), 1, wxSOLID),
  wxPen(wxColor(10,100,10), 1, wxSOLID),   wxPen(wxColor(238,221,204), 1, wxSOLID), wxPen(wxColor(231,7,231), 1, wxSOLID),
  wxPen(wxColor(128,64,64), 1, wxSOLID),   wxPen(wxColor(255,255,255), 1, wxSOLID), wxPen(wxColor(200,50,50), 1, wxSOLID), 
  wxPen(wxColor(255,220,200), 1, wxSOLID), wxPen(wxColor(85,85,255), 1, wxSOLID),   wxPen(wxColor(0,0,150), 1, wxSOLID), 
  wxPen(wxColor(100,0,0), 1, wxSOLID),     wxPen(wxColor(200,0,0), 1, wxSOLID)};


int trans_death[MAXNUMBEROFELEMENTS]= {0, 0, 0, 0, 0, 0, 0, 0, PASTQ, 0, 0, 0, 0, WATER, 0, 0, 0};
double trans_prob[MAXNUMBEROFELEMENTS][MAXNUMBEROFELEMENTS];
char trans_to[MAXNUMBEROFELEMENTS][MAXNUMBEROFELEMENTS];
char trans_from[MAXNUMBEROFELEMENTS][MAXNUMBEROFELEMENTS];


int numberOfElements;

MainFrame* g_mainFrame;
Canvas* g_canvas;
char data[3*1280*1024];
//unsigned char draw[3*1280*1024];
unsigned char calc[3*1280*1024];
unsigned char bitmapdata[1280*1024*4];

unsigned int numberToDraw;
int draw_x[3*1280*1024];
int draw_y[3*1280*1024];

wxMenuItem* wallsCB;
wxMenuItem* sourcesCB;
wxMenuItem* drawCB;
wxMenuItem* gravityCB;
wxMenuItem* interactionsCB;
wxMenuItem* limitCB;

bool doWalls;
bool doSources;
bool doDraw;
bool doGravity;
bool doInteractions;
bool doLimit;

bool drawAll;
wxString sandboxFilename;
wxString physicsFilename;
wxListBox* penSelections;
bool pauseDrawing;
wxTimer* g_timer;
wxTimer* g_timer_second;
bool mouseIsDown;
int mousex, mousey;
char sand_type;
int pen_width;
wxStatusBar* statusbar;
double previous_now;
int g_width;
int g_height;
bool walls;
bool sources;
wxTimerEvent g_e;

int frameCount;
double lastCalculateTime;

Canvas::Canvas(wxWindow* parent, wxWindowID id = -1, wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize) : wxWindow( parent, id, pos, size, wxCLIP_CHILDREN ){
  g_canvas = this;

  this->SetSizeHints(g_width,g_height,g_width,g_height);
  this->SetBackgroundColour(wxColor(_("BLACK")));
  sand_type = 1;
  pen_width = 32;
  lastCalculateTime = 0;
  numberToDraw = 0;
  drawAll = true;

  memset(data, 0, g_width*g_height*sizeof(char));
  memset(calc, false, g_width*g_height*sizeof(char));

  for(int i=0;i<MAXNUMBEROFELEMENTS;++i){
    for(int j=0;j<MAXNUMBEROFELEMENTS;++j){
      trans_prob[i][j] = 0;
      trans_to[i][j] = 0;
      trans_from[i][j] = 0;
    }
  }

  //Torch
  trans_prob[TORCH][EMPTY] = 0.2;
  trans_to[TORCH][EMPTY] = TORCH;
  trans_from[TORCH][EMPTY] = FIRE;
 
  trans_prob[TORCH][WATER] = 0.2;
  trans_to[TORCH][WATER] = TORCH;
  trans_from[TORCH][WATER] = STEAM;
 
  trans_prob[TORCH][WATER] = 0.2;
  trans_to[TORCH][WATER] = TORCH;
  trans_from[TORCH][WATER] = STEAM;

  trans_prob[TORCH][SALTWATER] = 0.2;
  trans_to[TORCH][SALTWATER] = TORCH;
  trans_from[TORCH][SALTWATER] = STEAM;

  trans_prob[SALTWATER][TORCH] = 0.2;
  trans_to[SALTWATER][TORCH] = SALT;
  trans_from[SALTWATER][TORCH] = TORCH;

  trans_prob[TORCH][OIL] = 0.2;
  trans_to[TORCH][OIL] = TORCH;
  trans_from[TORCH][OIL] = FIRE;

  trans_prob[TORCH][PLANT] = 0.2;
  trans_to[TORCH][PLANT] = TORCH;
  trans_from[TORCH][PLANT] = FIRE;

  trans_prob[TORCH][CERA] = 0.005;
  trans_to[TORCH][CERA] = TORCH;
  trans_from[TORCH][CERA] = FCERA;

  //saltwater
  trans_prob[WATER][SALT] = 0.004;
  trans_to[WATER][SALT] = SALTWATER;
  trans_from[WATER][SALT] = SALTWATER;

  trans_prob[SALTWATER][SALTWATER] = 0.00001;
  trans_to[SALTWATER][SALTWATER] = SALT;
  trans_from[SALTWATER][SALTWATER] = WATER;

  trans_prob[SALTWATER][WATER] = 0.04;
  trans_to[SALTWATER][WATER] = WATER;
  trans_from[SALTWATER][WATER] = SALTWATER;

  trans_prob[SALTWATER][FIRE] = 0.9;
  trans_to[SALTWATER][FIRE] = SALT;
  trans_from[SALTWATER][FIRE] = STEAM;

  //embers
  trans_prob[EMBER][EMPTY] = 0.2;
  trans_to[EMBER][EMPTY] = EMBER;
  trans_from[EMBER][EMPTY] = FIRE;

  trans_prob[EMBER][WATER] = 0.9;
  trans_to[EMBER][WATER] = EMPTY;
  trans_from[EMBER][WATER] = STEAM;

  trans_prob[EMBER][OIL] = 0.75;
  trans_to[EMBER][OIL] = EMBER;
  trans_from[EMBER][OIL] = FIRE;

  trans_prob[EMBER][PLANT] = 1;
  trans_to[EMBER][PLANT] = EMBER;
  trans_from[EMBER][PLANT] = FIRE;

  trans_prob[EMBER][CERA] = 0.005;
  trans_to[EMBER][CERA] = FCERA;
  trans_from[EMBER][CERA] = EMBER;

  //fire
  trans_prob[FIRE][FIRE] = 0.0005;
  trans_to[FIRE][FIRE] = EMPTY;
  trans_from[FIRE][FIRE] = EMPTY;

  trans_prob[FIRE][WATER] = 0.9;
  trans_to[FIRE][WATER] = EMPTY;
  trans_from[FIRE][WATER] = STEAM;

  trans_prob[FIRE][OIL] = 0.75;
  trans_to[FIRE][OIL] = FIRE;
  trans_from[FIRE][OIL] = FIRE;

  trans_prob[FIRE][PLANT] = 1;
  trans_to[FIRE][PLANT] = FIRE;
  trans_from[FIRE][PLANT] = FIRE;

  trans_prob[FIRE][CERA] = 0.005;
  trans_to[FIRE][CERA] = FCERA;
  trans_from[FIRE][CERA] = EMBER;

  //fcera turns back into cera.
  trans_prob[FCERA][CERA] = 0.01;
  trans_to[FCERA][CERA] = CERA;
  trans_from[FCERA][CERA] = CERA;

  trans_prob[FCERA][WALL] = 0.001;
  trans_to[FCERA][WALL] = CERA;
  trans_from[FCERA][WALL] = WALL;

  trans_prob[FCERA][WATER] = 0.001;
  trans_to[FCERA][WATER] = CERA;
  trans_from[FCERA][WATER] = WATER;

  trans_prob[FCERA][PLANT] = 0.001;
  trans_to[FCERA][PLANT] = CERA;
  trans_from[FCERA][PLANT] = PLANT;

  trans_prob[FCERA][SAND] = 0.001;
  trans_to[FCERA][SAND] = CERA;
  trans_from[FCERA][SAND] = SAND;

  trans_prob[FCERA][OIL] = 0.001;
  trans_to[FCERA][OIL] = CERA;
  trans_from[FCERA][OIL] = OIL;

  trans_prob[FCERA][SPOUT] = 0.001;
  trans_to[FCERA][SPOUT] = CERA;
  trans_from[FCERA][SPOUT] = SPOUT;

  //plant
  trans_prob[PLANT][WATER] = 0.2;
  trans_to[PLANT][WATER] = PLANT;
  trans_from[PLANT][WATER] = PLANT;

  //spout
  trans_prob[SPOUT][EMPTY] = 0.75;
  trans_to[SPOUT][EMPTY] = SPOUT;
  trans_from[SPOUT][EMPTY] = WATER;

  trans_prob[SPOUT][SAND] = 0.5;
  trans_to[SPOUT][SAND] = EMPTY;
  trans_from[SPOUT][SAND] = EMPTY;

  //???
  trans_prob[Q][EMPTY] = 0.33;
  trans_to[Q][EMPTY] = Q;
  trans_from[Q][EMPTY] = Q;
  
  trans_prob[Q][PASTQ] = 0.01;
  trans_to[Q][PASTQ] = PASTQ;
  trans_from[Q][PASTQ] = PASTQ;

  trans_prob[Q][SPOUT] = 0.2;
  trans_to[Q][SPOUT] = Q;
  trans_from[Q][SPOUT] = Q;
  
  trans_prob[Q][SALT] = 0.2;
  trans_to[Q][SALT] = Q;
  trans_from[Q][SALT] = Q;
  
  trans_prob[Q][WATER] = 0.2;
  trans_to[Q][WATER] = Q;
  trans_from[Q][WATER] = Q;
  
  trans_prob[Q][OIL] = 0.2;
  trans_to[Q][OIL] = Q;
  trans_from[Q][OIL] = Q;
  
  trans_prob[Q][FCERA] = 0.2;
  trans_to[Q][FCERA] = Q;
  trans_from[Q][FCERA] = Q;
  
  trans_prob[Q][CERA] = 0.2;
  trans_to[Q][CERA] = Q;
  trans_from[Q][CERA] = Q;
  
  trans_prob[Q][WALL] = 0.2;
  trans_to[Q][WALL] = Q;
  trans_from[Q][WALL] = Q;
  
  trans_prob[Q][SAND] = 0.2;
  trans_to[Q][SAND] = Q;
  trans_from[Q][SAND] = Q;
  

  /*
  trans_prob[][] = 0;
  trans_to[][] = ;
  trans_from[][] = ;

  */

}



bool inline checkForTransformation(int center, int other, int cx, int cy, int ox, int oy){
  char t1 = data[center];
  char t2 = data[other];
  if (trans_prob[t1][t2] == 0) return false;
  if (rand() < trans_prob[t1][t2]*RAND_MAX){
    data[center] = trans_to[t1][t2];
    data[other] = trans_from[t1][t2];
    draw_x[numberToDraw] = cx;
    draw_y[numberToDraw] = cy;
    ++numberToDraw;
    draw_x[numberToDraw] = ox;
    draw_y[numberToDraw] = oy;
    ++numberToDraw;
    //draw[center] = true;
    //draw[other] = true;
    calc[center] = true;
    calc[other] = true;
    return true;
  }
  return false;
}



void calculate(){
  lastCalculateTime = wxDateTime::UNow().GetMinute()*60*1000 + wxDateTime::UNow().GetSecond()*1000 + wxDateTime::UNow().GetMillisecond();


  if (pauseDrawing == true)
    return;

  pauseDrawing = true;

  ++frameCount;

  if (doSources){
    int offset = g_width*50;
    int space = g_width/5;

    for(int i=0;i<10;++i){
      if (rand() < RAND_MAX/4){
	data[offset+space+i] = SAND;
	data[offset+2*space+i] = WATER;
	data[offset+3*space+i] = SALT;
	data[offset+4*space+i] = OIL;
      }
    }
  }
  

  
  if (mouseIsDown){
    for(int dx=-pen_width/2;dx<pen_width/2;++dx){
      int x = mousex+dx;
      int dy = (int)round(sqrt(pen_width/2 - dx*dx));
      for(int y=mousey-dy;y<mousey+dy;++y){
	if (x > 0 && x < g_width && y > 0 && y < g_height){
	  data[(g_width*y)+x] = sand_type;
	  draw_x[numberToDraw] = x;
	  draw_y[numberToDraw] = y;
	  ++numberToDraw;
	  //draw[(g_width*y)+x] = true;
	}
      }
    }
  }
  


  //int max=g_width*g_height;
  //for(int center=max-1;center>=0;--center){
  for(int y=g_height-1;y>0;--y){
    for(int x=1;x<g_width-1;++x){

    int center = (g_width*y)+x;

    if (data[center] == 0)
      continue;
    if (calc[center])
      continue;
    
    
    if (deathrate[data[center]] > 0){
      if (rand() < deathrate[data[center]] * RAND_MAX){
	data[center] = trans_death[data[center]];
	draw_x[numberToDraw] = x;
	draw_y[numberToDraw] = y;
	++numberToDraw;
	//draw[center] = true;
	continue;
      }
    }
    
    int left = center-1;
    int right = center+1;
    int up = center - g_width;
    int down = center + g_width;
    int downleft = center + g_width - 1;
    int downright = center + g_width + 1;
    int upleft = center - g_width - 1;
    int upright = center - g_width + 1;
    
    if (doInteractions){
      if (rand() > 0.5*RAND_MAX){
	//Check for specific transformations.
	//if(checkForTransformation(center, upleft)) continue;
	if(checkForTransformation(center, left, x, y, x-1, y)) continue;
	//if(checkForTransformation(center, downleft)) continue;
	if(checkForTransformation(center, down, x, y, x, y+1)) continue;
	//if(checkForTransformation(center, downright)) continue;
	if(checkForTransformation(center, right, x, y, x+1, y)) continue;
	//if(checkForTransformation(center, upright)) continue;
	if(checkForTransformation(center, up, x, y, x, y-1)) continue;
      }
      else{
	//Check for specific transformations.
	//if(checkForTransformation(center, up)) continue;
	if(checkForTransformation(center, upright, x, y, x+1, y-1)) continue;
	//if(checkForTransformation(center, right)) continue;
	if(checkForTransformation(center, downright, x, y, x+1, y+1)) continue;
	//if(checkForTransformation(center, down)) continue;
	if(checkForTransformation(center, downleft, x, y, x-1, y+1)) continue;
	//if(checkForTransformation(center, left)) continue;
	if(checkForTransformation(center, upleft, x, y, x-1, y-1)) continue;
      }
    }
    
    
    
    if (doGravity){
      
      if (gravity[data[center]] == 0)
	continue;
      
      if (gravity[data[center]] > 0){
	//Check down
	//falling
	if (data[down] == 0){
	  if (rand() < gravity[data[center]]*RAND_MAX){
	    found(center, down, x, y, x, y+1);
	    continue;
	  }
	}
	else{
	  //density
	  if (density[data[center]] > density[data[down]]){
	    if (rand() < gravity[data[center]]*0.5*RAND_MAX ){
	      found(center, down, x, y, x, y+1);
	      continue;
	    }
	  }
	}
	
      
	if (rand() < 0.5*RAND_MAX){ //left versus right.
	  //Check down and left
	  //falling
	  if (data[downleft] == 0){
	    if (data[down] == 0){//no friction.
	      if (rand() < LEFTRIGHTPROB*gravity[data[center]]*RAND_MAX){
		found(center, downleft, x, y, x-1, y+1);
		continue;
	      }
	    }
	    else{
	      if (rand() < LEFTRIGHTPROB*gravity[data[center]]*slip[data[center]]*RAND_MAX){
		found(center, downleft, x, y, x-1, y+1);
		continue;
	      }
	    }
	  }
	  else{
	    //density
	    if (density[data[center]] > density[data[downleft]]){
	      if ( rand() < LEFTRIGHTPROB*gravity[data[center]]*0.5*RAND_MAX ){
		found(center, downleft, x, y, x-1, y+1);
		continue;
	      }
	    }
	  }
	}
	else{//left versus right.
	  //Check down and right
	  //falling
	  if (data[downright] == 0){
	    if (data[down] == 0){//no friction.
	      if (rand() < LEFTRIGHTPROB*gravity[data[center]]*RAND_MAX){
		found(center, downright, x, y, x+1, y+1);
		continue;
	      }
	    }
	    else{
	      if (rand() < LEFTRIGHTPROB*slip[data[center]]*gravity[data[center]]*RAND_MAX){
		found(center, downright, x, y, x+1, y+1);
		continue;
	      }
	    }
	  }
	  else{
	    //density
	    if (density[data[center]] > density[data[downright]]){
	      if ( rand() < LEFTRIGHTPROB*gravity[data[center]]*0.5*RAND_MAX ){
		found(center, downright, x, y, x+1, y+1);
		continue;
	      }
	    }
	  }
	}
      }
      else{
	//gravity < 0

	//up or to the side?
	if (rand() < 0.7*RAND_MAX){
	  //Check up
	  if (data[up] == 0){
	    if (rand() < -gravity[data[center]]*RAND_MAX){
	      found(center, up, x, y, x, y-1);
	      continue;
	    }
	  }
	}
	else{
	//Check up and left.
	  if (rand() < 0.5*RAND_MAX){ //left versus right.
	    //floating
	    if (data[upleft] == 0){
	      if (rand() < -gravity[data[center]]*RAND_MAX){
		found(center, upleft, x, y, x-1, y-1);
		continue;
	      }
	    }
	  }
	  else{	   //left versus right.
	    //Check up and right.
	    if (data[upright] == 0){
	      if (rand() < -gravity[data[center]]*RAND_MAX){
		found(center, upright, x, y, x+1, y-1);
		continue;
	      }
	    }
	  }
	}
      }

    
      if (rand() < 0.5*RAND_MAX){ //left versus right.
	//Check left.

	//Random opening.
	if (data[left] == 0){
	  if (rand() < LEFTRIGHTPROB*gravity[data[center]]*RAND_MAX){
	    found(center, left, x, y, x-1, y);
	    continue;
	  }
	  
	  //Check for being pushed to the side.
	  if (data[right] > 0){
	    if (rand() < LEFTRIGHTPROB*slip[data[center]]*RAND_MAX){
	      found(center, left, x, y, x-1, y);
	      continue;
	    }
	    continue;
	  }
	}

	//density
	if (density[data[center]] > density[data[left]]){
	  if ( rand() < 0.001*0.5*RAND_MAX ){
	    found(center, left, x, y, x-1, y);
	    continue;
	  }
	}
      }
      else{
	//Check right.

	//Random opening.
	if (data[right] == 0){
	  if (rand() < LEFTRIGHTPROB*gravity[data[center]]*RAND_MAX){
	    found(center, right, x, y, x+1, y);
	    continue;
	  }
	  
	  //Check for being pushed to the side.
	  if (data[left] > 0){
	    if (rand() < LEFTRIGHTPROB*slip[data[center]]*RAND_MAX){
	      found(center, right, x, y, x+1, y);
	      continue;
	    }
	    continue;
	  }
	}

	//density
	if (density[data[center]] > density[data[right]]){
	  if ( rand() < 0.001*0.5*RAND_MAX ){
	    found(center, right, x, y, x+1, y);
	    continue;
	  }
	}
      }
    }
    }
  }

  pauseDrawing = false;
  g_canvas->Refresh();
}


void Canvas::Refresh(){
  ::wxYield();
  if (pauseDrawing == true)
    return;
  pauseDrawing = true;

  char drawee = 0;
  if (doWalls){
    drawee = WALL;
  }

  for(int x=0;x<g_width;++x){
    data[(0*g_width)+x] = drawee;
    data[((g_height-1)*g_width)+x] = drawee;
    draw_x[numberToDraw] = x;
    draw_y[numberToDraw] = 0;
    ++numberToDraw;
    draw_x[numberToDraw] = x;
    draw_y[numberToDraw] = g_height-1;
    ++numberToDraw;
    //draw[((g_height-1)*g_width)+x] = true;
    //draw[(0*g_width)+x] = true;
  }
  for(int y=0;y<g_height;++y){
    data[(y*g_width)] = drawee;
    data[(y*g_width)+g_width-1] = drawee;
    draw_x[numberToDraw] = 0;
    draw_y[numberToDraw] = y;
    ++numberToDraw;
    draw_x[numberToDraw] = g_width-1;
    draw_y[numberToDraw] = y;
    ++numberToDraw;
    //draw[(y*g_width)] = true;
    //draw[(y*g_width)+g_width-1] = true;
  }


  if (doDraw){
    wxClientDC dc(this);
    dc.BeginDrawing();
    
    if (drawAll){
      for(int center=0;center<g_width*g_height;++center){
	bitmapdata[(center*3)+0] = colors[data[center]].Red();
	bitmapdata[(center*3)+1] = colors[data[center]].Green();
	bitmapdata[(center*3)+2] = colors[data[center]].Blue();
      }
      drawAll = false;
    }
    else{
      for(int i=0;i<numberToDraw;++i){
	int center = (draw_y[i]*g_width)+draw_x[i];
	bitmapdata[(center*3)+0] = colors[data[center]].Red();
	bitmapdata[(center*3)+1] = colors[data[center]].Green();
	bitmapdata[(center*3)+2] = colors[data[center]].Blue();
      }
    }
    

    wxMemoryDC memdc;
    wxImage image(g_width, g_height, bitmapdata, true);
    wxBitmap bmp(image);
    memdc.SelectObject(bmp);
    dc.Blit(0,0,g_width, g_height, &memdc, 0, 0);

    
    dc.EndDrawing();
  }

  numberToDraw = 0;
  memset(calc, false, g_width*g_height*sizeof(unsigned char));

  pauseDrawing = false;
}

void Canvas::OnIdle(wxIdleEvent& e){
  if (doLimit == false){
    calculate();
    e.RequestMore(true);
    ::wxWakeUpIdle();
  }
}

void Canvas::OnTimer(wxTimerEvent& event){
  if (doLimit == true){
    calculate();
  }
}

void Canvas::OnSecondTimer(wxTimerEvent& event){
  wxString str = _("");
  str.Printf("%d fps", frameCount);
  frameCount = 0;
  statusbar->SetStatusText(str);
}

void Canvas::OnEraseBG(wxEraseEvent& e){
  //Do absolutely nothing, thereby halting this event.
  //memset(draw, true, g_width*g_height*sizeof(unsigned char));
  drawAll = true;
  this->Refresh();
}


void drawCircle(int centerx, int centery){
  for(int dx=-pen_width/2;dx<pen_width/2;++dx){
    int x = centerx+dx;
    int dy = (int)round(sqrt(pen_width/2 - dx*dx));
    for(int y=centery-dy;y<centery+dy;++y){
      if (x > 0 && x < g_width && y > 0 && y < g_height){
	data[(g_width*y)+x] = sand_type;
	draw_x[numberToDraw] = x;
	draw_y[numberToDraw] = y;
	++numberToDraw;
	//draw[(g_width*y)+x] = true;
      }
    }
  }
}

void drawLine(int x2, int y2, int x1, int y1){
  int dx = x1 - x2;
  int dy = y1 - y2;

  int t_max = abs(dx);
  if (abs(dy) > abs(dx))
    t_max = abs(dy);

  if (t_max == 0){
    drawCircle(x1, y1);
  }
  else{
    double ddx = double(dx)/double(t_max);
    double ddy = double(dy)/double(t_max);
    
    double x=x2;
    double y=y2;
    for(int t=0;t<=t_max;++t){
      drawCircle((int)round(x),(int)round(y));
      x += ddx;
      y += ddy;
    }
  }
}

void Canvas::OnMouseLeftDown(wxMouseEvent& event){
  mouseIsDown = true;
  mousex = event.GetX();
  mousey = event.GetY();

  drawCircle(mousex, mousey);
}

void Canvas::OnMouseLeftUp(wxMouseEvent& event){
  mouseIsDown = false;
}


void Canvas::OnMouseRightDown(wxMouseEvent& event){
  int oldmousex = mousex;
  int oldmousey = mousey;

  mousex = event.GetX();
  mousey = event.GetY();

  drawLine(oldmousex, oldmousey, mousex, mousey);

}

void Canvas::OnMouseMove(wxMouseEvent& event){
  if (event.LeftIsDown()){
    int oldmousex = mousex;
    int oldmousey = mousey;

    mousex = event.GetX();
    mousey = event.GetY();


    drawLine(oldmousex, oldmousey, mousex, mousey);
  }
  else
    mouseIsDown = false;
}

void Canvas::OnPaint(wxPaintEvent& event){
  //memset(draw, true, g_width*g_height*sizeof(unsigned char));
  drawAll = true;
  this->Refresh();
}


bool OnNew(wxWindow* window){
  //New
  wxString choices[] = {_("320x240 (fastest)"), _("640x480"), _("800x600"), _("1024x768"), _("1280x1024 (slowest)")};
  wxSingleChoiceDialog dialog(NULL, _("Select an area size"), _("New"), 5, choices, NULL);
  
  if (dialog.ShowModal() == wxID_OK){
    if (dialog.GetSelection() == 0){
      g_width = 320;
      g_height = 240;
    }
    else if (dialog.GetSelection() == 1){
      g_width = 640;
      g_height = 480;
    }
    else if (dialog.GetSelection() == 2){
      g_width = 800;
      g_height = 600;
    }
    else if (dialog.GetSelection() == 3){
      g_width = 1024;
      g_height = 768;
    }
    else if (dialog.GetSelection() == 4){
      g_width = 1280;
      g_height = 1024;
    }
    return true;
  }

  return false;
}


MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size, 
		     long style) : wxFrame(NULL, -1, title, pos, size, style){
  g_mainFrame = this;

  //this->SetBackgroundColour(wxColor(100,100,100));

  wxMenu* fileMenu = new wxMenu;
  wxMenu* optionsMenu = new wxMenu;
  wxMenu* helpMenu = new wxMenu;
  
  fileMenu->Append(1050, _("New.."), _(""));
  fileMenu->AppendSeparator();
  fileMenu->Append(1051, _("Load sandbox.."), _(""));
  fileMenu->Append(1052, _("Save sandbox.."), _(""));
  fileMenu->AppendSeparator();
  fileMenu->Append(1055, _("Load physics.."), _(""));
  fileMenu->Append(1056, _("Save physics.."), _(""));
  fileMenu->AppendSeparator();
  fileMenu->Append(1053, _("Exit"), _(""));

  wallsCB = new wxMenuItem(optionsMenu, 1002, _("Boundary Walls"), _(""), wxITEM_CHECK);
  sourcesCB = new wxMenuItem(optionsMenu, 1003, _("Sources"), _(""), wxITEM_CHECK);
  drawCB = new wxMenuItem(optionsMenu, 1004, _("Paint Updates"), _(""), wxITEM_CHECK);
  gravityCB = new wxMenuItem(optionsMenu, 1005, _("Gravity"), _(""), wxITEM_CHECK);
  interactionsCB = new wxMenuItem(optionsMenu, 1006, _("Element Interactions"), _(""), wxITEM_CHECK);
  limitCB = new wxMenuItem(optionsMenu, 1007, _("Limit FPS"), _(""), wxITEM_CHECK);

  optionsMenu->Append(wallsCB);
  optionsMenu->Append(sourcesCB);
  optionsMenu->Append(drawCB);
  optionsMenu->Append(gravityCB);
  optionsMenu->Append(interactionsCB);
  optionsMenu->Append(limitCB);

  helpMenu->Append(1054, _("About"), _(""));

  wxMenuBar* menuBar = new wxMenuBar();
  menuBar->Append(fileMenu, _("File"));
  menuBar->Append(optionsMenu, _("Options"));
  menuBar->Append(helpMenu, _("Help"));

  SetMenuBar(menuBar);

  wallsCB->Check();
  sourcesCB->Check();
  drawCB->Check();
  gravityCB->Check();
  interactionsCB->Check();
  limitCB->Check();

  doWalls = wallsCB->IsChecked();
  doSources = sourcesCB->IsChecked();
  doDraw = drawCB->IsChecked();
  doGravity = gravityCB->IsChecked();
  doInteractions = interactionsCB->IsChecked();
  doLimit = limitCB->IsChecked();

  statusbar = this->CreateStatusBar();
  statusbar->Show(true);

  g_canvas = new Canvas(this, -1, wxDefaultPosition, wxSize(g_width, g_height));
  
  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
  {
    wxFlexGridSizer* RHSizer = new wxFlexGridSizer(2);
    {
      
      const wxString labels2[10] = {_("1"), _("2"), _("4"), _("8"), _("16"), _("32"), _("64"), _("128")};

      penSelections =new wxListBox(this, 1000, wxDefaultPosition, wxDefaultSize, numberOfElements, names, wxLB_SINGLE);
      wxListBox* lb2=new wxListBox(this, 1001, wxDefaultPosition, wxDefaultSize, 8, labels2, wxLB_SINGLE);

      penSelections->SetSelection(1);
      lb2->SetSelection(4);
      
      RHSizer->Add(new wxStaticText(this, -1, _("Element")));
      RHSizer->Add(new wxStaticText(this, -1, _("Pen Size")));
      RHSizer->Add(penSelections, 0, wxEXPAND);
      RHSizer->Add(lb2, 0, wxEXPAND);

      RHSizer->Add(new wxButton(this, 1025, _("Refresh\nReload"), wxDefaultPosition, wxSize(75,75)), 0, wxCENTER | wxALL, 25);
    }

    sizer->Add(g_canvas, 0, wxADJUST_MINSIZE|wxALL, 10);
    sizer->Add(RHSizer, 0);
  }
  this->SetSizer(sizer);
  
  this->Layout();
  sizer->Fit(this);

  pauseDrawing = false;

  g_timer = new wxTimer(g_canvas, 999);
  g_timer->Start(TIMERINTERVAL);

  g_timer_second = new wxTimer(g_canvas, 998);
  g_timer_second->Start(1000);

}

void loadSandbox(wxString filename){
  wxImage loadBitmap;
  loadBitmap.LoadFile(filename);
  g_width = loadBitmap.GetWidth();
  g_height = loadBitmap.GetHeight();
  drawAll = true;

  for(int y=0;y<g_height;++y){
    for(int x=0;x<g_width;++x){
      int i = (y*g_width)+x;
      
      data[i] = 0;
      //draw[i] = true;
      
      //Find the closest.
      unsigned int smallestDistance = 256+256+256;
      
      unsigned char red = loadBitmap.GetRed(x,y);
      unsigned char green = loadBitmap.GetGreen(x,y);
      unsigned char blue = loadBitmap.GetBlue(x,y);
      
      if (red == 0 && green == 0 && blue == 0)
	continue;
      
      for(int j=0;j<numberOfElements;++j){
	if (j == 8)
	  continue;
	unsigned int dist = 
	  int(abs(red - colors[j].Red())) + 
	  int(abs(green - colors[j].Green())) + 
	  int(abs(blue - colors[j].Blue()));
	if (dist < smallestDistance){
	  smallestDistance = dist;
	  data[i] = j;
	}
      }
    }
  }
  
  g_canvas->SetSizeHints(g_width,g_height,g_width,g_height);
  //g_canvas->SetClientSize(g_width,g_height);
  g_mainFrame->Layout();
  g_mainFrame->GetSizer()->Fit(g_mainFrame);

  //g_canvas->Refresh();
}

void loadPhysics(wxString filename){
  FILE* file = fopen(filename, "r");
  char line[200];
  
  int state = 0;//0=between elements, 1=have read an element.
  int elementNumber = -1;
  while(fgets(line, 200, file) != NULL){
    if (line[0] == '#'){
      //skip. comment.
    }
    else if (line[0] == ' ' || line[0] == '\t' || line[0] == '\n' || line[0] == '\r'){
      //Empty line.
      state = 0;
    }
    else{
      if (state == 0){
	++elementNumber;
	char name[128];
	sscanf(line, "%s\t", name);
	names[elementNumber] = wxString(name);
	state = 1;
      }
      else{
	//Just skip for now.
      }
    }
  }
  
  numberOfElements = elementNumber + 1;
  
  for(int i=0;i<MAXNUMBEROFELEMENTS;++i){
    for(int j=0;j<MAXNUMBEROFELEMENTS;++j){
      trans_from[i][j] = 0;
      trans_to[i][j] = 0;
      trans_prob[i][j] = 0;
    }
  }
  
  rewind(file);
  state = 0;
  elementNumber = -1;
  while(fgets(line, 200, file) != NULL){
    if (line[0] == '#'){
      //skip. comment.
    }
    else if (line[0] == ' ' || line[0] == '\t' || line[0] == '\n' || line[0] == '\r'){
      //Empty line.
      state = 0;
    }
    else{
      if (state == 0){
	++elementNumber;
	char name[128];
	int red;
	int green;
	int blue;
	char deathname[128];
	float d,s,g,dr;
	
	
	sscanf(line, "%s\t%d\t%d\t%d\t%f\t%f\t%f\t%s\t%f", 
	       &name, &red, &green, &blue, 
	       &d, &s, &g, 
	       &deathname, &dr);
	
	density[elementNumber] = (double)d;
	slip[elementNumber] = (double)s;
	gravity[elementNumber] = (double)g;
	deathrate[elementNumber] = (double)dr;
	
	colors[elementNumber] = wxColour(red, green, blue);
	pens[elementNumber] = wxPen(colors[elementNumber], 1, wxSOLID);
	
	trans_death[elementNumber] = 0;
	for(int i=0;i<numberOfElements;++i){
	  if (wxString(deathname) == names[i]){
	    trans_death[elementNumber] = i;
	  }
	}
	
	state = 1;
      }
      else{
	//Reading element interactions.
	char neighbor[128];
	char to[128];
	char from[128];
	float prob = 0;
	
	sscanf(line, "%s\t%s\t%s\t%f", &neighbor, &to, &from, &prob);
	
	int n_neighbor = -1;
	int n_to = -1;
	int n_from = -1;
	for(int i=0;i<numberOfElements;++i){
	  if (names[i] == wxString(neighbor))
	    n_neighbor = i;
	  if (names[i] == wxString(to))
	    n_to = i;
	  if (names[i] == wxString(from))
	    n_from = i;
	}
	
	if (n_neighbor != -1 && n_to != -1 && n_from != -1){
	  trans_to[elementNumber][n_neighbor] = n_to;
	  trans_from[elementNumber][n_neighbor] = n_from;
	  trans_prob[elementNumber][n_neighbor] = (double)prob;
	}

      }
    }
  }
  
  //Update selections.
  penSelections->Set(numberOfElements, names);
  
  
  fclose(file);

}

void MainFrame::OnMenu(wxCommandEvent& event){
  pauseDrawing = true;

  if (event.GetId() == 1050){
    OnNew(this);

    drawAll = true;
    //memset(draw, true, g_width*g_height*sizeof(unsigned char));
    memset(data, 0, g_width*g_height*sizeof(char));

    g_canvas->SetSizeHints(g_width,g_height,g_width,g_height);
    //g_canvas->SetClientSize(g_width,g_height);
    g_mainFrame->Layout();
    g_mainFrame->GetSizer()->Fit(g_mainFrame);
    
    g_canvas->Refresh();
  }
  else if(event.GetId() == 1051){
    //Load
    wxFileDialog dialog(this, _("Load from a file"), _(""), _(""), _("Any Image Files (*.*)|*.*"), wxOPEN);
    
    if (dialog.ShowModal() == wxID_OK){
      sandboxFilename = dialog.GetPath();
      loadSandbox(sandboxFilename);
    }
  }
  else if(event.GetId() == 1052){
    //Save
    wxFileDialog dialog(this, _("Save to a file"), _(""), _(""), _("PNG files (*.png)|*.png|BMP files (*.bmp)|*.bmp"), wxSAVE);
    
    if (dialog.ShowModal() == wxID_OK){
      wxString filename = dialog.GetPath();
      
      for(int i=0;i<g_width*g_height;++i){
	bitmapdata[(i*3)] = colors[data[i]].Red();
	bitmapdata[(i*3)+1] = colors[data[i]].Green();
	bitmapdata[(i*3)+2] = colors[data[i]].Blue();
      }
      wxImage saveBitmap(g_width, g_height, bitmapdata, true);
      wxBitmap bmp(saveBitmap);

      if (dialog.GetFilterIndex() == 0 || filename.find(_(".png")) != -1 || filename.find(_(".PNG")) != -1){
	bmp.SaveFile(filename, wxBITMAP_TYPE_PNG);
      }
      else{
	bmp.SaveFile(filename, wxBITMAP_TYPE_BMP);
      }
    }
  }
  else if(event.GetId() == 1053){
    //Exit
    g_mainFrame->Close();
  }
  else if(event.GetId() == 1054){
    //About
    wxMessageDialog dlg(this, _("Owen Piette's Falling Sand Game\nWritten by Owen Piette.\nThank you to Troy Larson for the refresh button idea.\nCheck for updates at:\nhttp://www.piettes.com/fallingsandgame/"), _("About"));
    dlg.ShowModal();
  }
  else if(event.GetId() == 1055){
    //Load physics

    wxFileDialog dialog(this, _("Load from a file"), _(""), _(""), _("Physics Files (*.txt)|*.txt"), wxOPEN);
    if (dialog.ShowModal() == wxID_OK){
      physicsFilename = dialog.GetPath();
      loadPhysics(physicsFilename);
    }
  }
  else if(event.GetId() == 1056){
    //Save physics

    wxFileDialog dialog(this, _("Save to a file"), _(""), _(""), _("Physics files (*.txt)|*.txt"), wxSAVE);
    
    if (dialog.ShowModal() == wxID_OK){
      wxString filename = dialog.GetPath();
      FILE* file = fopen(filename, "w");

      fprintf(file, "#wxSand: Owen Piette's Falling Sand Game\n#Version 4, File subversion 1\n");
      fprintf(file, "#Name\tRed\tGreen\tBlue\tdensity\tslip\tgravity\tdeath element\tdeathrate\n");
      fprintf(file, "#Neighbor\tThis becomes\tNeighbor becomes\tProbability\n\n");
      
      for(int i=0;i<numberOfElements;++i){
	fprintf(file, "%s\t%d\t%d\t%d\t%f\t%f\t%f\t%s\t%f\n", 
		names[i].c_str(), colors[i].Red(), colors[i].Green(), colors[i].Blue(), 
		density[i], slip[i], gravity[i], names[trans_death[i]].c_str(), deathrate[i]);
	for(int j=0;j<numberOfElements;++j){
	  if (trans_prob[i][j] != 0){
	    fprintf(file, "%s\t%s\t%s\t%f\n", names[j].c_str(), names[trans_to[i][j]].c_str(), names[trans_from[i][j]].c_str(), trans_prob[i][j]);
	  }
	}
	fprintf(file, "\n");
      }
      
      fclose(file);
      
    }
  }

  doWalls = wallsCB->IsChecked();
  doSources = sourcesCB->IsChecked();
  doDraw = drawCB->IsChecked();
  doGravity = gravityCB->IsChecked();
  doInteractions = interactionsCB->IsChecked();
  doLimit = limitCB->IsChecked();

  pauseDrawing = false;

}

void MainFrame::OnButton(wxCommandEvent& event){
  if (event.GetId() == 1025){
    if(sandboxFilename == _("")){
      //Refresh
      drawAll = true;
      memset(data, 0, g_width*g_height*sizeof(unsigned char));
      //memset(draw, true, g_width*g_height*sizeof(unsigned char));
    }
    else{
      //reload
      loadSandbox(sandboxFilename);
    }
    
    if (physicsFilename != _("")){
      loadPhysics(physicsFilename);
    }
  }
}

void MainFrame::OnChoice(wxCommandEvent& event){
  if (event.GetId() == 1000){
    sand_type = (char)event.GetSelection();
  }
  else if (event.GetId() == 1001){
    pen_width = 2*(int)pow(2,(event.GetSelection()));
  }
}

void MainFrame::OnSize(wxSizeEvent& event){
  drawAll = true;
  //memset(draw, true, g_width*g_height*sizeof(unsigned char));
  g_mainFrame->Layout();
}

void MainFrame::OnMove(wxFocusEvent& event){
  drawAll = true;
  //memset(draw, true, g_width*g_height*sizeof(unsigned char));
  if (g_canvas){
    g_canvas->Refresh();
  }
}


bool DropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames){
  
  if (filenames[0].First(_(".txt")) != -1 || filenames[0].First(_(".TXT")) != -1 ){
    loadPhysics(filenames[0]);
  }
  else{
    loadSandbox(filenames[0]);
  }

  if (filenames.GetCount() == 2){
    if (filenames[1].First(_(".txt")) != -1 || filenames[1].First(_(".TXT")) != -1 ){
      loadPhysics(filenames[1]);
    }
    else{
      loadSandbox(filenames[1]);
    }
  }

  return true;
}


/* this is executed upon startup, like 'main()' in non-wxWidgets programs */
bool Sand::OnInit()
{
  g_mainFrame = NULL;
  g_canvas = NULL;
  mouseIsDown = false;
  statusbar = NULL;
  g_width = 640;
  g_height = 480;
  sandboxFilename = _("");
  physicsFilename = _("");

  numberOfElements=17;

  ::wxInitAllImageHandlers();

  wxString str = _("Owen Piette's Falling Sand Game v");
  str << VERSION;
  wxFrame *frame = new MainFrame(str, wxDefaultPosition, wxDefaultSize);//, wxCLOSE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN);
  frame->Show(TRUE);
  //SetTopWindow(frame);

  if (this->argc > 1){
    sandboxFilename = wxString(argv[1]);
    loadSandbox(sandboxFilename);
  }

  if (this->argc > 2){
    physicsFilename = wxString(argv[2]);
    loadPhysics(physicsFilename);
  }
  
  g_mainFrame->SetDropTarget(new DropTarget());

  return true;
}
