// LifeSim.cpp : main project file.
#include "stdafx.h"
#include "Form1.h"
#include <vector>
#include <cmath>
#include "Life.h"
#include "BasicLife.h"
#include "FastLife.h"
#include "SparseLife.h"
#include "HashLife.h"
#include "Viewport.h"
#include "PrefDialog.h"
using namespace std;

using namespace LifeSim;
using namespace System::Drawing;
using namespace System::Windows::Forms;

namespace { // anon

	// simulators to use
BasicLife basicSim_;
FastLife fastSim_; 
SparseLife sparseSim_;
HashLife hashSim_;
LifeSimulator * life_ = &hashSim_; // &basicSim_; // todo

BasicLife simCheck_; // backup sim to check validity of other sim engines

Viewport viewport_;  // our default viewport

// see if two sims are the same state, return true iff they are
bool CompareSim(const LifeSimulator & s1, const LifeSimulator & s2)
	{
	int w1,h1,w2,h2;
	s1.GetSize(w1,h1);
	s2.GetSize(w2,h2);
	if ((w1 != w2) || (h1 != h2))
		return false; // must be same size
	for (int j = 0; j < h1; ++j)
		for (int i = 0; i < w1; ++i)
			if (s1.GetValue(i,j) != s2.GetValue(i,j))
				return false;
	// todo- check gen and population also
	return true;
	} // CompareSim

// copy one simulator state to another
void CopySim(const LifeSimulator & src, LifeSimulator & dst)
	{
	int w,h;
	src.GetSize(w,h);
	dst.SetSize(w,h);
	dst.Reset();
	for (int j = 0; j < h; ++j)
		for (int i = 0; i < w; ++i)
			dst.SetValue(i,j,src.GetValue(i,j));
	dst.CountPopulation();
	dst.ConvertToInternal();
	} // CopySim

// given a destination image, a viewport, a  life simulation, a renderer (todo), 
// and a foreground color, draw the image
// drawBackground false only draws foreground cells, else draws both
// clip everything as needed
void DrawBitmap(Bitmap ^ bmp, Viewport & vp, LifeSimulator & life, 
				Color foreground, 
				Color back,
				Color line1,
				Color line2,
				bool drawBackground)
	{
	using namespace System::Drawing::Imaging;

	// draw background
	if (true == drawBackground)
		{ 
		Graphics ^ gr = Graphics::FromImage(bmp);
		gr->Clear(back);
		}

	int bw = bmp->Width, bh = bmp->Height; // dest sizes

	// Lock the bitmap bits.
	Rectangle rect = Rectangle(0,0,bw,bh);
	BitmapData^ bmpData = bmp->LockBits( rect, ImageLockMode::ReadWrite, PixelFormat::Format24bppRgb);
	unsigned char * basePtr = reinterpret_cast<unsigned char*>(bmpData->Scan0.ToPointer());
	int stride = bmpData->Stride;

	unsigned char r,g,b;
	r = foreground.R;
	g = foreground.G;
	b = foreground.B;

	life.DrawBuffer(vp,basePtr,stride,bw,bh,r,g,b);

	// Unlock the bits.
	bmp->UnlockBits( bmpData );

	int zoomNum, zoomDen;
	vp.GetZoom(zoomNum, zoomDen);
	zoomNum = 1<<zoomNum;
	zoomDen = 1<<zoomDen;

	// draw lines
	if (zoomDen >= 4)
		{ // todo - draw lines, color every 10th in a nice color
		Graphics ^ gr = Graphics::FromImage(bmp);
		Pen ^ pen1 = gcnew Pen(line1);
		Pen ^ pen2 = gcnew Pen(line2);
		
		for (int i = 0; i+zoomDen < bw; i += zoomDen)
			{
			gr->DrawLine(pen1,i,0,i,bh-1);
			}
		for (int j = 0; j+zoomDen < bh; j += zoomDen)
			{
			gr->DrawLine(pen1,0,j,bw-1,j);
			}

		// x,y goes over cell coords, x1,y1 to x2,y2
		int x1, y1, x2, y2;
		vp.GetViewportWorldCoords(bw,bh,x1,y1,x2,y2);

		// now every 10th line in different color
		for (int i = 0; i+zoomDen < bw; i += zoomDen)
			{
			if (0 == ((i/zoomDen+x1)%10))
				gr->DrawLine(pen2,i,0,i,bh-1);
			}
		for (int j = 0; j+zoomDen < bh; j += zoomDen)
			{
			if (0 == ((j/zoomDen+y1)%10))
				gr->DrawLine(pen2,0,j,bw-1,j);
			}
		}

	} // DrawBitmap
	
	} // end anon namespace

void Form1::ShowInfo(void)
	{
	populationText->Text = String::Concat("Population ", life_->GetPopulation());
	generationText->Text = String::Concat("Generation ", life_->GetGeneration());
	int num,den,x1,x2,y1,y2;
	viewport_.GetZoom(num,den);
	String ^ nStr, ^dStr;
	if (num < 10)
		nStr = String::Concat(1<<num);
	else
		nStr = String::Concat("2^",num);
	if (den < 10)
		dStr = String::Concat(1<<den);
	else
		dStr = String::Concat("2^",den);
	zoomText->Text       = String::Concat("Zoom ",nStr,":",dStr);
	int w = picturePanel->Width;
	int h = picturePanel->Height;
	viewport_.GetViewportWorldCoords(w,h,x1,y1,x2,y2);
	positionText->Text   = String::Concat("Position (",x1,",",y1,")-(",x2,",",y2,")");

/*	StringBuilder ^ sb = gcnew StringBuilder();
	sb->Append("Population ");
	sb->Append(life_->GetPopulation());
	sb->Append(" Generation ");
	sb->Append(life_->GetGeneration());
	status->Text = sb->ToString(); */
	} // ShowInfo

// draw hashSim stats
void Form1::ShowHashStats(void)
	{
	totalNodeText->Text = String::Concat("Total Nodes: ", hashSim_.stats_.hashNodes_);
	missedNodeText->Text = String::Concat("Missed Nodes: ",hashSim_.stats_.nodeMissed_);
	foundNodeText->Text = String::Concat("Found Nodes: ", hashSim_.stats_.nodeMatched_);
	tileComputeText->Text = String::Concat("Tiles Computed: ",hashSim_.stats_.tileComputes_);
	expandText->Text = String::Concat("Universe Expanded: ",hashSim_.stats_.expands_);
	reusedAnswerText->Text = String::Concat("Reused Answers: ", hashSim_.stats_.answered_);
	zeroGenCutoffText->Text = String::Concat("Zero Gen Cutoffs: ",hashSim_.stats_.zeroCutoff_);
	zeroDrawCutoffText->Text = String::Concat("Zero Gen Cutoffs: ",hashSim_.stats_.zeroDraw_);	
	} // ShowHashStats

void Form1::DoOneFrame(void)
	{
	using namespace System::Text;
	int mx = Convert::ToInt32(gensPerFrameBox->Value);
	for (int pos = 0; pos < mx; ++pos)
		{
		life_->NextGeneration();

		if (true == preferences_.checkEngine_)
			{ // do check
			simCheck_.NextGeneration();
			simCheck_.ConvertToExternal();
			life_->ConvertToExternal();
			if (false == CompareSim(*life_,simCheck_))
				statusStrip->Items[2]->Text = "Sim Compare Error!";
			}
		if (life_ == &hashSim_)
			ShowHashStats();
		}

	DrawImage();
	} // DoOneFrame

void Form1::Clear(void)
	{
	life_->Reset();
	DrawImage();
	} // Clear

void Form1::DrawImage(void)
	{
	int w,h;
	life_->GetSize(w,h);
	
	DrawBitmap(image_,viewport_, *life_, 
		preferences_.aliveColor_, preferences_.deadColor_, preferences_.gridColor_, preferences_.bigGridColor_, 
		true); 
	if (true == preferences_.checkEngine_)
		{ // draw other one in another color
		DrawBitmap(image_,viewport_, *life_, 
			Color::Red, preferences_.deadColor_, preferences_.gridColor_, preferences_.bigGridColor_, 
			true); 
		}

	picturePanel->Invalidate();
	picturePanel->Update();
	ShowInfo();
	} // DrawImage

void Form1::RandomImage(void)
	{
	// todo - reinstate
#if 0
	int w,h;

	life_->GetSize(w,h);
	life_->Reset();

	int pop = 0;
	int rate = densityScroll->Value*10;

	for (int x = 0; x < w; ++x)
		for (int y = 0; y < h; ++y)
			{
			if ((rand()%1000) < rate)
				{
				life_->SetValue(x,y,1);
				++pop;
				}
			else
				life_->SetValue(x,y,0);
			}
	life_->SetPopulation(pop);
#endif
	DrawImage();
	} // RandomImage

System::Void Form1::Form1_Load(System::Object^  sender, System::EventArgs^  e) 
	{
	int size = 256; // 512;
	image_ = gcnew Bitmap(picturePanel->Width+50,picturePanel->Height+50);
	life_->SetSize(size,size);
	viewport_.SetCenter(size/2,size/2);

	RandomImage();

	// Set the value of the double-buffering style bits to true. - todo!
	//picturePanel->SetStyle( static_cast<ControlStyles>(ControlStyles::DoubleBuffer | ControlStyles::UserPaint | ControlStyles::AllPaintingInWmPaint), true );


#if 0
	SetStyle(ControlStyles::UserPaint, true);
	SetStyle(ControlStyles::AllPaintingInWmPaint, true);
	SetStyle(ControlStyles::DoubleBuffer, true);
	SetStyle(ControlStyles::UserPaint, true);
	SetStyle(ControlStyles::AllPaintingInWmPaint, true);
	SetStyle(ControlStyles::DoubleBuffer, true);
#endif

	picturePanel->Image = image_;

	using namespace System::IO;
	String ^ patternDir = String::Concat(Directory::GetCurrentDirectory(),"\\Patterns");
	LoadPatternView(patternDir);
	}

void Form1::ChangeEngine(PrefDialog::Preferences::Engine engine) 
	{
	if ((PrefDialog::Preferences::Engine::BasicEngine == engine) && (life_ != &basicSim_))
		{
		CopySim(*life_,basicSim_);
		life_ = &basicSim_;
		if (true == preferences_.checkEngine_)
			CopySim(*life_,simCheck_); // copy current one to simCheck
		}
	else if ((PrefDialog::Preferences::Engine::FastEngine == engine) && (life_ != &fastSim_))
		{
		CopySim(*life_,fastSim_);
		life_ = &fastSim_;
		if (true == preferences_.checkEngine_)
			CopySim(*life_,simCheck_); // copy current one to simCheck
		}
	else if ((PrefDialog::Preferences::Engine::HashEngine == engine) && (life_ != &hashSim_))
		{
		CopySim(*life_,hashSim_);
		life_ = &hashSim_;
		if (true == preferences_.checkEngine_)
			CopySim(*life_,simCheck_); // copy current one to simCheck
		}
	else if ((PrefDialog::Preferences::Engine::SparseEngine == engine) && (life_ != &sparseSim_))
		{
		CopySim(*life_,sparseSim_);
		life_ = &sparseSim_;
		if (true == preferences_.checkEngine_)
			CopySim(*life_,simCheck_); // copy current one to simCheck
		}
	} // ChangeEngine


System::Void Form1::checkEngine_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
	{
	if (true == preferences_.checkEngine_)
		{
		// copy current one to simCheck
		CopySim(*life_,simCheck_);
		}
	else
		statusStrip->Items[2]->Text = "";
	} // checkEngine_CheckedChanged

System::Void Form1::jumpButton_Click(System::Object^  sender, System::EventArgs^  e)
	{
	// todo - integrate checking with default, etc.
	int val = Convert::ToInt32(jumpBox->Text);
	while (val--)
		{
		life_->NextGeneration();
		}
	DrawImage();
	} // jumpButton_Click

System::Void Form1::loadButton_Click(System::Object^  sender, System::EventArgs^  e)
	{
	OpenFileDialog ^ fd = gcnew OpenFileDialog();
	fd->Filter = "Life files(*.RLE;*.LIF;*.PNG;*.BMP;*.GIF)|*.RLE;*.LIF;*.PNG;*.BMP;*.GIF|Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG|All files (*.*)|*.*";
	if (System::Windows::Forms::DialogResult::OK == fd->ShowDialog())
		LoadFile(fd->FileName);
	} // loadButton_Click

System::Void Form1::saveButton_Click(System::Object^  sender, System::EventArgs^  e)
	{
	SaveFileDialog ^ fd = gcnew SaveFileDialog();
	fd->Filter = "Life files(*.RLE;*.PNG;*.BMP;*.GIF)|*.RLE;*.PNG;*.BMP;*.GIF|Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG|All files (*.*)|*.*";
	if (System::Windows::Forms::DialogResult::OK == fd->ShowDialog())
		{ // todo - save RLE, etc
		image_->Save(fd->FileName);
		}
	} // saveButton_Click

void Form1::Resize(void)
	{
	int w = picturePanel->Width+50;
	int h = picturePanel->Height+50;
	image_ = gcnew Bitmap(w,h);
	picturePanel->Image = image_;
	DrawImage();
	} // Resize

void Form1::OnMouseWheel(MouseEventArgs ^ e)
	{

//	if (0 == tabControl->SelectedIndex)
		{
		if (e->Delta < 0)
			viewport_.Zoom(-1);
		else
			viewport_.Zoom(1);
		DrawImage();
		}
	}

System::Void Form1::vPicScroll_Scroll(System::Object^  sender, System::Windows::Forms::ScrollEventArgs^  e) 
	{
	int dy = e->OldValue - e->NewValue;
	dy = dy*vPicScroll->Height/100;
	viewport_.ScrollCenter(0,dy);
	DrawImage();
	}
System::Void Form1::hPicScroll_Scroll(System::Object^  sender, System::Windows::Forms::ScrollEventArgs^  e) 
	{
	int dx = e->OldValue - e->NewValue;
	dx = dx*hPicScroll->Width/100;
	viewport_.ScrollCenter(dx,0);
	DrawImage();
	}

void Form1::LoadFile(String ^ file)
	{
	Windows::Forms::Cursor::Current = Cursors::WaitCursor;
	try
		{
		if (file->Length > 0)
			{
			String ^ comment;
			int width, height;
			if (true == LoadPattern(file,comment,width,height,life_))
				{
				if (life_ == &hashSim_)
					{
					// center
					viewport_.SetCenter(width/2,height/2);
					double widthRatio  = width;  // cells per pixel
					widthRatio  /= picturePanel->Width;
					double heightRatio = height; // cells per pixel
					heightRatio /= picturePanel->Height;
					double ratio = max(widthRatio,heightRatio);
					int zoom = static_cast<int>(log(ratio)/log(2.0));
					if (zoom > 0)
						viewport_.SetZoom(zoom,0);
					else
						viewport_.SetZoom(0,-zoom);
					}
				DrawImage();
				}
			infoBox->Text = comment;
			jumpBox->Focus();
			}
		}
	catch (...)
		{
		MessageBox::Show("Error opening file");
		}
	Windows::Forms::Cursor::Current = Cursors::Default;
	} // LoadFile

System::Void Form1::patternView_AfterSelect(System::Object^  sender, System::Windows::Forms::TreeViewEventArgs^  e) 
	{
	try
		{
		LoadFile(safe_cast<String^>(patternView->SelectedNode->Tag));
		}
	catch (...)
		{
		MessageBox::Show("Error opening file");
		}
	} // patternView_AfterSelect

void Form1::SetHashing(bool hash)
	{
	// handle check states
	hashSim_.SetHyperspeed(hash);
	hyperToolButton->Checked = hash;
	hashBox->Checked = hash;
	} // SetHashing

System::Void Form1::hashToolButton_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
	{
	SetHashing(hyperToolButton->Checked);
	}

System::Void Form1::hashBox_CheckedChanged(System::Object^  sender, System::EventArgs^  e)
	{
	SetHashing(hashBox->Checked);
	}

System::Void Form1::resetView_Click(System::Object^  sender, System::EventArgs^  e)
	{
	viewport_.Reset();
	life_->Reset();
	DrawImage();
	}


System::Void Form1::prefButton_Click(System::Object^  sender, System::EventArgs^  e)
	{
	PrefDialog ^ prefDlg = gcnew PrefDialog();
	prefDlg->SetPreferences(this->preferences_);
	prefDlg->ShowDialog();	
	if (Windows::Forms::DialogResult::OK == prefDlg->DialogResult)
		{
		preferences_ = prefDlg->preferences_;
		ChangeEngine(preferences_.engine_);
		DrawImage();
		}
	} // prefButton_Click


[STAThreadAttribute]
int main(array<System::String ^> ^args)
{
	// Enabling Windows XP visual effects before any controls are created
	Application::EnableVisualStyles();
	Application::SetCompatibleTextRenderingDefault(false); 

	// Create the main window and run it
	Application::Run(gcnew Form1());
	return 0;
}
