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

#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 = spnRows->Position;
  FColumns = 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;
}