//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "MainForm.h"
//---------------------------------------------------------------------------

#include <stdio.h>
#include "AboutForm.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

//******************************************************************************
//******************************************************************************
//******************************************************************************
// Global stuff
//******************************************************************************

TfrmMain *frmMain;

// Set up some colors
TColor MapColors[] = {clAqua,    // empty
											clWhite,   // obstacle
											clBlue,    // character
											clRed,     // enemy
											clYellow   // prize
										 };

ColorID ColorMap[] = {{ctEMPTY, MapColors[ctEMPTY]},
											{ctOBSTACLE, MapColors[ctOBSTACLE]},
											{ctCHARACTER, MapColors[ctCHARACTER]},
											{ctENEMY, MapColors[ctENEMY]},
											{ctPRIZE, MapColors[ctPRIZE]}
										 };

/*
	Constructor.
*/
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)

{
	pnlWorld->DoubleBuffered = true;     // prevent flicker
	gridWorld->DoubleBuffered = true;    // prevent flicker

		// Some defaults
	FShowGrid = true;
	FGridColor = clBlack;
	FCurrColorID = ColorMap[ctOBSTACLE];
	FColumns = 20;
	FRows = 20;
	FCurrFileName = "Exported.txt"; // for compatibility with CS230

		// Setup the GUI
	Init();
}
//---------------------------------------------------------------------------

/*
	Initializes the state of the game and the UI
*/
void TfrmMain::Init(void)
{
	for (int i = 0; i < FMaxRows; i++)
		for (int j = 0; j < FMaxColumns; j++)
			FDataMap[i][j] = 0;

	spnRows->Position = FRows;
	spnColumns->Position = FColumns;

		// Assume mouse if off of the grid
	FCurrRow = -1;
	FCurrColumn = -1;
	FPrevRow = -1;
	FPrevColumn = -1;

	AdjustGrid();
	UpdateCurrentCellDisplay();
}

/*
	Displays current row/column in the status bar
*/
void TfrmMain::UpdateCurrentCellDisplay(void)
{
		// Invalid location, erase the display
	if (FOffGrid || FCurrColumn == -1 || FCurrRow == -1)
		barStatus->Panels->Items[0]->Text = "";
	else
		barStatus->Panels->Items[0]->Text = Format(" Row: %d, Col: %d", ARRAYOFCONST((FCurrRow + 1, FCurrColumn + 1)));
}

/*
	If the grid is resized, need to update variables with the new sizes
*/
void TfrmMain::AdjustGrid(void)
{
	gridWorld->ColCount = FColumns;
	gridWorld->RowCount = FRows;

	FCellWidth = gridWorld->Width / FColumns;
	FCellHeight = gridWorld->Height / FRows;

		// The size of the cells without the borders
	if (gridWorld->GridLineWidth)
	{
		FCellWidth -= gridWorld->GridLineWidth;
		FCellHeight -= gridWorld->GridLineWidth;
	}

	gridWorld->DefaultColWidth = FCellWidth;
	gridWorld->DefaultRowHeight = FCellHeight;

	TGridRect rect = {{FCurrColumn, FCurrRow}, {FCurrColumn, FCurrRow}};
	gridWorld->Selection = rect;
}

/*
	Displays text in the bottom of the app. Used for debugging.
*/
void TfrmMain::AddStatus(const char *text)
{
	mmoStatus->Lines->Insert(0, text);
}

/*
	Draw the cell
*/
void TfrmMain::HighlightCell(bool Force)
{
		// If we are in the same cell, do nothing
	if ( (!Force) && (FPrevRow == FCurrRow && FPrevColumn == FCurrColumn) )
		return;

		// Otherwise, we've moved from one cell to the next.
		// Un-highlight (lowlight?) the previous cell.
	TRect rect = gridWorld->CellRect(FPrevColumn, FPrevRow);
	TGridDrawState state;
	gridWorldDrawCell(this, FPrevColumn, FPrevRow, rect, state);

		// Off the board, do nothing
	if (FCurrRow < 0 || FCurrRow >= FRows || FCurrColumn < 0 || FCurrColumn >= FColumns)
		return;

		// Highlight the new cell
	rect = gridWorld->CellRect(FCurrColumn, FCurrRow);
	gridWorldDrawCell(this, FCurrColumn, FCurrRow, rect, state);
}

//******************************************************************************
//******************************************************************************
//******************************************************************************
// Event Handlers
//******************************************************************************
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
	pgeWorld->Align = alClient;
	pnlWorld->Align = alClient;
	gridWorld->Align = alClient;
	pnlBottom->BevelOuter = bvNone;
	pnlBottom->Color = MapColors[ctEMPTY];
	gridWorld->Color = MapColors[ctEMPTY];

		// Size of the main window
	Width = 700;
	Height = 700;

		// Default size
	spnColumns->Position = FColumns;
	spnRows->Position = FRows;

		// Hide the memo for now
	mmoStatus->Height = 0;

		// Register this application with the OS as a drop target.
	DragAcceptFiles(this->Handle, true);
}
//---------------------------------------------------------------------------

/*
	Called each time a cell needs to be repainted.
*/
void __fastcall TfrmMain::gridWorldDrawCell(TObject *Sender, int ACol, int ARow,
		TRect &Rect, TGridDrawState State)
{
		// Rectangle of cell to draw
	Rect.Left = Rect.Left - 1;
	Rect.Right = Rect.Right;
	Rect.Top = Rect.Top - 1;
	Rect.Bottom = Rect.Bottom;

		// Draw a 3 pixel wide black border on the highlighted cell
	if (ACol == FCurrColumn && ARow == FCurrRow && !FOffGrid && (ACol != -1 && ARow != -1) )
	{
		gridWorld->Canvas->Pen->Width = 3;
		gridWorld->Canvas->Pen->Color = clBlack;
		Rect.left += gridWorld->Canvas->Pen->Width - 1;
		Rect.Top += gridWorld->Canvas->Pen->Width - 1;
		Rect.right -= gridWorld->Canvas->Pen->Width - 1;
		Rect.Bottom -= gridWorld->Canvas->Pen->Width - 1;
	}
	else // restore the default border
	{
		gridWorld->Canvas->Pen->Width = 1;
		gridWorld->Canvas->Pen->Color = FGridColor;
	}

		// BUG FIX (from WarBoats!)
	if ((ACol == -1) || (ARow == -1))
		return;

		// The cell is empty
	if (FDataMap[ARow][ACol] == ctEMPTY)
	{
		gridWorld->Canvas->Brush->Color = MapColors[ctEMPTY];
		gridWorld->Canvas->Rectangle(Rect);
	}
		// The cell is an obstacle (collision)
	else if (FDataMap[ARow][ACol] == ctOBSTACLE)
	{
		gridWorld->Canvas->Brush->Color = MapColors[ctOBSTACLE];
		gridWorld->Canvas->Rectangle(Rect);
	}
		// The cell is the character in the game
	else if (FDataMap[ARow][ACol] == ctCHARACTER)
	{
		gridWorld->Canvas->Brush->Color = MapColors[ctCHARACTER];
		gridWorld->Canvas->Rectangle(Rect);
	}
		// The cell is an enemy
	else if (FDataMap[ARow][ACol] == ctENEMY)
	{
		gridWorld->Canvas->Brush->Color = MapColors[ctENEMY];
		gridWorld->Canvas->Rectangle(Rect);
	}
		// The cell is a prize (coin)
	else if (FDataMap[ARow][ACol] == ctPRIZE)
	{
		gridWorld->Canvas->Brush->Color = MapColors[ctPRIZE];
		gridWorld->Canvas->Rectangle(Rect);

	}
}
//---------------------------------------------------------------------------


/*
	Called when a cell is selected (with the mouse or keyboard).
*/
void __fastcall TfrmMain::gridWorldSelectCell(TObject *Sender, int ACol,	int ARow, bool &CanSelect)
{
	FCurrRow = ARow;
	FCurrColumn = ACol;
	FDataMap[ARow][ACol] = FCurrColorID.type;
	UpdateCurrentCellDisplay();
}
//---------------------------------------------------------------------------

/*
	Tracks the mouse movement across the grid so the cell under the mouse
	can be highlighted.
*/
void __fastcall TfrmMain::gridWorldMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
	TGridCoord coord = gridWorld->MouseCoord(X, Y);

	FPrevRow = FCurrRow;
	FPrevColumn = FCurrColumn;
	FCurrRow = coord.Y;
	FCurrColumn = coord.X;
	if (Shift.Contains(ssRight))
	{
		if (FPrevRow == FCurrRow && FPrevColumn == FCurrColumn)
			return;

		FDataMap[FCurrRow][FCurrColumn] = ctEMPTY;
	}
	UpdateCurrentCellDisplay();
	HighlightCell();
}
//---------------------------------------------------------------------------

/*
	When the mouse leaves the grid, we need to update the display.
*/
void __fastcall TfrmMain::gridWorldMouseLeave(TObject *Sender)
{
	FOffGrid = true;
	TRect rect = gridWorld->CellRect(FCurrColumn, FCurrRow);
	TGridDrawState state;
	gridWorldDrawCell(this, FCurrColumn, FCurrRow, rect, state);
	UpdateCurrentCellDisplay();
}
//---------------------------------------------------------------------------

/*
	When the mouse moves onto the grid, we need to start tracking it and update
	the display.
*/
void __fastcall TfrmMain::gridWorldMouseEnter(TObject *Sender)
{
	FOffGrid = false;
	TRect rect = gridWorld->CellRect(FCurrColumn, FCurrRow);
	TGridDrawState state;
	gridWorldDrawCell(this, FCurrColumn, FCurrRow, rect, state);
}
//---------------------------------------------------------------------------

/*
	Handles the event when the sheet (in the PageControl) is resized.
*/
void __fastcall TfrmMain::shtPierResize(TObject *Sender)
{
	AdjustGrid();
}
//---------------------------------------------------------------------------

/*
	If the splitter bar is moved, the board will be resized and needs to be
	updated.
*/
void __fastcall TfrmMain::splitHorizontalMoved(TObject *Sender)
{
	AdjustGrid();
}
//---------------------------------------------------------------------------

/*
	When the user changes the number of rows/columns, need to update the
	display.
*/
void __fastcall TfrmMain::OnCellCountChange(TObject *Sender)
{
	FRows = static_cast<short>(spnRows->Position);
	FColumns = static_cast<short>(spnColumns->Position);
	AdjustGrid();
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::ColorChange(TObject *Sender)
{
	TButton *b = (TButton *)Sender;
	FCurrColorID = ColorMap[b->Tag];
}
//---------------------------------------------------------------------------

/*
	Handler for File Open
*/
void __fastcall TfrmMain::actFileOpenExecute(TObject *Sender)
{
	if (dlgFileOpen->Execute())
	{
		Init();
		if (ImportMapData(dlgFileOpen->FileName.c_str()))
		{
			short r = FRows;
			short c = FColumns;
			spnRows->Position = r;
			spnColumns->Position = c;
		}
	}
}
//---------------------------------------------------------------------------

/*
	Handler for File save
*/
void __fastcall TfrmMain::actFileSaveExecute(TObject *Sender)
{
	if (dlgFileSaveText->Execute())
		ExportMapData(dlgFileSaveText->FileName.c_str());
}
//---------------------------------------------------------------------------

/*
	Handler for testing the level in the game
*/
void __fastcall TfrmMain::actTestExecute(TObject *Sender)
{
		// Export the current level
	ExportMapData(FCurrFileName.c_str());

		// Launch the game
	int result =  WinExec("CS230_Platform.exe", SW_NORMAL);

		// If it failed to launch for some reason
	if (result < 32)
		ShowMessage(IntToStr(result));
}
//---------------------------------------------------------------------------

/*
	Handler for File New
*/
void __fastcall TfrmMain::actFileNewExecute(TObject *Sender)
{
	Init();
}
//---------------------------------------------------------------------------

/*
	Handler for toggling the grid on/off
*/
void __fastcall TfrmMain::actShowGridExecute(TObject *Sender)
{
	FShowGrid = !FShowGrid; // toggle
	ChangeGrid(FShowGrid);
}
//---------------------------------------------------------------------------

void TfrmMain::ChangeGrid(bool Show)
{
	if (Show)
		FGridColor = clBlack;
	else
		FGridColor = MapColors[ctEMPTY];

	gridWorld->Refresh(); // repaint grid
}

/*
	Handler for the WM_DROPFILES message from Windows.
*/
void __fastcall TfrmMain::WMDropFiles(TWMDropFiles &Message)
{
	int num_files, filename_length;
	char file_name[MAX_PATH + 1];

		// Refer to the Win SDK documentation about the details
		// of DragQueryFile and the HDROP structure

		// Get the number of files that are being dropped on the windows
	num_files = DragQueryFile((HDROP)Message.Drop, -1, NULL, 0);

		// Unlikely to happen
	if (num_files < 1)
		return;

		// Retrieve each filename that was dropped
	for(int i = 0; i < num_files; i++)
	{
			// Get the length of the i'th filename
		filename_length = DragQueryFile((HDROP)Message.Drop, i, NULL, 0);

			// Retrieve the filename from the HDROP structure
		DragQueryFile((HDROP)Message.Drop, i, file_name, filename_length + 1);

			// Reset the editor
		Init();

			// Read in the data
		ImportMapData(file_name);

			// Update the UI
		short r = FRows;
		short c = FColumns;
		spnRows->Position = r;
		spnColumns->Position = c;

			// We can only deal with one at this point so bail out
		return;
	}
}

/*
	Handler for the OnMouseDown event. The Shift parameter contains
	additional information such as the state of the control/alt/shift keys.
*/
void __fastcall TfrmMain::gridWorldMouseDown(TObject *Sender,	TMouseButton Button, TShiftState Shift, int X, int Y)
{
		// If the right mouse button is pressed down
	if (Button == mbRight)
	{
			// Convert the x/y pixel location to a row and column in the grid
		TGridCoord coord = gridWorld->MouseCoord(X, Y);

			// Update current row/column
		FCurrRow = coord.Y;
		FCurrColumn = coord.X;

			// Set this cell as empty (background color)
		FDataMap[FCurrRow][FCurrColumn] = ctEMPTY;

			// Paint the cell
		HighlightCell(true);
	}
}
//---------------------------------------------------------------------------

/*
	Handler for the "Help | About..." event
*/
void __fastcall TfrmMain::actHelpAboutExecute(TObject *Sender)
{
	frmAbout->ShowModal();
}
//---------------------------------------------------------------------------

bool TfrmMain::ImportMapData(const AnsiString &FileName)
{
	FILE *fp;
	char tmpStr[20] = {0};
	int Value;
	int i, j;

	fp = fopen(FileName.c_str(), "rt");
	if(!fp)
		return 0;

		// e.g.  Width 20
	fscanf(fp, "%s %i", tmpStr, &Value);
	FColumns = (short)Value;

		// e.g. Height 20
	fscanf(fp, "%s %i", tmpStr, &Value);
	FRows = (short)Value;

	for(j = 0; j < FRows; ++j)
	{
		for(i = 0; i < FColumns; ++i)
		{
			fscanf(fp, "%i", &Value);
			FDataMap[FRows - j - 1][i] = Value;
		}
	}
	fclose(fp);
	return 1;
}

bool TfrmMain::ExportMapData(const AnsiString &FileName)
{
	FILE *fp;
	int Value;
	int i, j;

	fp = fopen(FileName.c_str(), "wt");
	if(!fp)
		return 0;

	fprintf(fp, "Width %i\n", FColumns);
	fprintf(fp, "Height %i\n", FRows);

	for(j = 0; j < FRows; ++j)
	{
		for(i = 0; i < FColumns; ++i)
		{
			Value = FDataMap[FRows - j - 1][i];
			fprintf(fp, "%i ", Value);
		}
		fprintf(fp, "\n");
	}
	fclose(fp);
	return 1;
}


