// Geom. Objekte: Statische Member: drawAll (Version mit Set, Z-Wert und Nummer)
// Header für abstrakte Basisklasse "GraObj"
//
// Klaus Kusche, 2020

#ifndef _GRAOBJ_H
#define _GRAOBJ_H

#include "color.h"

// Wir verwenden zur Speicherung aller Objekte ein Set:
// Das ist ein Container, der die Werte intern sortiert speichert
// (implementiert als ein balancierter binärer Suchbaum)
#include <set>

using namespace std;

// Vorab-Deklaration, damit der folgende Code weiß,
// dass GraObj der Name einer Klasse ist
class GraObj;  

// Normalerweise verwendet set das normale "<" zum Vergleich bzw. zum Ordnen
// Es gibt viele Möglichkeiten, einen eigenen Vergleich anzugeben:
// Mit Lambda's, mit einer eigenen Klasse mit operator(),
// mit Überschreiben des "operator<" und "less<>", 
// mit einem Function Pointer auf eine normale Vergleichsfunktion usw.

// Wir verwenden hier eine Klasse mit operator()
// set erzeugt ein Objekt dieser Klasse und verwendet es als Funktions-Objekt,
// d.h. ruft zum Vergleich zweier Elemente obj(elem1, elem2) auf
// operator() soll sich wie < verhalten:
// bool-Returnwert, true wenn a < b, false wenn a >= b
class GraObjCompare
{
  public:
    bool operator()(const GraObj *a, const GraObj *b) const;
};

// Und das ist der Typ unseres set:
// set mit GraObj-Pointern als Elementen
// und unserer Vergleichsobjekt-Klasse
typedef set<GraObj *, GraObjCompare> graPtrSet;

class GraObj
{
  // Die Vergleichsfunktion gehört nicht zur Klasse,
  // aber soll direkt auf mZ zugreifen könnnen
  friend class GraObjCompare;

  public:
    // 1. Neuer Parameter für mZ dazu
    // 2. In mNr die nächste Nummer speichern und sNextNum weiterzählen
    // 3. Objekt in sObjects eintragen
    GraObj(const Color &color, int x, int y, int w, int h,
          int sx = 0, int sy = 0, int z = 0)
    : mRGB(color), mX(x), mY(y), mW(w), mH(h), mSX(sx), mSY(sy), mZ(z),
      mNr(sNextNum++),
      // füge einen Pointer auf das gerade erzeugte Objekt in sObjects ein
      // Lösung mit mMyEntry: insert liefert ein pair aus Iterator und bool
      mMyEntry(sObjects.insert(this).first)
    {
      // Lösung ohne mMyEntry:
      //sObjects.insert(this);
    }

    // Auch der Copy-Konstruktor muss das Objekt in sObjects eintragen
    // und ihm eine neue Nummer geben
    GraObj(const GraObj &orig)
    : mRGB(orig.mRGB), mX(orig.mX), mY(orig.mY), mW(orig.mW), mH(orig.mH),
      mSX(orig.mSX), mSY(orig.mSY), mZ(orig.mZ),
      mNr(sNextNum++),
      // Lösung mit mMyEntry: insert liefert ein pair aus Iterator und bool
      mMyEntry(sObjects.insert(this).first)
    {
      // Lösung ohne mMyEntry:
      //sObjects.insert(this);
    }

    // ... und der Destruktor muss das Objekt aus sObjects rauslöschen
    virtual ~GraObj()
    {
      // Lösung ohne mMyEntry: Löschen nach Wert
      //sObjects.erase(this);
      // Lösung mit mMyEntry: Löschen mittels Iterator
      // (bei einem Set ist der Gewinn im Unterschied zur Liste nur klein)
      sObjects.erase(mMyEntry);
    }

    virtual GraObj *clone() const = 0;

    Color getColor() const { return mRGB; }
    int getX() const { return mX; }
    int getY() const { return mY; }
    int getW() const { return mW; }
    int getH() const { return mH; }
    int getSX() const { return mSX; }
    int getSY() const { return mSY; }
    int getZ() const { return mZ; }

    void setColor(const Color &color)
    {
      mRGB = color;
      draw();
    }

    void setSpeed(int sx, int sy)
    {
      mSX = sx; mSY = sy;
    }
    
    void setPos(int x, int y);
    void move(int dx, int dy);
    void setSize(int w, int h);
    void scale(int percentX, int percentY);

    // Setze den Z-Wert eines Objektes neu
    void setZ(int z)
    {
      // Wenn wir einfach nur den Z-Wert ändern würden,
      // würde das die Reihenfolge in sObjects zerstören,
      // da das Objekt nicht automatisch umsortiert wird.
      // Daher: Rausnehmen, Z-Wert ändern, wieder reingeben
      sObjects.erase(this);
      mZ = z;
      sObjects.insert(this);
    }

    bool fly(bool bounce = false);

    virtual void draw() = 0;
    virtual void undraw() = 0;

    // Klassenweite Methode: Zeichne alle Objekte neu, in Z-Reihenfolge
    // (wird nicht für ein bestimmtes Objekt aufgerufen,
    // sondern für die ganze Klasse)
    static void drawAll();
  
  protected:
    Color mRGB;
    int mX, mY;
    int mW, mH;
    int mSX, mSY;

  // die abgeleiteten Klassen dürfen nicht an den Z-Werten
  // und dem Verzeichnis aller Objekte herumfummeln
  // und auch nicht an den Objekt-Nummern
  private:
    int mZ;   // Tiefe des eigenen Objektes (für die Zeichen-Reihenfolge)
    int mNr;  // Nummer des Objektes (jedes Objekt enthält seine eigene Nummer)
    static int sNextNum;  // nächste zu vergebende Nummer

    // Verzeichnis aller GraObj:
    // Ein Set, dessen Elemente vom Typ "GraObj *" sind
    static graPtrSet sObjects;
    // optional: Iterator auf das eigene Element im Set
    graPtrSet::iterator mMyEntry;  

};

#endif
