// This may look like C code, but it is really -*- C++ -*-
///////////////////////////////////////////////////////////////////////////
//
//  AMIGA Minesweeper - Open window, init gadgets, ...
//
//  (c) 1992 Hubert Feyrer (c9020@rrzc1.rz.uni-regensburg.de)
//
///////////////////////////////////////////////////////////////////////////

/*
** Diverse "C"-Header
*/
#define class _class
#define template _template
#define IntuitionBase IntuiBase
#define GfxBase GraphBase
extern "C" {
# include <stdio.h>
# include <stdlib.h>
# include <hardware/intbits.h>
# include <exec/types.h>
# include <exec/exec.h>
# include <exec/Interrupts.h>
# include <exec/Memory.h>
# include <exec/Ports.h>
# include <exec/ExecBase.h>
# include <devices/timer.h>
# include <utility/tagitem.h>
# include <intuition/intuition.h>
# include <intuition/intuitionbase.h>
# include <intuition/gadgetclass.h>
# include <graphics/view.h>
# include <libraries/gadtools.h>
# include <clib/gadtools_protos.h>
# include <clib/intuition_protos.h>
# include <clib/exec_protos.h>
# include <math.h>
}    
#undef IntuitionBase
#undef GfxBase 
#undef class
#undef template

/*
** C++-Header
*/
#include "mine.h"
#include "field.h"
#include <MLCG.h>
#include <RndInt.h>



//*
//* (zu) VIELE globale Daten...
//*
IntuiBase *IntuitionBase=NULL;         // Typ NICHT IntuitionBase!!!
GraphBase *GfxBase=NULL;               // Typ NICHT GfxBase!!!
Library *GadToolsBase=NULL;
NewWindow nwin={ 0,0,0,0,-1,-1,IDCMP_CLOSEWINDOW|IDCMP_GADGETUP|
		 IDCMP_MOUSEBUTTONS,WFLG_CLOSEGADGET|WFLG_OTHER_REFRESH
		 |WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_ACTIVATE|WFLG_RMBTRAP
		 ,NULL,NULL,NULL,NULL,NULL,0,0,0,0,WBENCHSCREEN };
Window *win=NULL;
Gadget *glist=NULL;
VisualInfo *vinfo=NULL;
Screen *scr=NULL;
timerequest treq;                      // Für Spielzeit-Anfrage
MsgPort *tport;                        // -"- ----"---- ---"---
int fxs=0;                             // field x size
int fys=0;                             // field y size
int X0;                                // linker Rand des Minenfeldes
int Y0;                                // rechter Rand des Minenfeldes
int win_w;                             // Breite des Spiel-Fensters
int win_h;                             // Höhe des Spiel-Fensters
char *argv0=NULL;                      // argv[0]
NewGadget nnumgad={ -1,-1,-1,-1,NULL,NULL,1000,PLACETEXT_IN,NULL,NULL};
Gadget *numgad=NULL;                   // Textgadget, um Restminen anzuzeigen
char tnumgad[5];                       // minesleft
NewGadget ntimegad={ -1,-1,-1,-1,NULL,NULL,1001,PLACETEXT_IN,NULL,NULL};
Gadget *timegad=NULL;                  // Um Spielzeit anzuzeigen
char ttimegad[5];                      // playtime
NewGadget ngogad={ -1,-1,-1,-1,NULL,NULL,1002,PLACETEXT_IN,NULL,NULL};
Gadget *gogad=NULL;                    // Restart-Button (nach Crash)
TextFont *txtfont;                     // mit setfont eingestellter Font
int countdown;                         // Restliche Felder
int minesleft;                         // Anzahl unentdeckter Minen
Offset validfields[]={ {-1,-1}, { 0,-1}, { 1,-1}, {-1, 0},
		       { 1, 0}, {-1, 1}, { 0, 1}, { 1, 1} };



//*
//* Resourcen freigeben und Programm beenden.
//*
void shutdown(int rc, char *msg=NULL)
{
    if(treq.tr_node.io_Message.mn_ReplyPort) CloseDevice((IORequest *)&treq);
    if(tport) DeletePort((MsgPort *)tport);
    if(win) CloseWindow(win);
    if(glist) FreeGadgets(glist);
    if(minefield){
	for(int i=0;i<lenx;i++){
	    if(minefield[i]){
		for(int j=0;j<leny;j++){
		    if(minefield[i][j]){
			delete minefield[i][j];
		    }
		}
		delete[] minefield[i];
	    }
	}
	delete[] minefield;
    }
    if(vinfo) FreeVisualInfo(vinfo);
    if(GadToolsBase) CloseLibrary(GadToolsBase);
    if(GfxBase) CloseLibrary((Library *)GfxBase);
    if(IntuitionBase) CloseLibrary((Library *)IntuitionBase);

    msg && printf("%s: %s\n",argv0,msg);
    exit(rc);
}


//*
//* Fenster, Gadget, ... initialisieren
//*
void init(int p)
{
    ULONG lock;
    int i,j;

    // Diverse Bibliotheken öffnen
    IntuitionBase=(IntuiBase *)OpenLibrary("intuition.library",37);
    if(IntuitionBase==NULL) shutdown(20,"No OS2.0-Intuition found!");

    GfxBase=(GraphBase *)OpenLibrary("graphics.library",37);
    if(GfxBase==NULL) shutdown(20,"Need some OS2.0-Gfx!");

    GadToolsBase=(Library *)OpenLibrary("gadtools.library",37);
    if(GadToolsBase==NULL) shutdown(20,"Where's my GadTools-Library?!");

    // Maßeinheiten ermitteln (f. Fenster, Gadgets, ...)
    lock=LockIBase(0);
    scr=IntuitionBase->ActiveScreen;
    UnlockIBase(lock);
    txtfont=(TextFont *)OpenFont(scr->Font);      // Mit c:setfont festge-
                                                  // legter Zeichensatz
    fxs=2*txtfont->tf_XSize;
    if(scr->ViewPort.Modes&~LACE==0) fxs*=2;    // LoRes
    fys=txtfont->tf_YSize+3;
    CloseFont(txtfont);
    X0=1.5*fxs;
    Y0=3*fys;
    win_w=X0+(1.5*fxs)+(lenx*fxs);
    win_h=Y0+fys+(leny*fys);

    // Gadgets initialisieren
    vinfo=GetVisualInfo(scr,TAG_END);
    if(vinfo==NULL){
	shutdown(20,"Can't GetVisualInfo()!");
    }

    Field::lastgad=CreateContext(&glist);
    if( Field::lastgad==NULL ){
        shutdown(20,"Can't CreateContext()!");
    }
    
    // Minenfeld errichten
    countdown=0;
    minefield=new Field **[lenx];
    for(i=0;i<lenx;i++){
	minefield[i]=new Field *[leny];
	for(j=0;j<leny;j++){
	    minefield[i][j]=new Field(X0+i*fxs,Y0+j*fys,i,j);
	}
    }

    // Minenanzeige-Gadget
    sprintf(tnumgad,"%04d",(lenx*leny*p)/100);
    if(tnumgad[0]=='0') tnumgad[0]=' ';
    nnumgad.ng_VisualInfo = vinfo;
    nnumgad.ng_TextAttr   = scr->Font;
    nnumgad.ng_LeftEdge   = fxs;
    nnumgad.ng_TopEdge    = 1.45*fys;
    nnumgad.ng_Width      = 2.8*fxs;
    nnumgad.ng_Height     = fys;
    Field::lastgad=numgad=CreateGadget(TEXT_KIND,Field::lastgad,&nnumgad,
				       GTTX_Text,tnumgad,
				       GTTX_Border, 2,
				       TAG_END);
    if(numgad==NULL) shutdown(20,"Can't create numgad!");

    // Zeitanzeige-Gadget
    sprintf(ttimegad,"%04d",playtime);
    ntimegad.ng_VisualInfo = vinfo;
    ntimegad.ng_TextAttr   = scr->Font;
    ntimegad.ng_Width      = 2.8*fxs;
    ntimegad.ng_Height     = fys;
    ntimegad.ng_LeftEdge   = win_w-(fxs+ntimegad.ng_Width);
    ntimegad.ng_TopEdge    = 1.45*fys;
    Field::lastgad=timegad=CreateGadget(TEXT_KIND,Field::lastgad,&ntimegad,
					GTTX_Text,ttimegad,
					GTTX_Border, 2,
					TAG_END);
    if(numgad==NULL) shutdown(20,"Can't create timegad!");

    // Restart-Button
    ngogad.ng_VisualInfo = vinfo;
    ngogad.ng_TextAttr   = scr->Font;
    ngogad.ng_GadgetText = "Go!";
    ngogad.ng_Width      = ntimegad.ng_LeftEdge-floor(5.4*fxs);
    ngogad.ng_LeftEdge   = 4.6*fxs;
    ngogad.ng_TopEdge    = 1.45*fys;
    ngogad.ng_Height     = fys;
    Field::lastgad=gogad=CreateGadget(BUTTON_KIND,Field::lastgad,&ngogad,
				      TAG_END);
    // gogad->Flags |= GFLG_DISABLED;
    if(gogad==NULL) shutdown(20,"Can't create gogad!");
    
    
    // Fenster aufmachen
    nwin.Width       = win_w;
    nwin.Height      = win_h;
    nwin.LeftEdge    = (scr->Width-win_w)/2;
    nwin.TopEdge     = (scr->Height-win_h)/2;
    nwin.Title       = WIN_T;
    if(nwin.LeftEdge<=0 || nwin.TopEdge<=0)
      shutdown(20,"Choose smaller dimension or font!");
    nwin.FirstGadget = glist;
    win=OpenWindowTags(&nwin,TAG_END);
    if(win==NULL) shutdown(20,"Can't open window!");

    // Timer-Device öffnen und Uhr initialisieren
    tport=CreatePort("Mine timer-port",0);
    if(tport==NULL) shutdown(20,"Can't create timer-port!");
    if(OpenDevice("timer.device",UNIT_VBLANK,(IORequest *)&treq,0))
      shutdown(20,"No timer.device?!?");
    treq.tr_node.io_Message.mn_ReplyPort = tport;
    treq.tr_node.io_Command              = TR_ADDREQUEST;
    treq.tr_node.io_Flags                = 0;
    treq.tr_node.io_Error                = 0;
    treq.tr_time.tv_secs                 = 1;     // 1 Sekunde warten
    treq.tr_time.tv_micro                = 0;
    playtime=0;

    return;
}


//*
//* Felder aufdecken, Minen anzeigen
//*
void showmines(void)
{
    for(int i=0;i<lenx;i++){
	for(int j=0;j<leny;j++){
	    minefield[i][j]->open(1);
	}
    }
}


//*
//* Felder zuschütten, Markierung löschen
//*
void removemines(void)
{
    for(int i=0;i<lenx;i++){
	for(int j=0;j<leny;j++){
	    minefield[i][j]->clear();
	}
    }
    RefreshGadgets(glist,win,NULL);
}


//*
//* Anzahl der Nachbarminen bei freien Feldern errechnen
//*
void calc_cnt(void)
{
    for(int j=0;j<leny;j++){
	for(int i=0;i<lenx;i++){
	    if(minefield[i][j]->cnt()!=-1){
		int no=0;        // Anzahl Minen in angrenzenden Feldern

		// Umgebung jedes einzelnen Feldes absuchen
		for(int v=0;v<8;v++){
		    int vi=i+validfields[v].dx;
		    int vj=j+validfields[v].dy;
		    
		    if(inminefield(vi,vj)){
			if(minefield[vi][vj]->cnt()==-1){
			    no++;
			}
		    }
		}
		minefield[i][j]->cnt(no);
	    }
	}
    }
}

//*
//* Liefert Zufallszahl in [0;high) mit Algorithmus aus libg++
//*
int rnd(int high)
{
    static MLCG gen(0,(long)time(NULL));    
    static RandomInteger r(0,65535,&gen); 

    return int(r.asLong(long(high-1)));
}


//*
//* Minenfeld zu p Prozent verminen und Anzahl Minen in
//* Nachbarfeldern berechnen lassen.
//*
void hidemines(int p)
{
    int n=(lenx*leny*p)/100.0;     // Anzahl zu legender Minen
    minesleft=n;

    while(n){
	int x=rnd(lenx);
	int y=rnd(leny);
	
	if(minefield[x][y]->cnt()==-1) continue;
	minefield[x][y]->cnt(-1);
	--n;
    }
    
    calc_cnt();
}
