/** \file ******************************************************************
* \brief Dotmatrix LCD Library
* 
* \authors 	Dominic Rathje (dominic.rathje@uni-ulm.de)<br>
* 			Christian Degenhart (christian.degenhart@uni-ulm.de)
* \version 1.1
* \note
* - Compiler          : WinAVR 20100110
* - Supported devices : ATMega8
* 
* 
*
*****************************************************************************/

#include <avr/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <util/delay.h>
#include "lcd.h"
#include "buzzer.h"

#define DISPLAY_bp 4
#define BACKLIGHT_bp 5

///buffer used when scrolling is enabled
char display_buffer[LCD_ROWS][LCD_COLS+1];

///buffer used to store the received characters and rotate the lines	
char input_buffer[LCD_ROWS][INPUT_BUFFER_SIZE+1]= { 
	"********************\0",
	"**  UART-Display  **\0",
	"**by Ernst Abrazzo**\0",
	"********************\0"
	};

///always pints to the first line
uint8_t buffer_index=0;

///points to the next char to write
uint8_t char_index=0;

///is set is an formfeed (\f) is received to parse a sequence of special characters
uint8_t formfeed=0;

///is set if an escape is received, to parse control secquences
uint8_t escape=0;

///is set if an bell is received, to parse control secquences
uint8_t bell=0;

///contains the first char of a sequence of 2 chars that schould be parsed
uint8_t firstchar=0;

///scroll enable flag
uint8_t scroll=0;

///lookup table to translate display charset to ASCII charset
char remap[256] = {
	  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
	 16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
	 32,  33,  34,  35, 162,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
	 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
	160,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
	 80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90, 250, 251, 252,  29, 196,
	 96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
	112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 253, 254, 255, 206, 127,
	128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
	144, 145, 146, 147, 148, 149,  45,  45, 152, 153, 154, 155, 156, 157, 158, 159,
	160,  40, 162,  64, 164, 163, 254,  95, 168, 169, 170, 171, 172, 173, 174, 175,
	128, 140, 130, 131, 180, 143, 182, 183, 184, 185, 186, 187, 139, 138, 190,  96,
	192, 193, 194, 195,  91, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
	208, 209, 210, 211, 212, 213,  92, 215, 216, 217, 218, 219,  94, 221, 222, 190,
	127, 225, 226, 227, 123, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
	240, 125, 242, 243, 244, 245, 124, 247, 248, 249, 250, 251, 126, 253, 254, 255
};

void print_buffer(void) {
	uint8_t i;
	i=buffer_index;

	lcd_clear();

	if(scroll) {
		#ifdef LCD_LINE1
		lcd_setadr(LCD_LINE1);
		lcd_puts(display_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE2
		lcd_setadr(LCD_LINE2);
		lcd_puts(display_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE3
		lcd_setadr(LCD_LINE3);
		lcd_puts(display_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE4
		lcd_setadr(LCD_LINE4);
		lcd_puts(display_buffer[i]);
		#endif

	} else {	
		#ifdef LCD_LINE1
		lcd_setadr(LCD_LINE1);
		lcd_puts(input_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE2
		lcd_setadr(LCD_LINE2);
		lcd_puts(input_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE3
		lcd_setadr(LCD_LINE3);
		lcd_puts(input_buffer[i]);
		i=( (i+1) & 3);
		#endif
		
		#ifdef LCD_LINE4
		lcd_setadr(LCD_LINE4);
		lcd_puts(input_buffer[i]);
		#endif
	}
}

void lcd_scroll(void) {
	static uint8_t line[4]={0,0,0,0};
	
	uint8_t i=0;
	uint8_t tmp,len;
	
	for(i=0; i<4; i++) {
		
		len = strlen(input_buffer[i]);
		
		if(len<LCD_COLS) {
			line[i]=0;
		}
		
		tmp = strlcpy(display_buffer[i], input_buffer[i]+line[i], LCD_COLS+1);
		
		if(len>LCD_COLS) {
			line[i]++;
			line[i] = line[i]%len;
			if(tmp<LCD_COLS) {
				strlcpy(display_buffer[i]+tmp,input_buffer[i], LCD_COLS-tmp+1);
			}
		}
	}
	print_buffer();
}

/** \brief push a character into the buffer.
 * 
 * This is a internal function, that should not be called from other files.
 * To print the buffer to the display call print_buffer()
 * 
 * \param data Character to print.
 * \return void
 */
void push_char(uint8_t data) {
	if(scroll) {
		if(char_index<INPUT_BUFFER_SIZE) {
			input_buffer[buffer_index][char_index] = data;
			char_index++;
		}
	} else {
		if(char_index<LCD_COLS) {
			input_buffer[buffer_index][char_index] = data;
			char_index++;
		} else {
			input_buffer[buffer_index][char_index-1] = 0x15;
		}
	}
	formfeed=0;
	firstchar=0;
}

int lcd_putchar(char data, FILE *stream) {
	if(formfeed) {							// Formfeed introduces special sequences consisting of \f (formfeed)
											// and two following ASCI-Symbols, in order to dipslay symbols
											// which are not available on keyboard
		switch(firstchar) { 				
			case 0:							// 
				firstchar = data;
				break;
			
			case '<':
				switch(data) {
					case '-': 				// \f<- : Pfeil nach links
						push_char(0xE1);	
						break;
					case '|':				// \f<| : Return-Zeichen
						push_char(0x1C);
						break;
					case 'b':				// \f<b : Dicker Pfeil nach links
						push_char(0x11);
						break;
					case '<':				// \f<< : Doppelpfeil nach links
						push_char(0x14);
						break;
						
					default: 	
						push_char('A');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
				
			case '>':
				switch(data) {
					case 'b':				// \f>b : Dicker Pfeil nach rechts
						push_char(0x10);
						break;
					case '>':				// \f>> : Doppelpfeil nach rechts
						push_char(0x15);
						break;
						
					default: 	
						push_char('B');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
			
			case '^':
				switch(data) {
					case '|':				// \f^| : Pfeil nach oben
						push_char(0xDE);
						break;
					case '\\':				// \f^\\ : Pfeil nach links-oben
						push_char(0x16);
						break;
					case '^':				// \f^^ : Doppelfpeil nach oben
						push_char(0x12);
						break;	
					case 'b':				// \f^b : Dicker Pfeil nach oben
						push_char(0x1A);
						break;
					case '0':				// \f^* : Hochgestellte Ziffer
					case '1':				
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						push_char(0x80-'0'+data);
						break;
						
						
					default:
						push_char('C');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
			
			case 'v':
				switch(data) {
					case '|':				// \fv| : Pfeil nach unten
						push_char(0xE0);
						break;
					case '/':				// \fv/ : Pfeil nach links-unten
						push_char(0x18);
						break;
					case 'v':				// \fvv : Doppelpfeil nach unten
						push_char(0x13);
						break;
					case 'b':				// \fvb : Dicker Pfeil nach unten
						push_char(0x1B);
						break;
					
					default: 
						push_char('D');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
			
			case '/':
				switch(data) {
					case '^':				// \f/^ : Pfeil nach rechts-oben
						push_char(0x17);
						break;
					
					default: 
						push_char('E');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
			
			case '\\':						
				switch(data) {
					case 'v':				// \f\\v : Pfeil nach rechts-unten
						push_char(0x19);
						break;
					
					default: 
						push_char('F');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
			
			case '-':						
				switch(data) {
					case '>':				// \f-> : Pfeil nach rechts
						push_char(0xDF);
						break;
					
					default: 
						push_char('G');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
				
			case '|':						
				switch(data) {
					case 'v':				// \fv| : Pfeil nach unten
						push_char(0xE0);
						break;
					case '^':				// \f^| : Pfeil nach oben
						push_char(0xDE);
						break;
					case '1':				// \f|* : Progressbar
						push_char(0xD4);	
						break;
					case '2':
						push_char(0xD3);
						break;
					case '3':
						push_char(0xD2);
						break;
					case '4':
						push_char(0xD1);
						break;
					case '5':
						push_char(0xD0);
						break;
					
					default: 
						push_char('H');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
						break;
				}
				break;
				
			
			default: 	
				push_char('I');	// Müllzeichen aufs display schreiben, um zu signalisieren dass die Sequenz ungültig war
				break;
		}
				
		return 0;
	}
	
	if(escape) {							// Escape-sequence (\e) introduces specialsequences consisting of \e and 
											// one following ASCI-symbol, on order to control some display-properties.
		switch(data) {
			case 'S':						// \eS enables scrolling-mode for line-length above 20 (displayed) symbols
				scroll=1;
				escape=0;
				break;
			case 's':						// \es disables scrolling-mode for line-length above 20 (displayed) symbols
				scroll=0;
				escape=0;
				break;				
			case 'i':						// \ei re-initilizes the display-IC
				lcd_init();
				_delay_ms(5);
				escape=0;
				break;
				
			case 'L':						// \eL switch Backlight on
				PORTB &= ~(1<<BACKLIGHT_bp);
				escape=0;
				break;
				
			case 'l':						// \el switch Backlight off
				PORTB |= (1<<BACKLIGHT_bp);
				escape=0;
				break;
				
			default:						//  Handle non-valid escape-sequences
				escape=0;
				break;
		}
		return 0;
	}
	
	if(bell) {
		switch(data){
			case '1':
				buzzer_start(50);
				break;
			case '2':
				buzzer_start(200);
				break;
			case '3':
				buzzer_start(1000);
				break;
		}
		bell=0;
		return 0;
	}
	
	switch(data) {							// Handle all cases which can occure, if there has no special case happened bevore and a character is received. 
		case '\r':							// \r carriage return detected. Shift lines one lines to the top an insert received string into bottom line in the buffer.
		case '\n':							// \n so do,if newline detected. 
			input_buffer[buffer_index][char_index] = 0;
			buffer_index = ( (buffer_index +1) % LCD_ROWS );
			char_index=0;
			if(data=='\r') print_buffer();	// in addition to the commands above, print buffer to display in case of \r
			break;
			
		case '\e':							// \e escape-sequence detected
			escape=1;
			break;
		case '\f':							// \f formfeed-sequence detected
			formfeed=1;
			break;
			
		case '\a':							// \a beep
			bell=1;
			break;
			
			
		default:
			push_char(remap[(uint8_t)data]);  // If none of the above mentioned exceptiones came true, remap received character. 
			break;
	}
	
	return 0;
}
