#include <stdlib.h>

#include "GLCD.h"
#include "fnt58.h"
#include "fnt46.h"

#define DELAY asm("nop \n nop \n nop");

// Getting maximum performance out of graphics module requires 
// some careful hackery.
//
// C versions of all the calls are also available, but they 
// can be 2-4 times slower!
//
// Line functions use basic drawing algorithms and internal
// precision of 200th of a pixel in fixed-point format. With a 
// screen width of 128, this ensures that the maximum pixel
// error is less than 1 for a line of any slope.
//
// these functions are for internal use (and hence not included in
// header file).
void GLCD_WriteData(BYTE v, BYTE chipsel);
void GLCD_WriteDataBulk(BYTE v, BYTE chipsel);
void GLCD_WriteInstruction(BYTE v, BYTE chipsel);
void GLCD_SetupBulkWrite(void);

BYTE GLCD_ReadData(BYTE chipsel);
BYTE GLCD_ReadDataBulk(BYTE chipsel);
BYTE GLCD_ReadStatus(BYTE chipsel);
void GLCD_SetupBulkRead(void);

void GLCD_WaitUntilNotBusy(BYTE chipsel);

// internal but higher-level commands
void GLCD_DisplayOnOff(BYTE on, BYTE chipsel);
void GLCD_SetYAddress(BYTE v, BYTE chipsel);
void GLCD_SetXAddress(BYTE v, BYTE chipsel);
void GLCD_SetStartZAddress(BYTE v, BYTE chipsel);


BYTE GLCD_ConsoleX=0;
BYTE GLCD_ConsoleY=0;
BYTE GLCD_ConsoleTop=4;
BYTE GLCD_ConsoleHeight=4;
BYTE GLCD_ConsoleLeft=2;
BYTE GLCD_ConsoleWidth=25;

BYTE GLCD_ConsoleNewlinePending=0;

BYTE GLCD_DrawCharXor=0;

#pragma fastcall GLCD_WriteInstruction
#pragma fastcall GLCD_WriteData
#pragma fastcall GLCD_ReadData
#pragma fastcall GLCD_ReadStatus
#pragma fastcall GLCD_WaitUntilNotBusy


void GLCD_Init()
{
	DATABUS_READ;
	CONTROLS = CS1 | CS2| RST;
	
	GLCD_WaitUntilNotBusy(CHIPSEL1);
	GLCD_WaitUntilNotBusy(CHIPSEL2);
	
	GLCD_DisplayOnOff(1, CHIPSEL1);
	GLCD_DisplayOnOff(1, CHIPSEL2);
	GLCD_Clear();
}

/*
Reference C implementations:
(see optimized assembly versions)

void GLCD_WaitUntilNotBusy(BYTE chipsel)
{
	while (GLCD_ReadStatus(chipsel)&0x80);
}

void GLCD_WriteInstruction(BYTE v, BYTE chipsel)
{
	DATABUS_WRITE;
	DATABUS=v;
	
	CONTROLS = chipsel | RST;
	DELAY;

	CONTROLS = chipsel | ENABLE | RST;
	DELAY;
	CONTROLS = chipsel | RST;
	
	DATABUS_READ;
}


void GLCD_WriteData(BYTE v, BYTE chipsel)
{
	DATABUS_WRITE;
	DATABUS=v;

	CONTROLS = chipsel | DI | RST;
	DELAY;
	CONTROLS = chipsel | DI | ENABLE | RST;
	DELAY;
	CONTROLS = chipsel | DI | RST;
	
	DATABUS_READ;
	
}

BYTE GLCD_ReadData(BYTE chipsel)
{
	BYTE v;
	
	DATABUS_READ;
	
	CONTROLS = chipsel | DI | RW | RST;
	DELAY;
	CONTROLS = chipsel | DI | RW | ENABLE | RST;
	DELAY;
	v=DATABUS;
	CONTROLS = chipsel | DI | RW | RST;
	
	return v;	
}

BYTE GLCD_ReadStatus(BYTE chipsel)
{
	BYTE v;
	
	DATABUS_READ;
	
	CONTROLS = chipsel | RW | RST;
	DELAY;
	CONTROLS = chipsel | RW | ENABLE | RST;
	DELAY;
	v=DATABUS;
	CONTROLS = chipsel | RW | RST;
	
	return v;	
}
 */
 
void GLCD_DisplayOnOff(BYTE on, BYTE chipsel)
{
	GLCD_WriteInstruction(0x3e | on, chipsel);
}

#define GLCD_SetYAddress(v, chipsel) 		GLCD_WriteInstruction(0x40 | (v), (chipsel));
#define GLCD_SetXAddress(v, chipsel)        GLCD_WriteInstruction(0xb8 | (v), (chipsel));
#define GLCD_SetStartZAddress(v, chipsel)   GLCD_WriteInstruction(0xc0 | (v), (chipsel));

/*
void GLCD_SetYAddress(BYTE v, BYTE chipsel)
{
	GLCD_WriteInstruction(0x40 | v, chipsel);	
}

void GLCD_SetXAddress(BYTE v, BYTE chipsel)
{
	GLCD_WriteInstruction(0xb8 | v, chipsel);	
}
void GLCD_SetStartZAddress(BYTE v, BYTE chipsel)
{
	GLCD_WriteInstruction(0xc0 | v, chipsel);	
}
*/
void GLCD_Clear()
{
	GLCD_Fill(0,0);
	GLCD_ConsoleNewlinePending=0;
}
	
void GLCD_Fill(BYTE backgroundeven, BYTE backgroundodd)
{
	int xadr;
	int yadr;
	
	for (xadr=0;xadr<8;xadr++)
	{
		GLCD_SetXAddress(xadr, CHIPSEL1);
		GLCD_SetXAddress(xadr, CHIPSEL2);
		GLCD_SetYAddress(0, CHIPSEL1);
		GLCD_SetYAddress(0, CHIPSEL2);
		
		for (yadr=0;yadr<64;yadr+=2)
		{
			GLCD_WriteData(backgroundeven, CHIPSEL1);			
			GLCD_WriteData(backgroundodd, CHIPSEL1);			
		}
		
		for (yadr=64;yadr<128;yadr+=2)
		{
			GLCD_WriteData(backgroundeven, CHIPSEL2);			
			GLCD_WriteData(backgroundodd, CHIPSEL2);			
		}
	}
	GLCD_ConsoleNewlinePending=0;
}

void GLCD_DrawImage(const BYTE *i)
{
	int xadr;
	int yadr;
	int idx;

	idx=0;
	
	for (xadr=0;xadr<8;xadr++)
	{
		GLCD_SetXAddress(xadr, CHIPSEL1);
		GLCD_SetXAddress(xadr, CHIPSEL2);
		GLCD_SetYAddress(0, CHIPSEL1);
		GLCD_SetYAddress(0, CHIPSEL2);

		for (yadr=0;yadr<64;yadr++)
		{
			GLCD_WriteData(i[idx++], CHIPSEL1);			
		}
		
		for (yadr=64;yadr<128;yadr++)
		{
			GLCD_WriteData(i[idx++], CHIPSEL2);			
		}

	}
	
	GLCD_ConsoleNewlinePending=0;
}

void GLCD_Plot(BYTE x, BYTE y, BYTE mode)
{
	BYTE chipsel;
	BYTE b;
	BYTE d;
	BYTE adr;

	y=y&0x3f;
	x=x&0x7f;
		
	// which pixel to modify?
	d=y&7;
	if (d<4) // 0123
		{
		if (d<2)  // 01
			{
				if (d==0)
					d=1;
				else
					d=2;
			}
		else	// 23
			{
				if (d==2)
					d=4;
				else
					d=8; 
			}
		}
	else // 4567
		{
		if (d<6)  // 45
			{
				if (d==4)
					d=16;
				else
					d=32;
			}
		else	// 67
			{
				if (d==6)
					d=64;
				else
					d=128; 
			}
		
		}
	
	if (x>=64)
		{
		adr=x-64;
		chipsel=CHIPSEL2;
		}
	else
		{
		adr=x;
		chipsel=CHIPSEL1;
		}
			
	GLCD_SetYAddress(adr, chipsel);
	GLCD_SetXAddress(y/8, chipsel);
	GLCD_ReadData(chipsel); // dummy read.
	b=GLCD_ReadData(chipsel); // read the background
 
	if (mode==MODE_PLOT)
		b=b|d;
	else if (mode==MODE_XOR)
		b=b^d;
	else if (mode==MODE_ERASE)
		b=b&(~d);
	
	GLCD_SetYAddress(adr, chipsel);
	GLCD_WriteData(b, chipsel);
}

BYTE GLCD_DrawChar(BYTE x, BYTE y, BYTE c, const BYTE *font)
{
	int offset=4+(c&0x7f)*font[3];
	BYTE cnt=0;
	BYTE currentchip;

	// Convert given coordinates into LCD's coordinates:
	cnt=y;
	y=x;
	x=cnt/8;
	cnt=0;
	
	if (y<0)
		{
		GLCD_SetYAddress(0, CHIPSEL1);
		currentchip=CHIPSEL1;
		}
	else if (y<64)
		{
		GLCD_SetYAddress(y, CHIPSEL1);
		currentchip=CHIPSEL1;
		}
	else
		{
		GLCD_SetYAddress(y-64, CHIPSEL2);
		currentchip=CHIPSEL2;
		}
	
	GLCD_SetXAddress(x, currentchip);
	
	for (cnt=0; cnt< font[1];cnt++)
	{
		if (y>=0 && y<128)
			GLCD_WriteData(font[offset+cnt]^GLCD_DrawCharXor, currentchip);

		y++;
		if (y==64)
			{
			GLCD_SetYAddress(0, CHIPSEL2);
			currentchip=CHIPSEL2;
			GLCD_SetXAddress(x, currentchip);
			}
	}
	return font[1];
}

BYTE GLCD_DrawString(BYTE x, BYTE y, char *c, const BYTE *font)
{
	int idx=0;
	BYTE offset=0;
	
	while (c[idx]!=0)
	{
		offset+=GLCD_DrawChar(x+offset, y, c[idx], font);
		idx++;
	}
	
	return offset;
}

BYTE GLCD_DrawStringC(BYTE x, BYTE y, const char *c, const BYTE *font)
{
	int idx=0;
	BYTE offset=0;
	
	while (c[idx]!=0)
	{
		offset+=GLCD_DrawChar(x+offset, y, c[idx], font);
		idx++;
	}
	
	return offset;
}

void GLCD_ConsoleWriteString(char *c)
{
	int idx=0;
	
	while (c[idx]!=0)
	{
		GLCD_ConsoleWriteChar(c[idx]);
		idx++;
	}
}

void GLCD_ConsoleWriteStringC(const char *c)
{
	int idx=0;
	
	while (c[idx]!=0)
	{
		GLCD_ConsoleWriteChar(c[idx]);
		idx++;
	}
}

// we delay the printing/scrolling of newlines so we can get
// a maximum amount of display area out of the LCD. (If we
// didn't, then we "lose" a line due to always scrolling up
// after the bottom line.)
void GLCD_ConsoleWriteChar(char c)
{
	if (GLCD_ConsoleNewlinePending)
		{
		GLCD_ConsoleNewlinePending=0;
		
		GLCD_ConsoleX=0;
		GLCD_ConsoleY++;
		if (GLCD_ConsoleY>=GLCD_ConsoleHeight)
			{
			GLCD_ConsoleY=GLCD_ConsoleHeight-1;
			GLCD_ConsoleScroll();
			GLCD_ConsoleEraseLine(GLCD_ConsoleY);
			}
		}
		
	if (c==13) // \r
		return;
		
	if (c==10) // \n
		{
		GLCD_ConsoleNewlinePending=1;
		return;
		}
		
	if (c==9) // tab
		{
		GLCD_ConsoleX+=3;
		if (GLCD_ConsoleX>=GLCD_ConsoleWidth)
			{
			GLCD_ConsoleX=0;
			GLCD_ConsoleY++;
			if (GLCD_ConsoleY>=GLCD_ConsoleHeight)
				{
				GLCD_ConsoleY=GLCD_ConsoleHeight-1;
				GLCD_ConsoleScroll();
				GLCD_ConsoleEraseLine(GLCD_ConsoleY);
				}
			}
		return;
		}
	
	if (c==8) // backspace
		{
		if (GLCD_ConsoleX==0 && GLCD_ConsoleY==0)
			return;
		
		if (GLCD_ConsoleX>0)
			{
			GLCD_ConsoleX--;
			return;
			}
		
		GLCD_ConsoleY--;
		GLCD_ConsoleX=GLCD_ConsoleWidth-1;
		return;		
		}

	GLCD_DrawChar((GLCD_ConsoleX+GLCD_ConsoleLeft)*4, (GLCD_ConsoleY+GLCD_ConsoleTop)*8, c, fnt46);
	GLCD_ConsoleX++;
	if (GLCD_ConsoleX>=GLCD_ConsoleWidth)
		{
		GLCD_ConsoleX=0;
		GLCD_ConsoleY++;
		if (GLCD_ConsoleY>=GLCD_ConsoleHeight)
			{
			GLCD_ConsoleY=GLCD_ConsoleHeight-1;
			GLCD_ConsoleScroll();
			GLCD_ConsoleEraseLine(GLCD_ConsoleY);
			}
		}
}

void GLCD_DrawLine( char x0, char y0, 
					char x1, char y1, unsigned char m)
{
	int step, stepacc;
	BYTE t;
		
	// ensure that (x0,y0) is always left of (x1,y1)
	if (x0>x1)
		{
		t=x0;
		x0=x1;
		x1=t;
		t=y0;
		y0=y1;
		y1=t;
		}
		
	// are we moving faster in the X or Y direction?	
	if (abs(x1-x0)>abs(y1-y0))
		{
		// we're moving in the X direction faster, so step in X
		// and compute y.
		stepacc=y0*128;
		step=(y1-y0)*128/(x1-x0);
		
		while (x0!=x1)
			{
				GLCD_Plot(x0,stepacc/128, m);
				stepacc+=step;
				if (x0<x1)
					x0++;
				else
					x0--;
			}
		}
	else
		{
		// we're moving faster in the Y direction, so step in Y
		stepacc=x0*128;
		step=(x1-x0)*128/(y1-y0);
		
		while (y0!=y1)
			{
				GLCD_Plot((stepacc+64)/128, y0, m);
				stepacc+=step;
				if (y0<y1)
					y0++;
				else
					y0--;
			}
		}
}

void GLCD_ConsoleHome()
{
	BYTE line;
	
	for (line=0;line<GLCD_ConsoleHeight;line++)
	{
		GLCD_ConsoleEraseLine(line);
	}
	GLCD_ConsoleX=0;
	GLCD_ConsoleY=0;
	GLCD_ConsoleNewlinePending=0;
}

void GLCD_ConsoleEraseLine(BYTE line)
{
	BYTE x0, x1;
	BYTE chipsel;
			
	x0=GLCD_ConsoleLeft*5;
	x1=(GLCD_ConsoleLeft+GLCD_ConsoleWidth)*5;
	
	line=(line+GLCD_ConsoleTop);

	GLCD_SetXAddress(line, CHIPSEL1);
	GLCD_SetXAddress(line, CHIPSEL2);
	
	if (x0<64)
	{
		GLCD_SetYAddress(x0, CHIPSEL1);
		chipsel=CHIPSEL1;
	}
	else
	{
		GLCD_SetYAddress(x0-64,CHIPSEL2);
		chipsel=CHIPSEL2;
	}
			
	while (x0<x1)
	{
		GLCD_WriteData(0, chipsel);			
	
		x0++;
		if (x0==64)
		{
			GLCD_SetYAddress(0, CHIPSEL2);
			chipsel=CHIPSEL2;
		}
	}
}

void GLCD_ConsoleScroll()
{
	BYTE line;
	BYTE i;
	BYTE b[8];
	BYTE ymin,ymax;
	BYTE bufsize;
	BYTE chipsel;
	BYTE y;
	
	ymin=GLCD_ConsoleLeft*5;
	ymax=(GLCD_ConsoleLeft+GLCD_ConsoleWidth)*5;
	
	for (line=GLCD_ConsoleTop;line<(GLCD_ConsoleTop+GLCD_ConsoleHeight);line++)
		{
				
		y=ymin;
		bufsize=8;
		while (y<ymax)
			{
				GLCD_SetXAddress(line+1, CHIPSEL1);
				GLCD_SetXAddress(line+1, CHIPSEL2);

				if ((y+bufsize)>=ymax)
					bufsize=ymax-y;
				if ((y+bufsize)>=64 && y<64)
					bufsize=64-y;
				
				if (y<64)
					{
					chipsel=CHIPSEL1;
					GLCD_SetYAddress(y, chipsel);
					}
				else
					{
					chipsel=CHIPSEL2;
					GLCD_SetYAddress(y-64, chipsel);
					}
				
				GLCD_ReadData(chipsel); // dummy read
				for (i=0;i<bufsize;i++)	
					b[i]=GLCD_ReadData(chipsel);
					
				GLCD_SetXAddress(line, CHIPSEL1);
				GLCD_SetXAddress(line, CHIPSEL2);

				if (y<64)
					{
					chipsel=CHIPSEL1;
					GLCD_SetYAddress(y, chipsel);
					}
				else
					{
					chipsel=CHIPSEL2;
					GLCD_SetYAddress(y-64, chipsel);
					}

				for (i=0;i<bufsize;i++)	
					GLCD_WriteData(b[i],chipsel);
					
				y+=bufsize;
			}
		}
}

void GLCD_DrawRow(BYTE x, BYTE y, BYTE *data, BYTE len)
{
	BYTE cnt=0;
	BYTE currentchip;
	
	// Convert given coordinates into LCD's coordinates:
	cnt=y;
	y=x;
	x=cnt/8;
	
	if (y<64)
		{
		GLCD_SetYAddress(y, CHIPSEL1);
		currentchip=CHIPSEL1;
		}
	else
		{
		GLCD_SetYAddress(y-64, CHIPSEL2);
		currentchip=CHIPSEL2;
		}
	
	GLCD_SetXAddress(x, currentchip);
	
	for (cnt=0;cnt<len;cnt++)
	{
		if (y>=0 && y<128)
			GLCD_WriteData(data[cnt], currentchip);

		y++;
		if (y==64)
			{
			GLCD_SetYAddress(0, CHIPSEL2);
			currentchip=CHIPSEL2;
			GLCD_SetXAddress(x, currentchip);
			}
	}
}


void GLCD_ReadRow(BYTE x, BYTE y, BYTE *data, BYTE len)
{
	BYTE cnt=0;
	BYTE currentchip;
	
	// Convert given coordinates into LCD's coordinates:
	cnt=y;
	y=x;
	x=cnt/8;
	
	if (y<64)
		{
		GLCD_SetYAddress(y, CHIPSEL1);
		currentchip=CHIPSEL1;
		}
	else
		{
		GLCD_SetYAddress(y-64, CHIPSEL2);
		currentchip=CHIPSEL2;
		}
	
	GLCD_SetXAddress(x, currentchip);
	GLCD_ReadData(currentchip); // dummy read
	
	for (cnt=0;cnt<len;cnt++)
	{
		if (y>=0 && y<128)
			data[cnt]=GLCD_ReadData(currentchip);

		y++;
		if (y==64)
			{
			GLCD_SetYAddress(0, CHIPSEL2);
			currentchip=CHIPSEL2;
			GLCD_SetXAddress(x, currentchip);
			GLCD_ReadData(currentchip); // dummy read
			}
	}
}