// hanoi.c
//
// Eine nette interaktive Variante der Türme von Hanoi.
//
// Autor: Jochen Becher

#include <classes/layouter/windows.h>
#include <classes/layouter/boopsigadgets.h>
#include <classes/layouter/decorgadgets.h>
#include <classes/exec/libraries.h>
#pragma header

#include <pragma/intuition_lib.h>

// *************************************************************

// Die Klasse TurmGeometryC übernimmt die Verwaltung der Geometrien in den
// Türmen
class TurmGeometryC : public GeometryGroupC {
public:
	TurmGeometryC(
		GroupLayoutC &frame,
		ULONG topRelation, GeometryC *topObject, WORD topOffset,
		ULONG bottomRelation, GeometryC *bottomObject, WORD bottomOffset,
		ULONG leftRelation, GeometryC *leftObject, WORD leftOffset,
		ULONG rightRelation, GeometryC *rightObject, WORD rightOffset);
	// Eine Geometrie als oberste auf den Turm legen und das dazugehörige
	// Gadget enablen
	VOID add(GeometryC &);
	// Das oberste Element vom Turm nehemn, und das darunterliegende Gadget
	// enablen
	GeometryC *rem();
	// Das oberste Element zurückgeben
	GeometryC *topElement();
};

TurmGeometryC::TurmGeometryC(
		GroupLayoutC &frame,
		ULONG topRelation, GeometryC *topObject, WORD topOffset,
		ULONG bottomRelation, GeometryC *bottomObject, WORD bottomOffset,
		ULONG leftRelation, GeometryC *leftObject, WORD leftOffset,
		ULONG rightRelation, GeometryC *rightObject, WORD rightOffset)
	: GeometryGroupC(frame,
	   topRelation,topObject,topOffset,
	   bottomRelation,bottomObject,bottomOffset,
	   leftRelation,leftObject,leftOffset,
	   rightRelation,rightObject,rightOffset)
{
}

VOID TurmGeometryC::add(GeometryC &g)
{
	ListCursorC lc(*this);
	lc.last();
	if (lc.isDone())
	{
		// Das erste Element wird unten an die Gruppe geklebt
		g.setBottomRule(LAYOUT_GROUP,NULL,0);
	}
	else {
		// Das oberste Element wird disabled
		((LGButtonC *) ((GeometryC *) lc.item())->layoutObject())->disable(TRUE);
		// Das neue Element wird auf das oberste geklebt
		g.setBottomRule(LAYOUT_OPPBORDER,(GeometryC *) lc.item(),-2);
	};
	g.setTopRule(LAYOUT_SIZE,NULL,0);
	// Das neue oberste wird enabled
	((LGButtonC *) g.layoutObject())->disable(FALSE);
	GeometryGroupC::add(g);
}

GeometryC *TurmGeometryC::rem()
{
	// Das oberste Element wird entfernt
	GeometryC *g = (GeometryC *) remTail();
	if (g)
	{
		ListCursorC lc(*this);
		lc.last();
		if (!lc.isDone())
		{
			// Das neue oberste wird enabled
			((LGButtonC *) ((GeometryC *) lc.item())->layoutObject())->disable(FALSE);
		};
	};
	return g;
}

GeometryC *TurmGeometryC::topElement()
{
	ListCursorC lc(*this);
	lc.last();
	return (GeometryC *) lc.item();
};

// *************************************************************

// Jede Scheibe ist ein Button, die mittels dieser Klasse reagiert
class ScheibenEventC : public GadgetEventC {
public:
	ScheibenEventC() : GadgetEventC(), meinTurm(NULL) { };
	VOID up(WindowC *, GadgetC *, IntuiMessageC * );
	VOID setTurm(TurmGeometryC *t) { meinTurm = t; };
private:
	TurmGeometryC *meinTurm;
};

// *************************************************************

// Diese Klasse erzeugt alle Geometrien und Gadgets für das Spiel
class ScheibenkisteC : public GadgetListC {
public:
	ScheibenkisteC(WindowC &, ULONG anzahl);
	~ScheibenkisteC();
	GeometryC *get(ULONG i);
	ULONG anzahl() { return maxanz; };
private:
	arraylist<GeometryC *> scheiben;
	ULONG maxanz;
	stringarray beschriftung;
};

ScheibenkisteC::ScheibenkisteC(WindowC &w, ULONG anzahl)
	: GadgetListC(),
	  scheiben(anzahl),
	  maxanz(anzahl),
	  beschriftung("1","2","3","4","5",NULL)
{
	// Höchstens 5 Scheiben sind erlaubt
	if (maxanz > 5)
		throw Exception();
	ULONG i;
	for (i = 0; i < maxanz; i++)
	{
		ScheibenEventC *ev = new ScheibenEventC();
		if (!ev)
			throw MemoryX(sizeof(ScheibenEventC));
		LBIButtonC *b = new LBIButtonC (ev,w,LAYOUT_AUTOSIZE,LAYOUT_AUTOSIZE,
			GA_Text, beschriftung[i],
			GA_RelVerify, TRUE,
			TAG_END);
		if (!b)
			throw MemoryX(sizeof(LBIButtonC));
		GadgetListC::add(*b);
		GeometryC *g = new GeometryC (*b,
			LAYOUT_GROUP,NULL,0,LAYOUT_SIZE,NULL,0,
			LAYOUT_PROCENT,NULL,5 + i*10,LAYOUT_PROCENT,NULL,95 - i*10);
		if (!g)
			throw MemoryX(sizeof(GeometryC));
		scheiben.addTail() = g;
	};
}

ScheibenkisteC::~ScheibenkisteC()
{
	arraycursor<GeometryC *> ac(scheiben);
	while (!ac.isDone())
	{
		GeometryC *g = ac.item();
		if (g)
		{
			LBIButtonC *b = (LBIButtonC *) g->layoutObject();
			delete g;
			if (b)
			{
				delete b;
			};
		};
		ac.next();
	};
}

GeometryC *ScheibenkisteC::get(ULONG i)
{
	if (i >= maxanz)
		return NULL;
	return scheiben[i];
}

// *************************************************************

class HanoiWindowC : public StandardWindowC {
public:
	HanoiWindowC(GTIDCMPortC &, ScreenC &);
	~HanoiWindowC();
	VOID open();
	TurmGeometryC *zumNaechsten(TurmGeometryC *, LayoutC *);
private:
	LBBevelboxC linksRand,mitteRand,rechtsRand;
	TurmGeometryC links,mitte,rechts;
	TransparentLayoutC minimum;
	GeometryC minimumG;
	ScheibenkisteC scheiben;
	WindowCloseHandlerC wch;
};

HanoiWindowC::HanoiWindowC(GTIDCMPortC &p, ScreenC &s)
	: StandardWindowC(p,s,NULL,
	  WA_IDCMP,IDCMP_GADGETUP,
	  WA_Title,"Die Türme von Hanoi",
	  TAG_END),
	  linksRand(*this,"von",NULL,LAYOUT_AUTOSIZE,LAYOUT_AUTOSIZE,
		TAG_END),
	  mitteRand(*this,"über",NULL,LAYOUT_AUTOSIZE,LAYOUT_AUTOSIZE,
		TAG_END),
	  rechtsRand(*this,"nach",NULL,LAYOUT_AUTOSIZE,LAYOUT_AUTOSIZE,
		TAG_END),
	  links(linksRand,
	   LAYOUT_GROUP,NULL,0,LAYOUT_GROUP,NULL,0,
	   LAYOUT_GROUP,NULL,0,LAYOUT_PROCENT,NULL,33),
	  mitte(mitteRand,
	   LAYOUT_GROUP,NULL,0,LAYOUT_GROUP,NULL,0,
	   LAYOUT_OPPBORDER,&links,2,LAYOUT_PROCENT,NULL,66),
	  rechts(rechtsRand,
	   LAYOUT_GROUP,NULL,0,LAYOUT_GROUP,NULL,0,
	   LAYOUT_OPPBORDER,&mitte,2,LAYOUT_GROUP,NULL,0),
	  minimum(0,0),
	  minimumG(minimum,
	   LAYOUT_GROUP,NULL,0,LAYOUT_GROUP,NULL,0,
	   LAYOUT_GROUP,NULL,0,LAYOUT_GROUP,NULL,0),
	  scheiben(*this,3),
	  wch(*this)
{
	// Alle Scheiben werden auf den ersten Turm gelegt
	ULONG i;
	for (i = 0; i < scheiben.anzahl(); i++)
	{
		if (scheiben.get(i))
		{
			((ScheibenEventC *) ((LBIButtonC *) scheiben.get(i)
				->layoutObject())->event())->setTurm(&links);
			links.add(*scheiben.get(i));
		};
	};
	titlegadgets.add(linksRand);
	titlegadgets.add(mitteRand);
	titlegadgets.add(rechtsRand);
	gadgets.add(scheiben);
	innerGeo.add(links);
	innerGeo.add(mitte);
	innerGeo.add(rechts);
	innerGeo.add(minimumG);
}

HanoiWindowC::~HanoiWindowC()
{
	close();
}

VOID HanoiWindowC::open()
{
	StandardWindowC::open();
	// Dieser Trick "speichert" die Breite des ersten Layouts für weitere
	// Layoutberechnungen, d.h. die Breite und Höhe des Elements minimum
	// wird ab jetzt ein Minimum für jede Layoutberechnung sein.
	minimumG.setBottomRule(LAYOUT_SIZE,NULL,0);
	minimumG.setRightRule(LAYOUT_SIZE,NULL,0);
}

TurmGeometryC *HanoiWindowC::zumNaechsten(TurmGeometryC *turm, LayoutC *lay)
{
	if (turm && lay)
	{
		// Zuerst ein paar Sicherheitstests, ob das Element das oberste auf
		// dem angegeben Turm ist.
		GeometryC *me = turm->topElement();
		if (!me)
			return NULL;
		if (me->layoutObject() != lay)
			return NULL;
		TurmGeometryC *neuerturm = NULL;
		TurmGeometryC *alterturm = turm;
		while (TRUE)
		{
			// Den neuen Turm suchen
			if (turm == &links)
				neuerturm = &mitte
			else if (turm == &mitte)
				neuerturm = &rechts
			else if (turm == &rechts)
				neuerturm = &links;
			// Kein Turm besetzt (sollte nie passieren) oder einmal im Kreis
			// herum, also kann die Scheibe nicht verschoben werden
			if (!neuerturm || neuerturm == alterturm)
				return NULL;
			GeometryC *top = neuerturm->topElement();
			if (!top)
			{
				// Der neue Turm ist leer - kein Problem
				alterturm->rem();
				neuerturm->add(*me);
				reLayout();
				return neuerturm;
			};
			GeometryBaseC *obj;
			WORD offset1, offset2;
			// Die Breite der Scheiben wird einfach über den Prozentwert der
			// linken Regel überprüft. Ziemlich gehackt, würde ich sagen
			me->leftRule(obj,offset1);
			top->leftRule(obj,offset2);
			if (offset1 > offset2)
			{
				// Die Scheibe des neuen Turms ist breiter als die zu
				// verschiebende, also ok
				alterturm->rem();
				neuerturm->add(*me);
				reLayout();
				return neuerturm;
			};
			// Und naechsten Turm versuchen
			turm = neuerturm;
		};
	};
	return NULL;
}

// *************************************************************

VOID ScheibenEventC::up(WindowC *w, GadgetC *g, IntuiMessageC * )
{
	if (meinTurm)
	{
		// Die Scheibe verschieben
		TurmGeometryC *m = ((HanoiWindowC *) w)->zumNaechsten(meinTurm,
			(LBIButtonC *) g);
		if (m)
		{
			// Hat geklappt, also neuer Turm
			meinTurm = m;
		}
		else {
			// Scheibe bleibt auf dem alten Turm
			DisplayBeep(w->window()->WScreen);
		};
	}
	else {
		DisplayBeep(w->window()->WScreen);
	};
}

// *************************************************************

LibraryBaseErrC GadToolsBase("gadtools.library",37);
LibraryBaseErrC UtilityBase("utility.library",37);
LibraryBaseErrC CxBase("commodities.library",37);
LibraryBaseErrC LayersBase("layers.library",37);
LibraryBaseErrC WorkbenchBase("workbench.library",37);

void main()
{
	if (!LibraryBaseC::areAllOpen())
		return;

	SignalsC sc;

	PublicScreenC screen(NULL);
	screen.open();

	GTIDCMPortC port;
	sc.add(port);

	HanoiWindowC window(port,screen);

	GadgetUpHandlerC uphandler;
	port.add(uphandler);

	CtrlCHandlerC ctrlchandler;
	sc.add(ctrlchandler);

	window.open();

	sc.loop();
}

