/*
 *  FoodConv.c 
 *
 *  FoodConv - liquid measure converter.
 *
 *  14-Apr-89
 *  Jeff Kunzelman & Joanne Lee
 *
 *  Written because we were tired of converting 6 person recipies 
 *  to 2 person recipies
 *
 *  Compiled with Lattice C 5.0 and blink 5.0.
 *
 *  lc -Lm -cf FoodCalc
 *  (edit result FoodCalc.lnk file to include 'DEFINE __main=__tinymain'
 *   this will remove the extra window when running from the workbench.)
 *  Then relink with blink.
 *
 *
 *  
 *  Converts Cup, ounce, teaspoon, tablespoon liquid units to 
 *  cups, ounces, teaspoon and tablespoon after multipling by
 *  a conversion factor.
 *
 *  All units are specified in fractional form (not decimal) which
 *  makes things very handy since that's the way recipies are written.
 *
 *  To operate enter the conversion factor (eg. 3/4 then CNV).  Then
 *  enter the measure to convert (eg. 1-3/4 TBL) the results in all
 *  units will be displayed next to the associated units buttons.
 *  If the measure is to small, the field will be blanked.
 *
 *  Operates completely from either keyboard or mouse.  
 */

#include <hardware/intbits.h>
#include <intuition/intuition.h>

#include <proto/all.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "general.h"

#define	NUM_TYPE	(0)
#define CONV_TYPE	(1 << 8)
#define CNTRL_TYPE	(2 << 8)

#define TYPE_MASK	0xff00
#define VAL_MASK	0x00ff

enum {
	KEY0_ID = NUM_TYPE, KEY1_ID, KEY2_ID, KEY3_ID,
	KEY4_ID, KEY5_ID, KEY6_ID, KEY7_ID,
	KEY8_ID, KEY9_ID, KEYSLASH_ID, KEYSPACE_ID
};

enum {
	KEYCLR_ID = CNTRL_TYPE, KEYCNV_ID
};

enum {
	KEYOZ_ID = CONV_TYPE, KEYCUP_ID, KEYTSP_ID, KEYTBL_ID
};

/* 
 *  Digit modes
 */
enum {
	IDLE, GET_DIGIT
};

#include "keylayout.h"

#define MAX_DISPLAY	128
#define MAX_DIGITS  10
#define DISPLAY_W	16

#define CHAR_WIDTH		8	

#define DISPLAY_X		34
#define DISPLAY_Y		34
#define DISPLAY_UNIT_X	(DISPLAY_X + 10 * CHAR_WIDTH)

#define RESULTS_X		200
#define RESULTS_Y		68
#define RESULTS_Y_H		11

#define FACTOR_X		200
#define FACTOR_Y		DISPLAY_Y

#define OZ_FACTOR		1.0
#define	CUP_FACTOR		8.0
#define TBL_FACTOR		(OZ_FACTOR / 2.0)
#define TSP_FACTOR		(TBL_FACTOR / 3.0)

#define BG_PEN		1
#define NORM_PEN	2

#define ERROR		1

#define	DLIST_END	-1

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase 		*GfxBase;

struct Window   *w;
struct RastPort *rp;

double			cFactor = 1;
double			value;

int				mode = IDLE;

char			mainDisplay[MAX_DISPLAY + 1];

short 	cupDList[] 	= { 2, 3, 4, 8, DLIST_END};
short	ozDList[]	= { 2, 3, 4, 8, DLIST_END};
short	tspDList[]	= { 2, 4, 8, 16, DLIST_END};
short	tblDList[]	= { 2, 3, 4, DLIST_END};
short	facDList[]	= { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16, DLIST_END};


int
exitTrap(int exitValue)
{
	if (!IS_NIL(IntuitionBase))
		CloseLibrary(IntuitionBase);

	if (!IS_NIL(GfxBase))
		CloseLibrary(GfxBase);

	return exitValue;
}

void
beep(int error)
{
	DisplayBeep(w->WScreen);
}

void
writeDisplay(char *text)
{
	char	buf[MAX_DISPLAY + 1];
	int		length = strlen(text);

	strcpy(buf, text);
	for (length = DISPLAY_W - length; length; length--)
		strcat(buf, " ");
	Move(rp, DISPLAY_X, DISPLAY_Y);
	Text(rp, buf, strlen(buf));
}

void
clearDisplay(void)
{
	mainDisplay[0] = EOS;
	writeDisplay(mainDisplay);
}

void
appendDisplayChar(char newChar)
{
	int		length;

	length = strlen(mainDisplay);
	mainDisplay[length] = newChar;
	mainDisplay[length + 1] = EOS;
	writeDisplay(mainDisplay);
}


void
addDigit(ushort gadID)
{
	if (mode == IDLE)
		clearDisplay();

	mode = GET_DIGIT;

	switch (gadID) {
		case KEYSPACE_ID:
			appendDisplayChar('-');
			break;

		case KEYSLASH_ID:
			appendDisplayChar('/');
			break;

		default:
			gadID &= VAL_MASK;
			if (strlen(mainDisplay) >= MAX_DIGITS) {
				beep(ERROR);
				break;
			}
			appendDisplayChar((char)(gadID + '0'));
	}
}

void
writeText(int x, int y, char *string)
{
	Move(rp, x, y);
	Text(rp, string, strlen(string));
}

void 
clearResults(void)
{
	char	*blanks = "        ";

	writeText(RESULTS_X, RESULTS_Y, blanks); 
	writeText(RESULTS_X, RESULTS_Y + RESULTS_Y_H, blanks);
	writeText(RESULTS_X, RESULTS_Y + RESULTS_Y_H * 2, blanks); 
	writeText(RESULTS_X, RESULTS_Y + RESULTS_Y_H * 3, blanks);
}

int
translate(char *buf, int *whole, int *numerator, int *denominator)
{
	int		retVal = SUCCESS;

	if (sscanf(buf, "%d-%d/%d", whole, numerator, denominator) == 3) {
	} else if (sscanf(buf, "%d/%d", numerator, denominator) == 2) {
		*whole = 0;
	} else if (sscanf(buf, "%d", whole) == 1) {
		*numerator = 0;
		*denominator = 1;
	} else {
		beep(ERROR);
		retVal = FAIL;
	}

	return retVal;
}

void
writeFrac(int x, int y, double value, short *denList)
{
	char	 buf[MAX_DISPLAY + 1];
	short	*curDen;
	short	 curNom;
	double	 frac 	 	= value - (long)value;
	double	 bestNom 	= 0;
	double	 bestDen 	= 1;
	double	 bestDelta	= frac;
	int		 i;

	/*
	 *  For every possible nominator/denominator combination
	 *  search for the closest to the orginal fraction.
	 */
	for (curDen = denList; *curDen != DLIST_END; curDen++) {
		for (curNom = 1; curNom < *curDen; curNom++) {
			if (bestDelta >  abs(frac - ((1.0 * curNom) / *curDen))) {
				bestNom = curNom;
				bestDen = *curDen;
				bestDelta = abs(frac - (bestNom / bestDen));
			}
		}
	}

	/*
	 *  If the closest fraction is worse then rounding up to the
	 *  next whole number, round up...
	 */
	if (bestDelta > 1 - frac) {
		bestNom = 0;
		value += 1;
	}

	/* 
	 *  Format output fraction.
	 */
	if ((int)(value - frac) == 0 && 
		(bestNom / bestDen > frac || (bestNom == 0 && frac != 0)))
		/*
		 *  The output is a fraction only, and is smaller than the
		 *  minimum fraction, supress the output.
		 */
		buf[0] = EOS;
	else if ((int)bestNom == 0) {
		/*
		 *  The fractional part is 0, just print whole number.
		 */
		sprintf(buf, "%d", (int)(value - frac));
	} else if ((int)(value - frac) == 0) {
		/* 
		 *  The whole number part is 0, just print the fraction.
		 */
		sprintf(buf, "%d/%d", (int)bestNom, (int)bestDen);
	} else {
		/*
		 *  Print the full whole # and fraction.
		 */
		sprintf(buf, "%d-%d/%d", 
				(int)(value - frac), (int)bestNom, (int)bestDen);
	}

	/*
	 *  Pad output string with blanks to clear old info.
	 */
	if (strlen(buf) > 8)
		/*
		 *  The output is to large to fit on the display
		 */
		strcpy(buf, "* Lots * ");
	else {
		/*
		 *  Pad output string with trailing blanks.
		 */
		for (i = strlen(buf); i < 8; i++)
			buf[i] = ' ';
		buf[i] = EOS;
	}

	Move(rp, x, y);
	Text(rp, buf, strlen(buf));
}


void
writeFactor(void)
{
	writeFrac(FACTOR_X, FACTOR_Y, cFactor, facDList);
}

void
doControl(ushort gadID)
{
	int		whole;
	int		numerator;
	int		denominator;

	switch (gadID) {
		case  KEYCLR_ID:
			mode = IDLE;
			clearDisplay();
			clearResults();
			break;

		case KEYCNV_ID:
			if (translate(mainDisplay, &whole, 
				&numerator, &denominator) != SUCCESS) {
				beep(ERROR);
				return;
			} else {
				mode = IDLE;
				cFactor = whole + (1.0 * numerator) / denominator;
				writeFactor();
				clearResults();
				clearDisplay();
			}
			break;
	}
}

void
writeResults(double value)
{
	value *= cFactor;

	writeFrac(RESULTS_X, RESULTS_Y, value / OZ_FACTOR, ozDList);
	writeFrac(RESULTS_X, RESULTS_Y+RESULTS_Y_H, value/TSP_FACTOR, tspDList);
	writeFrac(RESULTS_X, RESULTS_Y+RESULTS_Y_H*2, value/TBL_FACTOR,tblDList);
	writeFrac(RESULTS_X, RESULTS_Y+RESULTS_Y_H*3, value/CUP_FACTOR,cupDList);
}

void
doConvert(ushort gadID)
{
	double	factor;
	char   *unit;
	int		whole;
	int		numerator;
	int		denominator;

	if (translate(mainDisplay, &whole, &numerator, &denominator) != SUCCESS) {
		beep(ERROR);
		clearResults();
		return;
	}

	switch (gadID) {
		case KEYOZ_ID:
			factor = OZ_FACTOR;
			unit   = "Oz  ";
			break;

		case KEYCUP_ID:
			factor = CUP_FACTOR;
			unit   = "Cups";
			break;

		case KEYTSP_ID:
			factor = TSP_FACTOR;
			unit   = "Tsps";
			break;

		case KEYTBL_ID:
			factor = TBL_FACTOR;
			unit   = "Tbls";
			break;
	}
	value = factor * (whole + (1.0 * numerator) / denominator);
	mode = IDLE;
	Move(rp, DISPLAY_UNIT_X, DISPLAY_Y);
	Text(rp, unit, strlen(unit));
	writeResults(value);
}

void
doKey(short key)
{
	if (key >= '0' && key <= '9')
		addDigit((ushort)(NUM_TYPE + (key - '0')));
	else switch (key) {
		case ' ':
		case '-':
			addDigit(KEYSPACE_ID);
			break;

		case  '/':
			addDigit(KEYSLASH_ID);
			break;

		case 'o':
		case 'O':
			doConvert(KEYOZ_ID);
			break;

		case 't':
		case 'T':
			doConvert(KEYTSP_ID);
			break;

		case 'b':
		case 'B':
			doConvert(KEYTBL_ID);
			break;

		case 'c':
		case 'C':
			doConvert(KEYCUP_ID);
			break;

		case 'l':
		case 'L':
			doControl(KEYCLR_ID);
			break;

		case 'n':
		case 'N':
			doControl(KEYCNV_ID);
			break;

		default:
			beep(ERROR);
	}
}

void
main(int argc, char **argv)
{
	AR0 struct IntuiMessage *idcmpMsg;
	bool 					 pleaseQuit = FALSE;
	ushort				     gadID;

	/*
	 *  Setup exit() cleanup function.
	 */
	if (!onexit(exitTrap))
		exit(1);

	/*
	 *  Open needed libraries.
	 */
	IntuitionBase = OpenLibrary("intuition.library", 0);
	if (IS_NIL(IntuitionBase))
		exit(2);

	GfxBase = OpenLibrary("graphics.library", 0);
	if (IS_NIL(GfxBase))
		exit(3);

	/*
	 *  Setup calculator window.
	 */
	w = OpenWindow(&NewWindowStructure1);
	if (IS_NIL(w))
		exit(4);
	rp = w->RPort;
	SetAPen(rp, NORM_PEN);
	SetBPen(rp, BG_PEN);
	SetWindowTitles(w, -1, "Jo & Jeffs' Liquid Measure Converter");

	/*
	 *  Initialize starting state and display.
	 */
	doControl(KEYCLR_ID);
	writeFactor();

	/* 
	 *  Read IDCMP input and process requests.
	 */
	do {
		/*
		 *  Read input from IDCMP
		 */
		WaitPort(w->UserPort);

		while (idcmpMsg = (struct Intuimessage *)GetMsg(w->UserPort)) {
			switch (idcmpMsg->Class) {
				case CLOSEWINDOW:
					pleaseQuit = TRUE;
					break;

				case GADGETUP:
					gadID = ((struct Gadget *)(idcmpMsg->IAddress))->GadgetID;
					if ((gadID & TYPE_MASK) == NUM_TYPE) 
						addDigit(gadID);
					else if ((gadID & TYPE_MASK) == CNTRL_TYPE)
						doControl(gadID);
					else if ((gadID & TYPE_MASK) == CONV_TYPE)
						doConvert(gadID);
					break;

				case VANILLAKEY:
					doKey(idcmpMsg->Code);
					break;
			}
			ReplyMsg((struct Message *)idcmpMsg);
		}
	} while (!pleaseQuit);

	CloseWindow(w);

	exit(0);
}
