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

#include "Sand.h"
#include <wx/wx.h>
#include <wx/dcscreen.h>
#include <wx/dcbuffer.h>


#define NUMBEROFELEMENTS 11

#define EMPTY 0
#define SALTWATER 1
#define WATER 2
#define SALT 3
#define SAND 4
#define WETSAND 5
#define WETWOOD 6
#define WETPLANT 7
#define WOOD 8
#define PLANT 9
#define STONE 10

double gravity[NUMBEROFELEMENTS] =  {0, 0.6, 0.6, 0.6, 0.6, 0.6,  0, 0, 0, 0, 0};
double friction[NUMBEROFELEMENTS] = {0, 1,   1,   0.3, 0.2, 0.05, 0, 0, 0, 0, 0};
double density[NUMBEROFELEMENTS] =  {0, 0,   0,   0.1, 0.1, 0.1,  1, 1, 1, 1, 1};
const wxString names[NUMBEROFELEMENTS] =    {"Eraser", "Salt Water",   "Water",   "Salt", "Sand", 
					     "Wet Sand",  "Wet Wood", "Wet Plant", "Wood", "Plant", "Stone"};

double trans_prob[NUMBEROFELEMENTS][NUMBEROFELEMENTS];
char trans_to[NUMBEROFELEMENTS][NUMBEROFELEMENTS];
char trans_from[NUMBEROFELEMENTS][NUMBEROFELEMENTS];


BEGIN_EVENT_TABLE(Canvas, wxWindow)
  //EVT_PAINT(Canvas::OnPaint)
  EVT_ERASE_BACKGROUND(Canvas::OnEraseBG)
  EVT_LEFT_DOWN(Canvas::OnMouseLeftDown)
  EVT_LEFT_UP(Canvas::OnMouseLeftUp)
  EVT_MOTION(Canvas::OnMouseMove)
  EVT_TIMER(1000, Canvas::OnTimer)
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)
END_EVENT_TABLE()


IMPLEMENT_APP(Sand)

MainFrame* g_mainFrame;
Canvas* g_canvas;
char data[1000][800];
bool draw[1000][800];
wxTimer* m_timer;
bool mouseIsDown = false;
int mousex, mousey;
char sand_type;
int pen_width;
int draw_counter = 0;

Canvas::Canvas(wxWindow* parent, wxWindowID id = -1, wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize) : wxWindow( parent, id, pos, size, wxSUNKEN_BORDER | wxCLIP_CHILDREN ){
  g_canvas = this;
  //this->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
  this->SetBackgroundColour(wxColor("BLACK"));
  //this->SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
  m_timer = new wxTimer(this, 1000);
  sand_type = 1;
  pen_width = 2;

  for(int y=0;y<800;++y){
    for(int x=0;x<1000;++x){
      data[x][y] = 0;
      draw[x][y] = true;
    }
  }

  //this->ClearBackground();

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

  //Evaporation.
  trans_prob[WATER][EMPTY] = 0.00002;
  trans_to[WATER][EMPTY] = EMPTY;
  trans_from[WATER][EMPTY] = EMPTY;

  //Crystalize
  trans_prob[SALTWATER][SALTWATER] = 0.00002;
  trans_to[SALTWATER][SALTWATER] = SALT;
  trans_from[SALTWATER][SALTWATER] = WATER;

  //randomly move saltwater
  trans_prob[SALTWATER][WATER] = 0.5;
  trans_to[SALTWATER][WATER] = WATER;
  trans_from[SALTWATER][WATER] = SALTWATER;

  //absorb salt into water
  trans_prob[WATER][SALT] = 0.01;
  trans_to[WATER][SALT] = SALTWATER;
  trans_from[WATER][SALT] = SALTWATER;

  //make wet sand
  trans_prob[SAND][WATER] = 0.25;
  trans_to[SAND][WATER] = WETSAND;
  trans_from[SAND][WATER] = EMPTY;

  //Wet the wood.
  trans_prob[WETSAND][WOOD] = .75;
  trans_to[WETSAND][WOOD] = SAND;
  trans_from[WETSAND][WOOD] = WETWOOD;

  //Move wet sand
  trans_prob[WETSAND][SAND] = 0.5;
  trans_to[WETSAND][SAND] = SAND;
  trans_from[WETSAND][SAND] = WETSAND;

  trans_prob[WETSAND][WATER] = 0.02;
  trans_to[WETSAND][WATER] = WETSAND;
  trans_from[WETSAND][WATER] = EMPTY;

  //Dry out wet sand.
  trans_prob[WETSAND][EMPTY] = 0.00002;
  trans_to[WETSAND][EMPTY] = SAND;
  trans_from[WETSAND][EMPTY] = EMPTY;

  trans_prob[WOOD][SALTWATER] = 0.0001;
  trans_to[WOOD][SALTWATER] = SAND;
  trans_from[WOOD][SALTWATER] = SALTWATER;

  trans_prob[WOOD][WATER] = 0.0001;
  trans_to[WOOD][WATER] = SAND;
  trans_from[WOOD][WATER] = WATER;

  //Wet the plant
  trans_prob[WETWOOD][PLANT] = .75;
  trans_to[WETWOOD][PLANT] = WOOD;
  trans_from[WETWOOD][PLANT] = WETPLANT;

  //Move wet wood.
  trans_prob[WETWOOD][WOOD] = 0.5;
  trans_to[WETWOOD][WOOD] = WOOD;
  trans_from[WETWOOD][WOOD] = WETWOOD;

  //Dry out the wood.
  trans_prob[WETWOOD][EMPTY] = 0.00005;
  trans_to[WETWOOD][EMPTY] = WOOD;
  trans_from[WETWOOD][EMPTY] = WATER;


  trans_prob[WETPLANT][EMPTY] = 0.2;
  trans_to[WETPLANT][EMPTY] = PLANT;
  trans_from[WETPLANT][EMPTY] = PLANT;

  trans_prob[WETPLANT][PLANT] = 0.01;
  trans_to[WETPLANT][PLANT] = PLANT;
  trans_from[WETPLANT][PLANT] = WETPLANT;

  trans_prob[WETPLANT][WOOD] = 0.0007;
  trans_to[WETPLANT][WOOD] = WOOD;
  trans_from[WETPLANT][WOOD] = WOOD;

  trans_prob[PLANT][SALTWATER] = 0.01;
  trans_to[PLANT][SALTWATER] = WOOD;
  trans_from[PLANT][SALTWATER] = WATER;

  trans_prob[PLANT][WOOD] = 0.0007;
  trans_to[PLANT][WOOD] = WOOD;
  trans_from[PLANT][WOOD] = WOOD;

  m_timer->Start(50);    // 1/10 second interval
}


inline bool checkForTransformation(int x, int y, int dx, int dy){
  char t1 = data[x][y];
  char t2 = data[dx][dy];
  if (trans_prob[t1][t2] == 0) return false;
  if (rand() < trans_prob[t1][t2]*RAND_MAX){
    data[x][y] = trans_to[t1][t2];
    data[dx][dy] = trans_from[t1][t2];
    draw[x][y] = true;
    draw[dx][dy] = true;
    return true;
  }
  return false;
}



void Canvas::OnTimer(wxTimerEvent& event){
  int j = 100;

  for(int i=100;i<110;++i)
    if (rand() < RAND_MAX/4){
      data[i][j] = SALT;
      draw[i][j] = true;
    }
  for(int i=200;i<210;++i)
    if (rand() < RAND_MAX/4){
      data[i][j] = SAND;
      draw[i][j] = true;
    }
  for(int i=400;i<410;++i)
    if (rand() < RAND_MAX/4){
      data[i][j] = WATER;
      draw[i][j] = true;
    }

  for(int y=797;y>1;--y){
    //Randomly calculate left or right.
    int startx = 2;
    int endx = 998;
    int move = 1;
    if (rand() < 0.5*RAND_MAX){
      startx = 998;
      endx = 1;
      move = -1;
    }

    for(int x = startx; x != endx-move; x += move){
      if (data[x][y] == 0)
	continue;

      if (rand() > 0.5*RAND_MAX){
	//Check for specific transformations.
	//if(checkForTransformation(x,y,x-1,y-1)) continue;
	if(checkForTransformation(x,y,x-1,y  )) continue;
	//if(checkForTransformation(x,y,x-1,y+1)) continue;
	if(checkForTransformation(x,y,x  ,y+1)) continue;
	//if(checkForTransformation(x,y,x+1,y+1)) continue;
	if(checkForTransformation(x,y,x+1,y  )) continue;
	//if(checkForTransformation(x,y,x+1,y-1)) continue;
	if(checkForTransformation(x,y,x  ,y-1)) continue;
      }
      else{
	//Check for specific transformations.
	if(checkForTransformation(x,y,x  ,y-1)) continue;
	if(checkForTransformation(x,y,x+1,y  )) continue;
	if(checkForTransformation(x,y,x  ,y+1)) continue;
	if(checkForTransformation(x,y,x-1,y  )) continue;
      }


      //Check for falling through the air.
      if (data[x][y+1] == 0){
	if (rand() < gravity[data[x][y]]*RAND_MAX){
	  data[x][y+1] = data[x][y];
	  data[x][y] = 0;
	  draw[x][y] = true;
	  draw[x][y+1] = true;
	  continue;
	}
	continue;
      }
      //Check for being pushed to the side.
      //Randomize which direction to check first.
      if (data[x-1][y] == 0 || data[x+1][y] == 0 ){
	if (rand() < 0.5*RAND_MAX){
	  if (data[x-1][y] == 0 && data[x+1][y] > 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x-1][y] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x-1][y] = true;
	      continue;
	    }
	    continue;
	  }
	  else if (data[x+1][y] == 0 && data[x-1][y] > 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x+1][y] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x+1][y] = true;
	      continue;
	    }
	    continue;
	  }
	}
	else{
	  if (data[x+1][y] == 0 && data[x-1][y] > 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x+1][y] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x+1][y] = true;
	      continue;
	    }
	    continue;
	  }
	  else if (data[x-1][y] == 0 && data[x+1][y] > 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x-1][y] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x-1][y] = true;
	      continue;
	    }
	    continue;
	  }
	}
      }
      //Check for falling downhill
      if (data[x-1][y+1] == 0 || data[x+1][y+1] == 0 ){
	if (rand() < 0.5*RAND_MAX){
	  if (data[x+1][y+1] == 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x+1][y+1] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x+1][y+1] = true;
	      continue;
	    }
	    continue;
	  }
	}
	else{
	  if (data[x-1][y+1] == 0){
	    if (rand() < friction[data[x][y]]*RAND_MAX){
	      data[x-1][y+1] = data[x][y];
	      data[x][y] = 0;
	      draw[x][y] = true;
	      draw[x-1][y+1] = true;
	      continue;
	    }
	    continue;
	  }
	}
      }


      //Check for falling through another material.
      if (density[data[x][y]] != 1.0){
	if (density[data[x][y]] > density[data[x][y+1]]){
	  if ( rand() < ((density[data[x][y]] - density[data[x][y+1]]))*RAND_MAX ){
	    char t = data[x][y];
	    data[x][y] = data[x][y+1];
	    data[x][y+1] = t;
	    draw[x][y] = true;
	    draw[x][y+1] = true;
	    continue;
	  }
	}
	if (rand() < 0.5*RAND_MAX){
	  if (density[data[x][y]] > density[data[x-1][y+1]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x-1][y+1]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x-1][y+1];
	      data[x-1][y+1] = t;
	      draw[x][y] = true;
	      draw[x-1][y+1] = true;
	      continue;
	    }
	  }
	}
	else{
	  if (density[data[x][y]] > density[data[x+1][y+1]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x+1][y+1]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x+1][y+1];
	      data[x+1][y+1] = t;
	      draw[x][y] = true;
	      draw[x+1][y+1] = true;
	      continue;
	    }
	  }
	}
	//Pushing left and right.
	//Randomly choose which side goes first.
	if (rand() < 0.5*RAND_MAX){
	  if (density[data[x][y]] > density[data[x-1][y]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x-1][y]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x-1][y];
	      data[x-1][y] = t;
	      draw[x][y] = true;
	      draw[x-1][y] = true;
	      continue;
	    }
	  }
	  if (density[data[x][y]] > density[data[x+1][y]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x+1][y]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x+1][y];
	      data[x+1][y] = t;
	      draw[x][y] = true;
	      draw[x+1][y] = true;
	      continue;
	    }
	  }
	}
	else{
	  if (density[data[x][y]] > density[data[x+1][y]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x+1][y]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x+1][y];
	      data[x+1][y] = t;
	      draw[x][y] = true;
	      draw[x+1][y] = true;
	      continue;
	    }
	  }
	  if (density[data[x][y]] > density[data[x-1][y]]){
	    if ( rand() < ((density[data[x][y]] - density[data[x-1][y]]))*RAND_MAX ){
	      char t = data[x][y];
	      data[x][y] = data[x-1][y];
	      data[x-1][y] = t;
	      draw[x][y] = true;
	      draw[x-1][y] = true;
	      continue;
	    }
	  }
	}
      }


      

    }
  }
  if (mouseIsDown){
    for(int i=mousex-pen_width/2;i<mousex+pen_width/2;++i){
      for(int j=mousey-pen_width/2;j<mousey+pen_width/2;++j){
	if (i > 0 && i < 1000 && j > 0 && j < 800){
	  data[i][j] = sand_type;
	  draw[i][j] = true;
	}
      }
    }
  }

  this->Refresh();
}


void Canvas::Refresh(){
  wxClientDC dc(g_canvas);

  int cx,cy,cw,ch;
  dc.GetClippingBox(&cx,&cy,&cw,&ch);
  for(int y = cy;y<cy+ch; ++y)
    for(int x = cx;x<cx+cw; ++x)
      draw[x][y] = true;

  for(int y=0;y<800;++y){
    for(int x=0;x<1000;++x){
      if (draw[x][y] == false)
	continue;
      
      if (data[x][y] == 0){
	dc.SetPen(*wxBLACK_PEN);
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == SALT){
	dc.SetPen(*wxWHITE_PEN);
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == SAND){
	dc.SetPen(wxPen(wxColor(238,204,128), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == WOOD){
	dc.SetPen(wxPen(wxColor(165,42,42), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == PLANT){
	dc.SetPen(wxPen(wxColor(0,255,0), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == WETSAND){
	dc.SetPen(wxPen(wxColor(238-10,204-10,128-10), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == WETWOOD){
	dc.SetPen(wxPen(wxColor(165-10,42-10,42-10), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == WETPLANT){
	dc.SetPen(wxPen(wxColor(0,255-10,0), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == WATER){
	dc.SetPen(wxPen(wxColor(32,32,255), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == SALTWATER){
	dc.SetPen(wxPen(wxColor(2,2,225), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }
      if (data[x][y] == STONE){
	dc.SetPen(wxPen(wxColor(192,192,192), 1, wxSOLID));
	dc.DrawPoint(x, y);
	dc.SetPen(wxNullPen);
	draw[x][y] = false;
	continue;
      }

    }
  }

}

void Canvas::OnEraseBG(wxEraseEvent& e){
  //Do absolutely nothing, thereby halting this event.
  
  for(int y=0;y<800;++y){
    for(int x=0;x<1000;++x){
      draw[x][y] = true;
    }
  }
   
  this->Refresh();
}

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

  for(int i=mousex-pen_width/2;i<mousex+pen_width/2;++i){
    for(int j=mousey-pen_width/2;j<mousey+pen_width/2;++j){
      if (i > 0 && i < 1000 && j > 0 && j < 800){
	data[i][j] = sand_type;
	draw[i][j] = true;
      }
    }
  }

  g_canvas->Refresh();
}

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

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

    for(int i=mousex-pen_width/2;i<mousex+pen_width/2;++i){
      for(int j=mousey-pen_width/2;j<mousey+pen_width/2;++j){
	if (i > 0 && i < 1000 && j > 0 && j < 800){
	  data[i][j] = sand_type;
	  draw[i][j] = true;
	}
      }
    }

    g_canvas->Refresh();
  }
  else
    mouseIsDown = false;
}

void Canvas::OnPaint(wxPaintEvent& event){
  wxPaintDC dc;
  this->Refresh();
}




MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size, 
		     long style) : wxFrame(NULL, -1, title, pos, size, style){
  g_mainFrame = this;
  //this->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
  g_canvas = new Canvas(this, -1, wxDefaultPosition, wxDefaultSize);
  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
  sizer->Add(g_canvas, 1, wxEXPAND);

  sizer->Add(new wxListBox(this, 1000, wxDefaultPosition, wxDefaultSize, NUMBEROFELEMENTS, names, wxLB_SINGLE), 0, wxEXPAND);
  
  const wxString labels2[5] = {"2", "4", "6", "8", "10"};
  sizer->Add(new wxListBox(this, 1001, wxDefaultPosition, wxDefaultSize, 5, labels2, wxLB_SINGLE), 0, wxEXPAND);
  

  this->SetSizer(sizer);
  //this->SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
}

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

}


void MainFrame::OnSize(wxSizeEvent& event){
  for(int y=0;y<800;++y){
    for(int x=0;x<1000;++x){
      draw[x][y] = true;
    }
  }
  g_mainFrame->Layout();
}

void MainFrame::OnMove(wxFocusEvent& event){
  for(int y=0;y<800;++y){
    for(int x=0;x<1000;++x){
      draw[x][y] = true;
    }
  }
  if (g_canvas)
    g_canvas->Refresh();
}


/* this is executed upon startup, like 'main()' in non-wxWidgets programs */
bool Sand::OnInit()
{
  wxFrame *frame = new MainFrame("Owen's Reality v1.0", wxDefaultPosition, wxSize(1000,800), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN);
  frame->Show(TRUE);
  SetTopWindow(frame);
  return true;
}

