#include "i2c.h"
#include "lcd.h"
#include "globals.h"
#include <glib.h>
#include <string.h>
#include <stdlib.h>

#define LCD_ENABLE	0x03	/* the 4x40 LCDs have two enable lines - 0x03 enables both at the same time */
#define LCD_DATA_RAM	128		/* display RAM, in the LCD ICs */
#define LCD_CG_RAM	64		/* custom character RAM, in the LCD ICs */

#define LCD_chars_total 160

char LCD_Data[LCD_rows][LCD_cols];	/* shadow copy of LCD display in local RAM, to minimize slow I2C writes */

/*** EndHeader */


static void break_up_string (char *in_string, int N, char **str1, char **str2, char **str3)
{
	int i;

	gchar** words = g_strsplit(in_string, " ", -1);

	GString* rows[3];

	for (i = 0; i<3; i++) {
		rows[i] = g_string_new(NULL);
	}

	int row = 0;
	int col = 0;
	gchar** word;

	for (word = words; *word != NULL; word++) {
		int wordlen = strlen(*word);

		if (wordlen > N) {
			// model numbers can be up to 64 chars

			int remainder = col + wordlen - N;
			if (row < 3) {
				g_string_append(rows[row], *word);
				col = 0;
				row++;
			}

			if (row < 3) {
				g_string_append(rows[row], *word + (wordlen - remainder));
				col += remainder;
			}

		} else { 

			if (col + wordlen > N) {
				col = 0;
				row++;
			}

			if (row < 3) {
				g_string_append(rows[row], *word);
				col += wordlen;
			}
		}

		if ((row < 3) && (col != 0) && (col < N)) {
			g_string_append(rows[row], " ");
			col++;
		}

		if (col >= N) {
			col = 0;
			row++;
		}
	}

	for (i = 0; i<3; i++) {
		g_string_truncate (rows[i], N);
	}
	
	*str1 = g_string_free(rows[0], FALSE);
	*str2 = g_string_free(rows[1], FALSE);
	*str3 = g_string_free(rows[2], FALSE);
	g_strfreev(words);
}


void LCD_clear()
{
	int i;
	int j;

	I2C_Write(PCF8574A+LCD_Port, 0x00 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x00);
	I2C_Write(PCF8574A+LCD_Port, 0x10 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x10);

	/* wait 1.64ms */
	g_usleep(1640);

	for (i=0; i<4; ++i)
		for (j=0; j<40; ++j) {
			LCD_Data[i][j]=0;
		}

	return;
}


static void LCD_RAM_write(int RAM_start,int row, int col, char *LCD_string)
{
	/* rows: 0-3, cols: 0-39 */

#define data_level 0x04

	int i;
	int cur_row, cur_col;
	int String_length;
	char char_out, enable_one_lcd;
	int ram_location;
	char last_op_a_write;

	if (!LCD_string) {
		return;
	}

	String_length=strlen(LCD_string);
	enable_one_lcd=0x02;

	last_op_a_write=0;
	cur_row=row;
	cur_col=col;

	if (cur_row<2) {
		enable_one_lcd=0x02;
	} else {
		enable_one_lcd=0x01;
	}

	/* write out the string */
	for (i=0; i<String_length; ++i) {
		if (cur_row<2) {
			enable_one_lcd=0x02;
		} else {
			enable_one_lcd=0x01;
		}

		// bounds check
		if ((cur_row >= LCD_rows) || (cur_col >= LCD_cols)) {
			break;
		}

		if (LCD_string[i]!=LCD_Data[cur_row][cur_col]) {
			if (!last_op_a_write) {
				/* move to new position */
				ram_location = (cur_row & 1) * 64 + cur_col + RAM_start;
				char_out=ram_location & 0xf0;
				I2C_Write(PCF8574A+LCD_Port, char_out | enable_one_lcd);
				I2C_Write(PCF8574A+LCD_Port, char_out);
				char_out=ram_location << 4;
				I2C_Write(PCF8574A+LCD_Port, char_out | enable_one_lcd);
				I2C_Write(PCF8574A+LCD_Port, char_out);
			}
			LCD_Data[cur_row][cur_col]=LCD_string[i];
			char_out=LCD_string[i] & 0xf0;
			I2C_Write(PCF8574A+LCD_Port, char_out | enable_one_lcd | data_level);
			I2C_Write(PCF8574A+LCD_Port, char_out | data_level);
			char_out=LCD_string[i] << 4;
			I2C_Write(PCF8574A+LCD_Port, char_out | enable_one_lcd | data_level);
			I2C_Write(PCF8574A+LCD_Port, char_out | data_level);
			last_op_a_write=1;
		} else {
			last_op_a_write=0;
		}

		++cur_col;
		if (cur_col==40) {
			cur_col=0;
			++cur_row;
		}
	}
	return;
}


void LCD_write(int row, int col, char *LCD_string)
{
	LCD_RAM_write(LCD_DATA_RAM, row, col, LCD_string);
}


void LCD_write_padded_spaces(int row, int col, char *LCD_string, int width)
{
	gchar *padded = NULL;

	int in_len = strlen (LCD_string);

	if (in_len > width) {
		padded = g_strdup (LCD_string);
		padded[width] = 0;
	} else {
		padded = g_strdup_printf ("%s%*s", LCD_string, width - in_len, "");
	}

	LCD_write(row, col, padded);
	g_free (padded);
}


void LCD_write_padded_to_end_of_line(int row, int col, char *LCD_string)
{
	LCD_write_padded_spaces (row, col, LCD_string, LCD_cols - col);
}


static void LCD_make_custom_chars()
{
	/* define custom LCD characters 2 and 3 (up arrow and down arrow).*/
	/* The up arrow is stored in one LCD controller (for rows 0 and 1) */
	/* The down arrow is stored in the other LCD controller (for rows 2 and 3) */
	LCD_RAM_write(LCD_CG_RAM,0,16,"\x4");
	LCD_RAM_write(LCD_CG_RAM,0,17,"\xe");
	LCD_RAM_write(LCD_CG_RAM,0,18,"\x15");
	LCD_RAM_write(LCD_CG_RAM,0,19,"\x4");
	LCD_RAM_write(LCD_CG_RAM,0,20,"\x4");
	LCD_RAM_write(LCD_CG_RAM,0,21,"\x4");
	LCD_RAM_write(LCD_CG_RAM,0,22,"\x4");
	LCD_RAM_write(LCD_CG_RAM,0,23,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,24,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,25,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,26,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,27,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,28,"\x4");
	LCD_RAM_write(LCD_CG_RAM,2,29,"\x15");
	LCD_RAM_write(LCD_CG_RAM,2,30,"\xe");
	LCD_RAM_write(LCD_CG_RAM,2,31,"\x4");
// prove that it works - debug only
//	LCD_RAM_write(LCD_DATA_RAM,0,39,"\x2");
//	LCD_RAM_write(LCD_DATA_RAM,3,39,"\x3");

	return;
}


void LCD_initialize(void)
{
	/* Wait 15ms */
	g_usleep(15000);

	I2C_Write(PCF8574A+LCD_Port, 0x30 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x30);

	/* wait 4.1ms */
	g_usleep(4100);

	I2C_Write(PCF8574A+LCD_Port, 0x30 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x30);

	/* wait 100us */
	g_usleep(100);

	I2C_Write(PCF8574A+LCD_Port, 0x30 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x30);

	/* wait 4.1ms */
	g_usleep(4100);

	I2C_Write(PCF8574A+LCD_Port, 0x20 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x20);

	/*wait 40us */
	g_usleep(40);

	I2C_Write(PCF8574A+LCD_Port, 0x20 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x20);
	I2C_Write(PCF8574A+LCD_Port, 0x80 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x80);

	/* wait 40us */
	g_usleep(40);

	LCD_clear();

	I2C_Write(PCF8574A+LCD_Port, 0x00 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x00);
	I2C_Write(PCF8574A+LCD_Port, 0x60 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x60);

	/* wait 40us */
	g_usleep(40);

	I2C_Write(PCF8574A+LCD_Port, 0x00 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0x00);
	I2C_Write(PCF8574A+LCD_Port, 0xc0 | LCD_ENABLE);
	I2C_Write(PCF8574A+LCD_Port, 0xc0);

	LCD_make_custom_chars();

	return;
}


void LCD_display_extended_message(char *response, gboolean show_change_message, gboolean is_error_screen)
{
	char *row0 = NULL;
	char *row1 = NULL;
	char *row2 = NULL;

	g_assert (response);

	if (is_error_screen) {
		globals.MenuStatus.Error_Screen = is_error_screen;
	}

	break_up_string (response, LCD_cols, &row0, &row1, &row2);

	LCD_clear();
	LCD_write(0,0,row0);
	LCD_write(1,0,row1);
	LCD_write(2,0,row2);

	if (show_change_message) {
		LCD_write(3,0,Press_Change_Message);
	}

	g_free (row0);
	g_free (row1);
	g_free (row2);

	return;
}