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

                         Denne filen er en del av
                           EXEC's store C-kurs

                              Programmert av 
                    Ørjan Eriksen og Torstein Pettersen

                          database.c   leksjon 5
  
**************************************************************************/

#include <exec/types.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <arp/arpbase.h>
#include <string.h>

#include "base.h"
#include "baseview.h"  /* inneholder definisjoner på vinduet og alle gadgets */

/* startposisjon for ramme rundt info tekst */
#define WPOSX	10 
#define WPOSY	70

#define ONOFFGADS 7 /* antall gadgets som skal slåes av og på */

/* dette er rutiner som finnes i andre moduler,
   de settes inn av linkeren */

extern struct Liste *hent_liste(struct Liste **,struct Liste **);
extern void lagre_liste(struct Liste *);

/* globale funksjoner */

void show_warn(char *);
void sett_inn_post(struct Liste **,struct Liste **,struct Liste *);

/* lokale funksjoner */

static void legg_inn_data(struct Liste *);
static int mystrcmp(UBYTE *,UBYTE *);
static struct Liste *finn_post(struct Liste *);
static void draw_display();
static struct Liste *fjern_post(struct Liste **,struct Liste **,struct Liste *);
static void plukk_ut_post(struct Liste **,struct Liste **,struct Liste *);
static int open_display();
static int open_libs();
static void cleanup();
static struct Liste *ny_post(struct Liste **,struct Liste **,struct liste *);
static void turn_off_gadgets();
static int turn_on_gadgets();
static void vis_nypost(struct Liste *);
static int event1();

/* diverse globale variabler -> for alle moduler */

struct Window *w = NULL; 			/* hoved vinduet */
struct ArpBase *ArpBase = NULL;		/* base peker til arp.library -> DISK libs: */
struct GfxBase *GfxBase = NULL;		/* base peker til graphics.library -> ROM */
struct IntuitionBase *IntuitionBase = NULL;	/* base peker til intuition.library ->ROM */

/* diverse lokale variabler -> for denne modulen */

static struct RastPort *rp;  		/* peker til rastport for vinduet */
static struct IntuiMessage *mesg; 	/* peker til beskjed fra operativ systemet */
static struct Gadget *MIAddress; 	/* peker til den gadget som blir valgt */
static ULONG wakeup;				/* verdi som viser hvilket vindu som beskjeden kom fra */
static ULONG class;					/* hvilken type beskjed som kom */
static USHORT code;					/* nærmere beskrivelse av beskjeden */

static USHORT curr_gadget; /* peker til gadget som er nedtrykkt */
static struct Liste *lhode = NULL;
static struct Liste *lslutt = NULL;
static struct Liste *denne = NULL;	/* peker til den post som er aktiv i vinduet */

/* diverse variabler til styring av INTUITICKS og spoling */
static int tilstand;
static int ticks,delay,selected;

/************************************************************************/
/* spesial STRCMP for å sortere norske æ,ø og å */

static int mystrcmp(str1,str2)
register UBYTE *str1,*str2;
{
	register short s1,s2;
	register UBYTE c1,c2;
	
	while (*str1) {
		c1 = *str1; /* plukk ut et tegn fra tekst 1 */
		c2 = *str2; /* plukk ut et tegn fra tekst 2 */
		s1 = (short)c1; /* gjør tegne 1 om til short */
		s2 = (short)c2; /* gjør tegne 2 om til short */

		if (c1 == 'Å' || c1 == 'å') /* hvis tegn 1 er 'å' øk s1 med 100 */
			s1 += 100;
		if (c2 == 'Å' || c2 == 'å') /* hvis tegn 2 er 'å' øk s2 med 100 */
			s2 += 100;

/* sjekk om de er forskjellig */
		if (s1-s2)
			return(s1-s2); /* returner differansen hvis de er forskjellig */
		str1++; /* øk begge tekst peker med 1 for å sjekke neste tegn */
		str2++;
	}

	return(0); /* hvis tekstene er like returneres 0 */
}

/****************** her starter de globale funksjonene ******************/

/* denne rutinen brukes for å vise hva som skjer i programmet */

void show_warn(str)
register char *str;  /* str = peker til en tekst streng */
{
/* rp er en global veriabel som peker til RastPorten til vinduet vårt */

	SetDrMd(rp,JAM1);		/* bruk bare forgrundsfarge */
	SetAPen(rp,2);			/* forgrunsfarge settes til farge 2 (sort) */
	RectFill(rp,WPOSX+2,WPOSY-1,w->Width-11,WPOSY+7); /* fyller et rectangel med forgrundsfarge */
	SetAPen(rp,3);	/* ny forgrundsfarge 3 (orange) */

	if (str) { /* kalles rutine med en NULL streng hopper vi over dette */

/* regn ut posisjon for sentrering av tekst 							*/
/* divider bredden av vinduet på 2, (w->Width >> 1) = (w->Width / 2)	*/
/* finn bredden i punkter av teksten (strlen(str)<<3) = (strlen(str)*8)	*/
/* halver bredden av teksten og trekk den fra bredden av hvinduet. 		*/
/* Her bruker vi litt triksing for å gjøre dette så raskt som mulig 	*/
/* For å dele med 2 bruker vi -> (verdi>>1) = (verdi/2)					*/
/* For å multiplisere med 8 bruker vi -> (verdi<<3) = (verdi*8)			*/

		Move(rp,(w->Width>>1)-((strlen(str)<<3)>>1),WPOSY+5); /* sett posisjon */
		Text(rp,str,strlen(str)); /* skriv ut teksten str i vinduet */
	}
}

/************************************************************************/
/* denne rutinen legger inn de nye postenen i riktig rekkefølge */

void sett_inn_post(lhd,lsl,pst)
struct Liste **lhd;
struct Liste **lsl;
register struct Liste *pst;
{
	register struct Liste *crt;

	if (pst == NULL)
		return;

	crt = *lhd;

	if (crt == NULL) {
		*lhd = pst;
		*lsl = pst;
		return;
	}
	else {
		while (crt) {
			if (mystrcmp(crt->Navn,pst->Navn) < 0)
				crt = crt->neste;
			else
				break;
		}
		if (crt == *lhd) {
			crt->forrige = pst;
			pst->neste = crt;
			*lhd = pst;
		}
		else if (crt) {
			pst->neste = crt;
			pst->forrige = crt->forrige;
			crt->forrige->neste = pst;
			crt->forrige = pst;
		}
		else if (crt == NULL) {
			pst->forrige = *lsl;
			pst->forrige->neste = pst;
			*lsl = pst;
		}
	}
}

/****************** her begyner de lokale funksjonene *******************/

/* denne rutinen åpner alle nødvendige libraries */

static int open_libs()
{
/*
  Base pekere til libraries i rom og på disk.
  Libraries på disk blir automatisk hentet inn i minnet av OpenLibrary
*/
	if (!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0)))
		return(0);
	if (!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)))
		return(0);

/* arp.library må ligge i libs: katalogen */

	if (!(ArpBase = (struct ArpBase *)OpenLibrary("arp.library",0)))
		return(0);

	return(1);
}

/************************************************************************/
/* her åpnes hovedvinduet til database programmet */

static int open_display()
{
/* prøver å åpne et vindu, returnere 0 hvis det ikke går */
	if (!(w  = OpenWindow(&hovednw)))
		return(0);

	rp = w->RPort; 			/* her får vi RastPort'en til vinduet */
	SetMenuStrip(w,&Menu1);	/* aktiver menyen til vinduet */
	return(1);				/* alt ok returner 1 */
}

/************************************************************************/
/* denne rutinen sletter alle poster i listen og frigjør minnet de brukte */

static void slett_alt(hode)
register struct Liste *hode;
{
	register struct Liste *lst,*dummy;

	lst = hode;

	while (lst) {
		dummy = lst->neste;
		FreeMem(lst,LLENGDE);
		lst = dummy;
	}
}

/************************************************************************/
/* denne rutinen lukker alt vi har åpnet og frigjør minne for listen */

static void cleanup()
{
	slett_alt(lhode);

	if (w) {
		ClearMenuStrip(w);	/* ta bort menyen */
		CloseWindow(w);		/* lukk vinduet */
	}

/* lukk alle libraries som er brukt av programmet */

	if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *)GfxBase);
	if (ArpBase) CloseLibrary((struct Library *)ArpBase);
}

/************************************************************************/
/* denne rutinen tegner bakgrunnen og rammene inne i vinduet */

static void draw_display()
{
	SetDrMd(rp,JAM1);							/* sett tegne modus */
	SetAPen(rp,2);								/* set pen til farge 2 (svart) */
	RectFill(rp,4,11,w->Width-5,80); 			/* fyller øverste del av vinduet med farge 2 */
	SetAPen(rp,1);								/* set pen farge til 1 (hvit) */
	RectFill(rp,4,81,w->Width-5,w->Height-3);	/* fyller nedre del av vinduet med farge 1 */
	DrawBorder(rp,&WarnBorder,WPOSX,WPOSY-3); 	/* tegn rammen til info tekst */
	RefreshGadgets(&NyPostGad,w,NULL); 			/* oppdater gadgets som er tegnet over */
}

/************************************************************************/
/* denne rutinen slår av gadgets som ikke må brukes */

static void turn_off_gadgets()
{
	register struct Gadget *p;
	register int x;
	
	p = &NyPostGad; /* peker til første gadget som skal slåes av */

	for (x=0;x<ONOFFGADS;x++,p=p->NextGadget)
		OffGadget(p,w,NULL);	/* slår av gadget p */

/* her gjøres den første string gadgeten aktiv og markøren komme fram */

	NavnGad.Flags ^=SELECTED;
	ActivateGadget(&NavnGad,w,NULL);
}

/************************************************************************/
/* denne rutinen slår på alle gadgets som ble slått av */

static int turn_on_gadgets()
{
	register struct Gadget *p;
	register int x;
	
	p = &NyPostGad;			/* første gadget som skal slåes på */
	for (x=0;x<ONOFFGADS;x++,p=p->NextGadget)
		OnGadget(p,w,NULL); /* slå på gadget p*/

	draw_display(); /* oppdater vinduet og tegn alle gadgets på nytt */
	return(0);		/* setter tilstand tilbake til 0 */
}

/************************************************************************/
/* Her overføres data i streng gadget bufferen til Liste-structuren post */

static void legg_inn_data(post)
register struct Liste *post;
{
/* her kopierer vi det som ligger i streng gadgetene inn i selve posten */

	if (post) {
		strcpy(post->Navn,NavnGadBuff);
		strcpy(post->Adresse,AdresseGadBuff);
		strcpy(post->PostSted,PostStedGadBuff);
		strcpy(post->Telefon,TelefonGadBuff);
		strcpy(post->PostNr,PostNrGadBuff);
	}
}

/************************************************************************/
/*
  Denne rutine finner den posten vi leter etter,
  eller den som ligger nærmest
*/
static struct Liste *finn_post(lhd)
struct Liste *lhd;
{
	register struct Liste *cr;

	cr = lhd;
	while (mystrcmp(cr->Navn,NavnGadBuff) < 0) {
			if (cr->neste == NULL)
				break;
			else
				cr = cr->neste;
	}

	return(cr); /* returner funnet post */
}

/************************************************************************/
/* denne rutinen fjerner en post fra listen og frigjør minnet posten har */

struct Liste *fjern_post(hode,slutt,dn)
register struct Liste **hode,**slutt;
register struct Liste *dn;
{
	register struct Liste *dumm;

	if (dn == *hode) {
		if (dn->neste) {
			*hode = dn->neste;
			(*hode)->forrige = NULL;
		}
		else
			*hode = NULL;

		FreeMem(dn,LLENGDE);
		return(*hode);
	}
	else if (dn == *slutt) {
		*slutt = dn->forrige;
		(*slutt)->neste = NULL;
		FreeMem(dn,LLENGDE);
		return(*slutt);
	}
	else {
		dn->forrige->neste = dn->neste;
		dn->neste->forrige = dn->forrige;

/* returner neste post i listen hvis mulig */
		if (dn->neste)
			dumm = dn->neste;
		else if (dn->forrige) /* prøv med forrige post */
			dumm = dn->forrige;
		else	/* listen ble tom */
			dumm = NULL;

		FreeMem(dn,LLENGDE);
		return(dumm);
	}
}

/************************************************************************/
/* denne rutinen kopierer data fra en post over til string gadget bufferene
   slik at dataene kan vises, hvis den får en NULL peker vil den 
   slette alt som står i string gadgetene */

static void vis_nypost(pst)
register struct Liste *pst;
{
/* her kopieres det som ligger i en post over i streng gadget bufferen */
	if (pst) {
		strcpy(NavnGadBuff,pst->Navn);
		strcpy(AdresseGadBuff,pst->Adresse);
		strcpy(PostStedGadBuff,pst->PostSted);
		strcpy(TelefonGadBuff,pst->Telefon);
		strcpy(PostNrGadBuff,pst->PostNr);
	}
/* hvis vi ikke gir en peker til en post blir streng gadgetene tømt */
	else {
		NavnGadBuff[0]     = '\0';
		AdresseGadBuff[0]  = '\0';
		PostStedGadBuff[0] = '\0';
		TelefonGadBuff[0]  = '\0';
		PostNrGadBuff[0]   = '\0';
	}	
	RefreshGadgets(&NavnGad,w,NULL); /* oppdater gadgetene med nye verdier */
}

/************************************************************************/
/* denne rutinen flytter en post ut av listen mens den redigeres */

static void plukk_ut_post(hode,slutt,sp)
register struct Liste **hode,**slutt;
register struct Liste *sp;
{
	if (sp == *hode) {
		if((*hode)->neste){
			*hode = sp->neste;
			(*hode)->forrige = NULL;
		}
		else {
			*hode = NULL;
			*slutt = NULL;
		}
	}
	else if (sp == *slutt) {
		*slutt = sp->forrige;
		(*slutt)->neste = NULL;
	}
	else {
		sp->forrige->neste = sp->neste;
		sp->neste->forrige = sp->forrige;
	}

	sp->forrige = NULL;
	sp->neste = NULL;
}

/************************************************************************/
/* sett av minne til ny post, les inn data og sett inn posten i listen */

static struct Liste *ny_post(hode,slutt,de)
struct Liste **hode,**slutt,*de;
{
	register struct Liste *p;

	p = (struct Liste *)AllocMem(LLENGDE,MEMF_CLEAR);

	if (p) {
		legg_inn_data(p); /* putt det som ligger i streng gadgeten inn i posten */
		sett_inn_post(hode,slutt,p); /* sett posten inn i listen */
	}
	else { /* for list minne til ny post */
		DisplayBeep(NULL); /* blinker alle skjermer en gang */
		show_warn("For Lite Minne for ny post!");
	}

	if (p) /* returner ny post hvis ledig minne */
		return(p);
	else /* ellrs returneres gammle "denne" */
		return(de);
}

/************************************************************************/
/* denne rutinen kontrolerer det som skjer på menyen */

static int getmenu() 
{
	switch(ITEMNUM(code)){ 	/* ITEMNUM er en makro som henter ut en verdi */
							/* som viser hva som ble valgt på menyen      */
							/* Vi har også en makro som henter MENUNUM(), */
							/* den returnerer hvilken meny vi har valgt.  */
							/* Vi har bare en meny så den trenger vi ikke.*/
		case 0: /* hent poster */
			if (lhode) {

/*
 AutoRequest setter opp en automatisk "requester" og returener 
 TRUE eller FALSE. Vi tilater to muligheter for å hente nye poster:
	 TRUE = slette alle poster og leser inn ny liste
	 FALSE = legg nye poster til de vi allerede har
*/

				if (AutoRequest(NULL,&btekst,&postxt1,&negtxt1,NULL,NULL,270,65)) {
					slett_alt(lhode);
					lhode = lslutt = denne = NULL;
					vis_nypost(NULL);
				}
			}

			denne = hent_liste(&lhode,&lslutt);
			vis_nypost(denne);

			break;

		case 1: /* lagre poster */
			lagre_liste(lhode);
			break;

		case 2: /* avslutt */
/*
  hvis vi prøver å avslutte med navn i listen får vi en advarsel
  og mulighet til å komme tilbake uten å miste data
*/
			if (lhode)
				return(AutoRequest(NULL,&quit1,&fortsett,&avslutt,NULL,NULL,270,65));
			else
				return(0);
	}
	ticks = delay = selected = curr_gadget = 0;
	return(1);
}

/************************************************************************/
/* denne rutinen kontrolerer hovedvinduet */

static int event1()
{
	if (class != INTUITICKS)
		show_warn(0);	 /* sletter de som står i info feltet */

	switch (class) {
		case CLOSEWINDOW:  /* bruker prøver å lukke vinduet */
			if(lhode)
				return(AutoRequest(NULL,&quit1,&fortsett,&avslutt,NULL,NULL,270,65));
			else
				return(0);

		case MENUPICK: /* bruker har foretatt et menyvalg */
			return(getmenu());

		case INTUITICKS: /* disse kommer ca 10 ganger i sekundet nå vinduet er aktivt */

/* bare hvis en gadget er trykke ned går vi videre */

			if (selected) {

/* vi venter 2 ticks før vi går videre */

				if (delay > 2) {
					if (ticks == 1) {

/* vi spoler bare hvert andre tick */

						switch (curr_gadget) {
							case 12:
								goto nestepos; /* samme som NesteGad */
							case 13:
								goto forrigepos; /* samme som ForrigeGad */
							default:
								break;
						}
					}
					ticks ^= 1; /* dette gjør at ticks skifter mellom 1 og 0 */
				}
				else
					delay++; /* øk vente tick med 1 */
			}
			break;

		case GADGETUP:
/* setter INTUITICKS kontrolvariabler til 0 nå en gadget er sluppet */
			ticks = delay = selected = curr_gadget = 0;
			goto around; /* hopp over setting av INTUITICKS variabler */

		case GADGETDOWN:
			selected = 1; /* settes når en gadget er trykket ned */
			curr_gadget = MIAddress->GadgetID; /* lagrer ID'en til gadgeten */
around:
			switch (MIAddress->GadgetID) {
				case 1: 								/* navn */
					if (tilstand == 3)
						goto finnpostpos;

					AdresseGad.Flags ^= SELECTED;
					ActivateGadget(&AdresseGad,w,NULL);
					break;

				case 2: 								/* adresse */
					PostNrGad.Flags ^= SELECTED;
					ActivateGadget(&PostNrGad,w,NULL);
					break;

				case 3: 								/* post nr */
					PostStedGad.Flags ^= SELECTED;
					ActivateGadget(&PostStedGad,w,NULL);
					break;

				case 4: 								/* post sted */
					TelefonGad.Flags ^= SELECTED;
					ActivateGadget(&TelefonGad,w,NULL);
					break;

				case 5: 								/* telefon */
/* 
  Her brukers tilstander for å vite hva som skal gjøres når vi 
  trykker return i Telefon gadgeten.
*/
					switch(tilstand){
						case 0: /* Ingen tilstand */
							show_warn("Velg Ny/Endre/Slett/Finn - post først");
							break;

						case 1: 						/* ny post */

/* Sett av minne for ny post og overfør data til post */

							denne = ny_post(&lhode,&lslutt,denne);

/* slå på alle gadgets og set tilstand tilbake til 0 */

							tilstand = turn_on_gadgets();
							break;

						case 2: 						/* endre post */ 
							plukk_ut_post(&lhode,&lslutt,denne); 	/* ta posten "denne" ut av listen */
							legg_inn_data(denne); 					/* legg inn nye data */
							sett_inn_post(&lhode,&lslutt,denne); 	/* sett inn posten igjen */
							tilstand = turn_on_gadgets(); 			/* tilstand tilbake til 0 og slå på gadgets */
							show_warn("Post endret!");
							break;

						case 3: 						/* finn post */
finnpostpos:
							denne = finn_post(lhode); /* hent navn og finn nærmeste post */
							tilstand = turn_on_gadgets();
							vis_nypost(denne);	/* skriv innholdet i en post i streng gadgetene */
							show_warn("Nærmeste post funnet!");
							break;
					}
					break;

				case 6: 								/* ny post */
					vis_nypost(NULL); /* tøm streng gadgetene */
					turn_off_gadgets(); /* slå av gadgetene som ikke må brukes */
					tilstand = 1; /* tilstand 1 er ny post */
					show_warn("Skriv inn dataene!"); /* si fra hva som skal gjøres */
					break;

				case 7: 								/* endre post */
					if (lhode == NULL || denne == NULL)
						break;

					turn_off_gadgets();
					tilstand = 2; /* tilstand 2 er endre post */
					show_warn("Endre data!");
					break;

				case 8: 								/* slett post */
					if (lhode == NULL)
						break;

/* her slettes posten "denne " uten å spørre så vær forsiktig med denne */

					denne = fjern_post(&lhode,&lslutt,denne);
					vis_nypost(denne); /* viser neste eller forrige post */
					show_warn("Post fjernet!");
					break;

				case 9: 								/* finn post */
					vis_nypost(NULL);

					if(lhode == NULL)
						break;

					turn_off_gadgets();
					tilstand = 3; /* tilstand 3 er finn post */
					break;

/* 				case 10: -> se lesson 6 */

				case 11:								/* slett alle */
					if (lhode) {
						if (AutoRequest(NULL,&atekst,&postxt,&negtxt,NULL,NULL,250,65)) {
							slett_alt(lhode);
							lhode = lslutt = denne = NULL;
							vis_nypost(NULL);
						}
					}

					break;

				case 12: 								/* neste post */
					if (class == GADGETUP) /* hvis gadgeten ble sluppet gjør vi ikke noe */
						break;
/*
  Vi sjekker om det er flere postern i listen, hvis det er flere
  så setter vi "denne" til å peke på neste post og viser den, 
  hvis ikke blinker vi skjermen
*/
nestepos:
					if (lhode == NULL || denne->neste == NULL)
						DisplayBeep(NULL);
					else
						denne = denne->neste;

					vis_nypost(denne);
					break;

				case 13: 								/* forrige post */
					if (class == GADGETUP) /* hvis sluppet, ignorer *7
						break;
/*
  Sjekk om dette er første post, hvis det ikke er første post
  settes "denne" til å peke på forrige i listen og vi viser den,
  hvis dette er første post eller listen er tom blinker vi med skjermen
*/
forrigepos:
					if (lhode == NULL || denne->forrige == NULL)
						DisplayBeep(NULL);
					else
						denne = denne->forrige;

					vis_nypost(denne);
					break;					
			}
			break;
	}
	return(1);
}

/************************************************************************/
/* her starter hovedprogrammet som åpner hovedvinduet
   og venter på at du skal gjøre noe */

void _main()
{

	lhode = lslutt = NULL;
	denne = NULL;
	ticks = delay = 0;

	if (!open_libs()) /* åpner alle libraries */
		goto avslutt;

	if (!open_display()) /* åpner vinduet */
		goto avslutt;

	draw_display(); /* tegner innholdet i vinduet */

	FOREVER { /* her en "evig" loop som programmet går i nå du ikke gjør noe */
/*
  Wait instruksjonen gjør at programmet ikke tar prosessortid når ingen
  gadgets er trykker
*/
		wakeup = Wait(1 << w->UserPort->mp_SigBit); 

/* her leser vi av hva som er hente */

		while (mesg = (struct IntuiMessage *)GetMsg(w->UserPort)) { /* få beskjeden */
			class = mesg->Class; 						/* type beskjed (IDCMP) */
			code = mesg->Code;  						/* sekunder type */
			MIAddress = (struct Gadget *)mesg->IAddress;/* hvilken gadget det ble trykket på */
			ReplyMsg((struct Message *)mesg); 			/* gi beskjeden tilbake */
			if (!event1()) 								/* finn ut hva som hente */
				goto avslutt;
		}
	}

avslutt:
	cleanup(); /* lukk vindu og libraries */
}
