// Die Gui-Klasse für Sudoku
// In dieser Klasse ist die GUI-Logik, aber nicht die Lösungslogik
// Klaus Kusche, 2010

#include <wx/wx.h>
#include "sudoku.h"
#include "solver.h"

// Text für unbelegte Felder
#define EMPTY ""

// Verzögerung pro Update in Slow Motion, ms
#define UPD_MS 100

enum
{
  // Nummern für unsere Event-Quellen...
  ID_Go = 1,
  ID_Cont,
  ID_Stop,
  ID_Back,
  ID_Clear,
  ID_About,
  ID_Quit,
  ID_SloMo
};

// eigene Events für die Kommunikation vom Worker-Thread zum Gui-Thread
wxDECLARE_EVENT(wxEVT_COMMAND_MYTHREAD_UPDATE, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_COMMAND_MYTHREAD_UPDATE, wxThreadEvent);
wxDECLARE_EVENT(wxEVT_COMMAND_MYTHREAD_SOLUTION, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_COMMAND_MYTHREAD_SOLUTION, wxThreadEvent);
wxDECLARE_EVENT(wxEVT_COMMAND_MYTHREAD_END, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_COMMAND_MYTHREAD_END, wxThreadEvent);

Sudoku::Sudoku() :
  wxFrame(nullptr, -1, "Sudoku", wxPoint(50, 50), wxSize(1500, 1000))
{
  int x, y;
  wxSizer *vert, *buttons;
  wxPanel *panel;
  wxGridCellEditor *edit;

  running = false;
  
  panel = new wxPanel(this, -1);
  vert = new wxBoxSizer(wxHORIZONTAL);
  buttons = new wxBoxSizer(wxVERTICAL);

  edit = new wxGridCellNumberEditor(0, 9);
  grid = new wxGrid(panel, -1);
  grid->CreateGrid(NUM_COL, NUM_COL);
  grid->EnableGridLines();
  grid->HideColLabels();
  grid->HideRowLabels();
  grid->SetDefaultColSize(110, true);
  grid->SetDefaultRowSize(90, true);
  grid->SetDefaultEditor(edit);
  grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
  grid->SetDefaultCellFont(grid->GetDefaultCellFont().Bold().Larger().Larger());
  
  for (x = 0; x < NUM_COL; ++x) {
    for (y = 0; y < NUM_COL; ++y) {
      grid->SetCellValue(x, y, EMPTY);
    }
  }

  CreateStatusBar();
  SetStatusText("Welcome to Sudoku!");

  goButton = new wxButton(panel, ID_Go, "Start");
  goButton->SetToolTip("Start searching for solutions");
  buttons->Add(goButton, 1, wxEXPAND | wxBOTTOM, 4);
  contButton = new wxButton(panel, ID_Cont, "Continue");
  contButton->SetToolTip("Continue searching for solutions");
  buttons->Add(contButton, 1, wxEXPAND | wxBOTTOM, 4);
  stopButton = new wxButton(panel, ID_Stop, "Stop");
  stopButton->SetToolTip("Cancel searching for solutions");
  buttons->Add(stopButton, 1, wxEXPAND | wxBOTTOM, 4);
  backButton = new wxButton(panel, ID_Back, "Start Values");
  backButton->SetToolTip("Set all cells to their most recent start value");
  buttons->Add(backButton, 1, wxEXPAND | wxBOTTOM, 4);
  clearButton = new wxButton(panel, ID_Clear, "Reset");
  clearButton->SetToolTip("Set all cells to empty values");
  buttons->Add(clearButton, 1, wxEXPAND | wxBOTTOM, 4);
  aboutButton = new wxButton(panel, ID_About, "About");
  aboutButton->SetToolTip("Program information");
  buttons->Add(aboutButton, 1, wxEXPAND | wxBOTTOM, 4);
  quitButton = new wxButton(panel, ID_Quit, "Exit");
  quitButton->SetToolTip("End program");
  buttons->Add(quitButton, 1, wxEXPAND | wxBOTTOM, 4);

  contButton->Disable();
  stopButton->Disable();

  buttons->AddSpacer(10);
  slowMotionBox = new wxCheckBox(panel, ID_SloMo, "Slow Motion");
  slowMotionBox->SetToolTip("Slowly display each step");
  buttons->Add(slowMotionBox, 1, wxEXPAND | wxALL, 0);
  sloMo = slowMotionBox->GetValue();

  vert->Add(grid, 0, wxALIGN_CENTER | wxALL, 20);
  vert->Add(buttons, 1, wxEXPAND | wxALL, 20);
  panel->SetSizer(vert);

  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnGo, this, ID_Go);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnCont, this, ID_Cont);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnStop, this, ID_Stop);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnBack, this, ID_Back);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnClear, this, ID_Clear);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnAbout, this, ID_About);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Sudoku::OnQuit, this, ID_Quit);
  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &Sudoku::OnSloMo, this, ID_SloMo);

  Bind(wxEVT_COMMAND_MYTHREAD_UPDATE, &Sudoku::OnUpdate, this);
  Bind(wxEVT_COMMAND_MYTHREAD_SOLUTION, &Sudoku::OnSolution, this);
  Bind(wxEVT_COMMAND_MYTHREAD_END, &Sudoku::OnThreadEnd, this);
  Bind(wxEVT_CLOSE_WINDOW, &Sudoku::OnClose, this);
}

int Sudoku::GetValue(int pos)
{
  return startVals[pos];
}

int Sudoku::SetValue(int pos, int v)
{
  wxThreadEvent *evt;
  
  curVals[pos] = v;

  if (sloMo) {
    evt = new wxThreadEvent(wxEVT_COMMAND_MYTHREAD_UPDATE);
    evt->SetInt(pos);
    wxQueueEvent(this, evt);
    wxMilliSleep(UPD_MS);
  }

  return (killed = GetThread()->TestDestroy());
}

int Sudoku::ResetValue(int pos)
{
  return SetValue(pos, 0);
}

int Sudoku::Solution(int n)
{
  wxThreadEvent *evt;

  evt = new wxThreadEvent(wxEVT_COMMAND_MYTHREAD_SOLUTION);
  evt->SetInt(n);
  wxQueueEvent(this, evt);
  wxMilliSleep(100);
  // während dieser Zeit werden wir vom Haupt-Thread suspended,
  // bis "Continue" gedrückt wird

  return (killed = GetThread()->TestDestroy());
}

wxThread::ExitCode Sudoku::Entry()
{
  int n;
  wxThreadEvent *evt;
  Solver s(this);

  n = s.Go();
  evt = new wxThreadEvent(wxEVT_COMMAND_MYTHREAD_END);
  evt->SetInt(n);
  wxQueueEvent(this, evt);

  return (wxThread::ExitCode) 0;
}

void Sudoku::OnGo(wxCommandEvent &WXUNUSED(event))
{
  int x, y, pos;
  wxString s;
  long int val;
  
  for (x = 0; x < NUM_COL; ++x) {
    for (y = 0; y < NUM_COL; ++y) {
      pos = y * NUM_COL + x;
      s = grid->GetCellValue(x, y);
      curVals[pos] = startVals[pos] = (s.ToLong(&val)) ? val : 0;
    }
  }
  
  running = true;
  killed = false;

  grid->EnableEditing(false);
  goButton->Disable();
  stopButton->Enable();
  backButton->Disable();
  clearButton->Disable();
  
  SetStatusText("Working ...");
  
  if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) {
    wxLogFatalError("Could not create the worker thread!");
  }
  if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
    wxLogFatalError("Could not run the worker thread!");
  }
}

void Sudoku::OnCont(wxCommandEvent &WXUNUSED(event))
{
  contButton->Disable();
  SetStatusText("Working ...");
  GetThread()->Resume();  
}

void Sudoku::OnStop(wxCommandEvent &WXUNUSED(event))
{
  contButton->Disable();
  stopButton->Disable();
  SetStatusText("Stopping ...");
  GetThread()->Delete();
}

void Sudoku::OnBack(wxCommandEvent &WXUNUSED(event))
{
  int x, y;
  int v;
  
  for (x = 0; x < NUM_COL; ++x) {
    for (y = 0; y < NUM_COL; ++y) {
      v = startVals[y * NUM_COL + x];
      if (v == 0) grid->SetCellValue(x, y, EMPTY);
      else grid->SetCellValue(x, y, wxString((char)(v + '0')));  
    }
  }
}

void Sudoku::OnClear(wxCommandEvent &WXUNUSED(event))
{
  int x, y;
  
  for (x = 0; x < NUM_COL; ++x) {
    for (y = 0; y < NUM_COL; ++y) {
      grid->SetCellValue(x, y, EMPTY);
    }
  }
}

void Sudoku::OnAbout(wxCommandEvent &WXUNUSED(event))
{
  wxMessageBox("Sudoku\nKlaus Kusche", "About Sudoku",
              wxOK|wxICON_INFORMATION, this);
}

void Sudoku::OnQuit(wxCommandEvent &WXUNUSED(event))
{
  Close(true);
}

void Sudoku::OnUpdate(wxThreadEvent &event)
{
  int x, y;
  int pos;
  int v;

  pos = event.GetInt();
  v = curVals[pos];
  y = pos / NUM_COL;
  x = pos % NUM_COL;
  if (v == 0) grid->SetCellValue(x, y, EMPTY);
  else grid->SetCellValue(x, y, wxString((char)(v + '0')));  
}

void Sudoku::OnSolution(wxThreadEvent &event)
{
  wxString s;
  int n;

  GetThread()->Pause();
  RefreshGrid();

  contButton->Enable();
  stopButton->Enable();

  n = event.GetInt();
  s.Printf("Solution %d", n);
  SetStatusText(s);
}

void Sudoku::OnThreadEnd(wxThreadEvent &event)
{
  wxString s;
  int n;

  grid->EnableEditing(true);
  goButton->Enable();
  contButton->Disable();
  stopButton->Disable();
  backButton->Enable();
  clearButton->Enable();

  n = event.GetInt();
  if (n < 0) s.Printf("Illegal input: Duplicate value %d", -n);
  else if (killed) {
    if (n == 0) s.Printf("Cancelled before any solution was found");
    else s.Printf("Cancelled after %d solutions", n);
  } else {
    if (n == 0) s.Printf("Done - No sulutions");
    else s.Printf("Done - %d solutions in total", n);
  }
  SetStatusText(s);

  running = false;
  GetThread()->Wait();
}

void Sudoku::OnSloMo(wxCommandEvent &WXUNUSED(event))
{
  sloMo = slowMotionBox->GetValue();

  if (sloMo && running) RefreshGrid();
}

void Sudoku::OnClose(wxCloseEvent &WXUNUSED(event))
{
  if (running) {
    GetThread()->Delete();
    GetThread()->Wait();
  }

  Destroy();
}

void Sudoku::RefreshGrid()
{
  int x, y;
  int v;
  
  for (x = 0; x < NUM_COL; ++x) {
    for (y = 0; y < NUM_COL; ++y) {
      v = curVals[y * NUM_COL + x];
      if (v == 0) grid->SetCellValue(x, y, EMPTY);
      else grid->SetCellValue(x, y, wxString((char)(v + '0')));  
    }
  }
}
