//----------------------------------------------------------------------------
// C main line
//----------------------------------------------------------------------------

#include <m8c.h>        // part specific constants and macros
#include <stdlib.h>
#include <flashblock.h>

#include "PSoCAPI.h"    // PSoC API definitions for all User Modules
#include "orcsplash.h"
#include "glcd.h" 
#include "ed_host_int.h"
#include "util.h"

#include "fnt46.h"
#include "fnt58.h"
#include "fnt68.h"
#include "fnt88.h"

#include "error.h"
#include "config.h"

#define BATTERYUPDATEINTERVAL 256
#define INPUTCMDMAXLEN  	  72    // long enough for a 64 byte raw write
#define PORT0PULLUPS 	      0xe0
#define RESEND_INTERVAL       128   // 4Hz. We send this often, if there's no reason to.
#define CONFIG_BUTTON_THRESH  1024  // 2 seconds
#define BATVOLTAGE_X 96

// how far from center do we need to be in order to pay any attention
// to the joystick?
#define JOY_THRESH_MIN (0x18)

// how much must the joystick accumulate in order for us to call it an 
// autorepreat?
#define JOY_THRESH_AUTOREPEAT (18*128*8)
#define JOY_THRESH_PRESS (2*128*8)

////////////// GLOBALS

char		  inputCmd[INPUTCMDMAXLEN];
unsigned char inputCmdChk=0;
unsigned char inputLen=0;

int 		  joyx, joyy, batteryvoltage;

int 		  batteryUpdateCount;

unsigned char currentMode=MODE_PLOT;

unsigned char analogcount=0;
unsigned char analogPort=0;

unsigned char buttons;

unsigned char needUpdate=0;
unsigned char resendtimer=RESEND_INTERVAL;

long 		  joyxacc=0, joyyacc=0;
unsigned char joymove=0;
unsigned char moveup=0, movedown=0, moveright=0, moveleft=0;
unsigned int  buttonDownCount=0;

int 		  debounceDelay;

unsigned char lastSleepCounter;

unsigned char badchars=0;

unsigned char batterystate=255;

////////////// EXTERNS
extern params_t 	  params;
extern unsigned char  sleepcounter;

///////////// PROTOTYPES

unsigned char convertJoyX(void);
unsigned char convertJoyY(void);
void ProcessInputChar(char b);
void handlePacket(void);
void sendAck(unsigned char flags, unsigned char error);
unsigned char chksend(unsigned char chk, unsigned char c);
void UpdateAnalog(void);
void UpdateButtons(void);
void SendPacket(unsigned char flags);
void doSleepInterval(void);
void DrawBatteryVoltage(unsigned char x, unsigned char y);

extern void calibrate(void);

///////////// MAIN

void main()
{
	sleepcounter=0;
	lastSleepCounter=sleepcounter;
 	INT_MSK0|=0x40;
	
	PRT0DR=PORT0PULLUPS;

	M8C_EnableGInt;
	
	ED_HOST_init();
	UART_HOST_IntCntl(UART_HOST_ENABLE_RX_INT | UART_HOST_ENABLE_TX_INT);
  	UART_HOST_Start(UART_HOST_PARITY_NONE);
  
 	AMUX4_1_Start();
 	RefMux_1_Start(RefMux_1_HIGHPOWER);

	DelSig_1_Start(DelSig_1_HIGHPOWER);
	DelSig_1_StartAD();
	
	PWM16_LOWBAT_Start();
	
	GLCD_Init();	
	GLCD_DrawImage(orcsplash);
	
	GLCD_ConsoleTop=1;
	GLCD_ConsoleHeight=7;
	GLCD_ConsoleLeft=0;
	GLCD_ConsoleWidth=32;
	GLCD_ConsoleY=5; //second to bottom most line
	
	loadPrefs();

	batteryUpdateCount=128; // give us a little time to read the A/Ds before drawing the voltage.
	
	while(1)
	 {
	 	while (ED_HOST_isData())
	 	{
	 		ProcessInputChar(ED_HOST_getData());
		}

		UpdateAnalog();
		
		UpdateButtons();

		if (debounceDelay>0)
			debounceDelay--;

		if ((needUpdate || resendtimer==0) && debounceDelay==0)
		{
			resendtimer=RESEND_INTERVAL;
			needUpdate=0;
			SendPacket(0x80);
			// this delay effects a ~9ms max update rate
			debounceDelay=700;
		}

		if (lastSleepCounter!=sleepcounter)
			doSleepInterval();

		if (buttonDownCount>=CONFIG_BUTTON_THRESH)
		{
			calibrate();
			buttonDownCount=0;
		}
		
	}
}

unsigned char convertJoyX()
{
	int v;
	
	if (joyx>params.joycx)
		v=128+(127L*(joyx-params.joycx))/(params.joyxmax-params.joycx);
	else
		v=(128L*(joyx-params.joyxmin))/(params.joycx-params.joyxmin);

	if (v<0)
		v=0;
	if (v>255)
		v=255;
		
	return v;
}

unsigned char convertJoyY()
{
	int v;

	if (joyy>params.joycy)
		v=128+(127L*(joyy-params.joycy))/(params.joyymax-params.joycy);
	else
		v=(128L*(joyy-params.joyymin))/(params.joycy-params.joyymin);

	if (v<0)
		v=0;
	if (v>255)
		v=255;
		
	return v;
}


void ProcessInputChar(char b)
{
	// wait for packet header
	if (inputLen==0 && b!=237)
	{
		badchars++;
		return;
	}
	
	inputCmd[inputLen++]=b;

	if (inputLen<2 || inputLen<inputCmd[1])
		inputCmdChk=(inputCmdChk<<1) + b + (inputCmdChk&0x80 ? 1 : 0);
			
	if (inputLen<2)
		return;
	
	// reject long packets
	if (inputCmd[1]>=INPUTCMDMAXLEN)
		{
		inputLen=0;
		inputCmdChk=0;
		return;
		}
		
	// wait until we have the whole packet
	if (inputLen<inputCmd[1])
		return;
	
	if (inputCmdChk==inputCmd[inputLen-1])
		{
		handlePacket();
		}
	else
		badchars+=inputCmd[1];
		
	inputLen=0;
	inputCmdChk=0;
}

#define BASEOFF 3
#define FLAGSOFF 2

void handlePacket(void)
{
	unsigned char x,y,x2,y2; 
	unsigned char pos;
	unsigned char len=inputCmd[1]-4; // length of actual command
	
	if (inputCmd[BASEOFF]=='*' && len==1)
		{
		SendPacket(inputCmd[FLAGSOFF]);
		return;
		}
		
	// HOME CURSOR:  H
	if (inputCmd[BASEOFF]=='H' && len==1)
		{
		GLCD_ConsoleHome();
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		batteryUpdateCount=0;
		return;
		}

	// GOTO CURSOR:  Gxy
	if (inputCmd[BASEOFF]=='G' && len==3)
		{
		x=inputCmd[BASEOFF+1];
		y=inputCmd[BASEOFF+2];
		if (x>=GLCD_ConsoleWidth)
			x=GLCD_ConsoleWidth-1;
		if (y>=GLCD_ConsoleHeight)
			y=GLCD_ConsoleHeight-1;
		GLCD_ConsoleX=x;
		GLCD_ConsoleY=y;
		GLCD_ConsoleNewlinePending=0;
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}

	// PLOT POINT:   Pxy  (or repeated xy) Pxyxyxyxyxy
	if (inputCmd[BASEOFF]=='P' && len>=3 && (len&1)==1)
		{
		pos=1;
		while (x2<len)
			{
			x=inputCmd[BASEOFF+pos];
			y=inputCmd[BASEOFF+pos+1];
			if (x>127)
				x=127;
			if (y>63)
				y=63;
			GLCD_Plot(x,y,currentMode);
			pos+=2;
			}
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}
	
	// SET PLOT MODE:   Mx
	if (inputCmd[BASEOFF]=='M' && len==2)
		{
		currentMode=inputCmd[BASEOFF+1];
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}
	
	// DRAW LINE:     Lxyxy (or repeated, Lxyxyxyxy)
	if (inputCmd[BASEOFF]=='L' && len>=5 && (len&3)==1)
		{
		pos=1;
		while (pos<len)
			{
			x=inputCmd[BASEOFF+pos];
			y=inputCmd[BASEOFF+pos+1];
			x2=inputCmd[BASEOFF+pos+2];
			y2=inputCmd[BASEOFF+pos+3];
			if (x>127)
				x=127;
			if (y>63)
				y=63;
			if (x2>127)
				x2=127;
			if (y2>63)
				y2=63;
			GLCD_DrawLine(x,y,x2,y2,currentMode);
			pos+=4;
			}
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}

	// CLEAR:        C
	if (inputCmd[BASEOFF]=='C' && len==1)
		{
		GLCD_Clear();
		GLCD_DrawCharXor=0x7f;
		GLCD_DrawStringC(0,0,"                                ",fnt46);
		GLCD_DrawCharXor=0;

		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		batteryUpdateCount=0;
		return;
		}

	// FILL:       Fxy
	// x in even columns, y in odd columns.
	if (inputCmd[BASEOFF]=='F' && len==3)
		{
		x=inputCmd[BASEOFF+1];
		y=inputCmd[BASEOFF+2];
		GLCD_Fill(x,y);
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		batteryUpdateCount=0;
		return;
		}
	
	// RAW Read:    rxyl (l=len)
	if (inputCmd[BASEOFF]=='r' && len>=3)
		{
		x=inputCmd[BASEOFF+1];
		y=inputCmd[BASEOFF+2];
		x2=inputCmd[BASEOFF+3];
		
		if (x>127)
			x=127;
		if (y>63)
			y=63;

		len=inputCmd[FLAGSOFF]; // remember the original flags
		
		GLCD_ReadRow(x,y,inputCmd, x2); // perform the read, reusing inputCmd buffer
		
		y2=0;
		y2=chksend(y2, 237);
		y2=chksend(y2, x2+5);
		y2=chksend(y2, len);
		y2=chksend(y2, ESUCCESS);
		for (pos=0;pos<x2;pos++)
			y2=chksend(y2, inputCmd[pos]);
		y2=chksend(y2, y2);
		return;
		}

	// RAW WRITE:    RxyDDD...DDD
	if (inputCmd[BASEOFF]=='R' && len>=3)
		{
		x=inputCmd[BASEOFF+1];
		y=inputCmd[BASEOFF+2];
		if (x>127)
			x=127;
		if (y>63)
			y=63;

		// we should really ack AFTER doing the work,
		// but this should be reliable because of our
		// large input buffer.
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);

		GLCD_DrawRow(x,y,&inputCmd[BASEOFF+3], len-3);
		return;
		}
	
	// draw text to the console : WDDDD...
	if (inputCmd[BASEOFF]=='W' && len>1)
		{
		inputCmd[BASEOFF+len]=0; // null terminate (overwriting checksum)
		GLCD_ConsoleWriteString(&inputCmd[BASEOFF+1]);
		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}

	// draw text at top :  txDDDDD...
	if (inputCmd[BASEOFF]=='t' && len>=3)
		{
		x=inputCmd[BASEOFF+1];
		if (x>127)
			x=127;		

		inputCmd[BASEOFF+len]=0; // null terminate (overwriting checksum)
		GLCD_DrawCharXor=0x7f;
		GLCD_DrawString(x, 0, &inputCmd[BASEOFF+2], fnt46);
		GLCD_DrawCharXor=0x00;

		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;		
		}				

	// draw text at random position: TxyfDDDD...
	if (inputCmd[BASEOFF]=='T' && len>=4)
		{
		x=inputCmd[BASEOFF+1];
		y=inputCmd[BASEOFF+2];
		if (x>127)
			x=127;
		if (y>63)
			y=63;
		if (inputCmd[BASEOFF+3]>='a') // reverse video
			GLCD_DrawCharXor=255;

		x2=inputCmd[BASEOFF+3];
		if (x2>='a') // crude toupper()
			x2-=32;
		
		inputCmd[BASEOFF+len]=0; // null terminate (overwriting checksum)
		
		if (x2=='A')
			GLCD_DrawString(x, y, &inputCmd[BASEOFF+4], fnt46);
		else if (x2=='B')
			GLCD_DrawString(x, y, &inputCmd[BASEOFF+4], fnt58);
		else if (x2=='C')
			GLCD_DrawString(x, y, &inputCmd[BASEOFF+4], fnt68);
		else if (x2=='D')
			GLCD_DrawString(x, y, &inputCmd[BASEOFF+4], fnt88);
		
		GLCD_DrawCharXor=0;

		sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		return;
		}

	// SET CONSOLE SIZE: Sthlw
	if (inputCmd[BASEOFF]=='S' && len==5) // set the size of the console
		{
		 GLCD_ConsoleTop=inputCmd[BASEOFF+1];
 		 GLCD_ConsoleHeight=inputCmd[BASEOFF+2];
 		 GLCD_ConsoleLeft=inputCmd[BASEOFF+3];
 		 GLCD_ConsoleWidth=inputCmd[BASEOFF+4];
		
		 GLCD_ConsoleHome();
		 sendAck(inputCmd[FLAGSOFF], ESUCCESS);
		 return;
		}
	
	// dunno what this is. NACK it.
	sendAck(inputCmd[FLAGSOFF], EUNKNOWNCMD);
}

// note: convention that error==0 means no error
void sendAck(unsigned char flags, unsigned char error)
{
	unsigned char chk=0;
	
	chk = chksend(chk, 237);
	chk = chksend(chk, 5);
	chk = chksend(chk, flags);
	chk = chksend(chk, error);
	chk = chksend(chk, chk);

	inputLen=0;
}

void SendPacket(unsigned char flags)
{
	unsigned char chk=0;
	
	chk = chksend(chk, 237);
	chk = chksend(chk, 13);   // packet length
	chk = chksend(chk, flags); // host/pad comms, no transID
	chk = chksend(chk, '*');
	chk = chksend(chk, convertJoyX());
	chk = chksend(chk, convertJoyY());
	chk = chksend(chk, buttons);
	chk = chksend(chk, ((moveup&0xf)<<4) | (movedown&0xf));
	chk = chksend(chk, ((moveleft&0xf)<<4) | (moveright&0xf));
	chk = chksend(chk, batteryvoltage>>8);
	chk = chksend(chk, batteryvoltage&0xff);

	chk = chksend(chk, badchars);

	chk = chksend(chk, chk);	
}

unsigned char chksend(unsigned char chk, unsigned char c)
{
	ED_HOST_putData(c);
	return (chk<<1)+c+(chk&0x80 ? 1: 0);
}

void UpdateAnalog()
{
	int v;
	
	if (!DelSig_1_fIsDataAvailable())
		return;

	v=DelSig_1_iGetDataClearFlag()>>4;
	analogcount++;

	if (analogcount<6)
		return;
	analogcount=0;
	
	if (analogPort==0)
		{
		v=1024-v;
		if (abs(v-joyx)>=4)
			{
			joyx=v;
			needUpdate=1;
			}
		}
	else if (analogPort==2)
		{
		if (abs(v-joyy)>=4)
			{
			joyy=v;
			needUpdate=1;
			}
		}
	else if (analogPort==4)
		{
			// batteryvoltage is voltage*100, e.g. 1234 = 12.34V
			// but batsensedivide is scaled by an extra factor of 10
			batteryvoltage=((long) 1000L*v)/params.batsensedivide;
		}

	// rotate the port	
	analogPort=analogPort+2;
	if (analogPort>=8)
		analogPort=0;
	
	AMUX4_1_InputSelect(analogPort>>1);
}

void UpdateButtons(void)
{
	unsigned char b;
	
	// update button data.		
	b=((PRT0DR & PORT0PULLUPS)>>5)^7;
	if (b!=buttons)
		{
		buttons=b&7;
		needUpdate=1;
		}
}

void doSleepInterval()
{
	unsigned char joyx2, joyy2;

	if (batteryvoltage>=1200)
	{
		if (batterystate!=0)
		{
			batterystate=0;
			PWM16_LOWBAT_WritePeriod(32768);
			PWM16_LOWBAT_WritePulseWidth(0);
		}		
	} else if (batteryvoltage>=1125) {
		if (batterystate!=1)
		{
			batterystate=1;
			PWM16_LOWBAT_WritePeriod(32768);
			PWM16_LOWBAT_WritePulseWidth(8192);
		}
	} else if (batteryvoltage>=1050) {
		if (batterystate!=2)
		{
			batterystate=2;
			PWM16_LOWBAT_WritePeriod(16384);
			PWM16_LOWBAT_WritePulseWidth(8192);	
		}
	} else if (batterystate!=3) {
		batterystate=3;
		PWM16_LOWBAT_WritePeriod(8192);
		PWM16_LOWBAT_WritePulseWidth(4096);
	}
	
	
	batteryUpdateCount--;
	// we don't want to redraw the banner too often, because
	// it takes a long time!
	if (batteryUpdateCount<=0)
		{
		batteryUpdateCount=BATTERYUPDATEINTERVAL;
		DrawBatteryVoltage(BATVOLTAGE_X, 0);
		}
	
	resendtimer--;

	if (buttons&2)
		{
		if (buttonDownCount<65534)
			buttonDownCount++;
		}
	else
		buttonDownCount=0;
		
	lastSleepCounter=sleepcounter;
	joyx2=convertJoyX();
	joyy2=convertJoyY();

	if (joyx2>(128+JOY_THRESH_MIN) || joyx2<(128-JOY_THRESH_MIN))
		joyxacc+=joyx2-128;
	else
		{
		if (joyxacc>JOY_THRESH_PRESS)
			{
			moveright++;
			goto gotpress;
			}
		if (joyxacc<-JOY_THRESH_PRESS)
			{
			moveleft++;
			goto gotpress;
			}		
		}

	if (joyy2>(128+JOY_THRESH_MIN) || joyy2<(128-JOY_THRESH_MIN))
		joyyacc+=joyy2-128;
	else
		{
		if (joyyacc>JOY_THRESH_PRESS)
			{
			movedown++;
			goto gotpress;
			}
		if (joyyacc<-JOY_THRESH_PRESS)
			{
			moveup++;
			goto gotpress;
			}		
		}

	if (joyxacc>JOY_THRESH_AUTOREPEAT)
		{
		moveright++;
		goto gotautokey;
		}
	if (joyxacc<-JOY_THRESH_AUTOREPEAT)
		{
		moveleft++;
		goto gotautokey;
		}
	if (joyyacc>JOY_THRESH_AUTOREPEAT)
		{
		movedown++;
		goto gotautokey;
		}
	if (joyyacc<-JOY_THRESH_AUTOREPEAT)
		{
		moveup++;
		goto gotautokey;
		}
	return;
	
	gotautokey:
		joyxacc=joyxacc*2/3;
		joyyacc=joyyacc*2/3;
		needUpdate=1;
		return;

	gotpress:
		joyxacc=0;
		joyyacc=0;
		needUpdate=1;
		return;
	
}

void DrawBatteryVoltage(unsigned char x, unsigned char y)
{
	unsigned char s[6];
	
	itoa(s, batteryvoltage, 10);
	
	GLCD_DrawCharXor=0x7f;

	x+=GLCD_DrawChar(x, y, ' ', fnt46);
	
	if (batteryvoltage>=1000)
		{
		x+=GLCD_DrawChar(x, y, s[0], fnt46);
		x+=GLCD_DrawChar(x, y, s[1], fnt46);
		x+=GLCD_DrawChar(x, y, '.', fnt46);
		x+=GLCD_DrawChar(x, y, s[2], fnt46);
		x+=GLCD_DrawChar(x, y, s[3], fnt46);
		}
	else if (batteryvoltage>=100)
		{
		x+=GLCD_DrawChar(x, y, ' ', fnt46);
		x+=GLCD_DrawChar(x, y, s[0], fnt46);
		x+=GLCD_DrawChar(x, y, '.', fnt46);
		x+=GLCD_DrawChar(x, y, s[1], fnt46);
		x+=GLCD_DrawChar(x, y, s[2], fnt46);
		}
	else
		{
		x+=GLCD_DrawStringC(x, y, "??INV", fnt46);
		}
	x+=GLCD_DrawStringC(x, y, "V ", fnt46);		

	GLCD_DrawCharXor=0x00;
	
}
