// Fotopuzzle-Verschiebe-Spiel
//
// Aufruf: puzzle
//
// Klaus Kusche, 2003, 2014, 2025

// Für Fehlermeldungen
#include <iostream>
// Für Zufallszahlen
#include <cstdlib>
#include <ctime>

using namespace std;

#include <wx/app.h> 
#include <wx/frame.h> 
#include <wx/button.h> 
#include <wx/sizer.h> 
#include <wx/event.h> 
#include <wx/bitmap.h> 
#include <wx/image.h> 
#include <wx/dcmemory.h> 
#include <wx/dcclient.h> 

// Wie viele Zeilen und Spalten hat das Spielfeld?
const int Rows = 4;

// Filename: Welches Image soll geladen werden?
const wxString ImageFile = "driver.png";
// Wie viele Pixel ist es hoch und breit?
const int Size = 640;
// Pixel pro Teilbild
const int TileSize = Size / Rows;

// Wie gründlich wird gemischt? (Anzahl Verschiebungen)
const int ScrambleCount = 500;

// Original-Foto, global: Geladen in MyApp, gebraucht in MyPict
wxBitmap Image;

class MyApp final: public wxApp
{
    virtual bool OnInit() override;
};

IMPLEMENT_APP(MyApp)

// Klasse für die eigentliche Spielfeld-Anzeige (das durchgemischte Bild)
class MyPict final: public wxWindow
{
  public:
    MyPict(wxWindow *parent);

    MyPict(const MyPict &orig) = delete;
    MyPict &operator=(const MyPict &orig) = delete;

    void OnPaint(wxPaintEvent &event);
    void OnClick(wxMouseEvent &event);
    
    void Scramble(void);                // zufällig mischen
        
  private:
    // Welcher Bildteil ist wo?
    // 0       ... leeres Feld (entspricht linker oberer Ecke im Original-Bild)
    // 1 bis n ... Bildteil Nummer i 
    int Index[Rows][Rows];                   
};

// Klasse für das Hauptfenster
class MyFrame final: public wxFrame
{
  public:
    MyFrame(void);

    MyFrame(const MyFrame &orig) = delete;
    MyFrame &operator=(const MyFrame &orig) = delete;

    void OnClose(wxCommandEvent &event);
    void OnScramble(wxCommandEvent &event);
        
  private:
    MyPict *Pict;                       // der Bild-Bereich
};

// Nummern der Buttons im Hauptfenster
enum
{
    ID_Close = 1,
    ID_Scramble
};

bool MyApp::OnInit()
{
  srand(time(nullptr));
  
  wxImage::AddHandler(new wxPNGHandler);  // damit wxImage PNG-Files kennt
    
  if (!Image.LoadFile(ImageFile, wxBITMAP_TYPE_PNG)) {
    cerr << "Error loading " << ImageFile << endl;
    return false;
  }
    
  MyFrame *frame = new MyFrame;
  frame->Show(true);
  SetTopWindow(frame);
  return true;
} 

MyFrame::MyFrame(void)
: wxFrame(nullptr, -1, "Puzzle"),
  Pict(new MyPict(this))
{   
  wxBoxSizer *mainsizer = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer *buttonsizer = new wxBoxSizer(wxHORIZONTAL);

  buttonsizer->Add(new wxButton(this, ID_Scramble, "Mischen"), 
    1, wxALL |  wxEXPAND, 5);
  buttonsizer->Add(new wxButton(this, ID_Close, "Beenden"), 
    1, wxALL |  wxEXPAND, 5);
  mainsizer->Add(0, 0, 1);  // vertikaler Füller, damit das Bild mittig ist
  mainsizer->Add(Pict, 0, wxALL | wxALIGN_CENTER, 5);
  mainsizer->Add(0, 0, 1);  // vertikaler Füller, damit das Bild mittig ist
  mainsizer->Add(buttonsizer, 0, wxEXPAND);
  SetSizerAndFit(mainsizer);

  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnClose, this, ID_Close);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnScramble, this, ID_Scramble);
}

void MyFrame::OnClose(wxCommandEvent &WXUNUSED(event))
{
  Close(true);
}

void MyFrame::OnScramble(wxCommandEvent &WXUNUSED(event))
{
  Pict->Scramble();
}

MyPict::MyPict(wxWindow *parent)
: wxWindow(parent, -1, wxDefaultPosition, wxSize(Size, Size), wxBORDER_SIMPLE)
{   
  // Index initialisieren:
  // Leeres Feld links oben, andere Felder in ursprünglicher Reihenfolge
  for (int i = 0; i < Rows; ++i) {
    for (int j = 0; j < Rows; ++j) {
      Index[i][j] = i * Rows + j;
    }
  }

  Bind(wxEVT_PAINT, &MyPict::OnPaint, this);
  Bind(wxEVT_LEFT_UP, &MyPict::OnClick, this);
}

void MyPict::OnPaint(wxPaintEvent &WXUNUSED(event))
{
  // der DC für das angezeigte Fenster
  wxPaintDC dc(this);

  // Ein zusätzlicher DC im Speicher für das Original-Bild,
  // für BitBlit-Operationen
  wxMemoryDC Bitmap;
  Bitmap.SelectObject(Image);

  // ganzes Bild auf Hintergrund-Farbe setzen
  dc.Clear();

  // Teilbilder aus "Bitmap" an die richtige Stelle kopieren
  for (int i = 0; i < Rows; ++i) {
    for (int j = 0; j < Rows; ++j) {
      int k = Index[i][j];
      if (k > 0) // leeres Feld (k == 0) nicht zeichnen!
        dc.Blit(i * TileSize, j * TileSize, TileSize, TileSize,
                &Bitmap, (k / Rows) * TileSize, (k % Rows) * TileSize);
    }
  }

  // Trennstriche zeichnen
  for (int i = TileSize; i < Size; i += TileSize) {
    dc.DrawLine(i, 0, i, Size - 1);
    dc.DrawLine(0, i, Size - 1, i);
  }
}

void MyPict::OnClick(wxMouseEvent &event)
{
  // In welches Feld wurde geklickt?
  int x, y;
  event.GetPosition(&x, &y);   // liefert Pixel-Koordinaten
  if ((x < 0) || (x >= Size) || (y < 0) || (y >= Size)) {
    // gemein: Klick ist außerhalb des Fensters
    // Passiert, wenn die Maus im Fenster gedrückt und
    // mit gedrückter Taste aus dem Fenster rausgezogen wird!
    return;
  }
  x = x / TileSize;
  y = y / TileSize;


  // Prüfe, ob ein Nachbar des geklickten Feldes das leere Feld ist
  // Wenn ja, schieb das eigene Teilbild ins leere Feld
  if ((x > 0) && (Index[x - 1][y] == 0)) {
    Index[x - 1][y] = Index[x][y];
  } else if ((y > 0) && (Index[x][y - 1] == 0)) {
    Index[x][y - 1] = Index[x][y];
  } else if ((x < Rows - 1) && (Index[x + 1][y] == 0)) {
    Index[x + 1][y] = Index[x][y];
  } else if ((y < Rows - 1) && (Index[x][y + 1] == 0)) {
    Index[x][y + 1] = Index[x][y];
  } else {
    return;                             // Klick ignorieren
  }
  // und setze das eigene Feld auf leer
  Index[x][y] = 0;

  // zeige das geänderte Bild an
  Refresh();
}

void MyPict::Scramble(void)
{
  // suche das leere Feld
  int x = 0, y = 0;
  while (Index[x][y] != 0) {
    ++y;
    if (y == Rows) {
      ++x;
      y = 0;
    }
  }

  // schiebe n Mal einen zufälligen Nachbarn in das leere Feld
  // (außer der zufällige Nachbar ist der Rand)
  for (int n = 0; n < ScrambleCount; ++n) {
    switch (rand() % 4) {
      case 0:
        if (x == 0) break;
        Index[x][y] = Index[x - 1][y];
        --x;
        Index[x][y] = 0;
        break;
      case 1:
        if (y == 0) break;
        Index[x][y] = Index[x][y - 1];
        --y;
        Index[x][y] = 0;
        break;
      case 2:
        if (x == Rows - 1) break;
        Index[x][y] = Index[x + 1][y];
        ++x;
        Index[x][y] = 0;
        break;
      case 3:
        if (y == Rows - 1) break;
        Index[x][y] = Index[x][y + 1];
        ++y;
        Index[x][y] = 0;
        break;
    }
  }

  // zeige das geänderte Bild an
  Refresh();
}
