/*
 * Library zur Ansteuerung von OLEDs mit SSD1306 Chip
 * Nur I²C Anschluss
 * Getestet mit ATmega328
 * 
 *  Created: 26.04.2020
 *  Author: Erwin Endres
 *	
 * Fontformat:
 * Fonts in 8, 16, 24, 36, 48 und 64 Bytes Höhe werden gleichzeitig unterstützt
 * Die Fonts sind spaltenweise organisiert, LSB oben
 */ 

//ToDo:	Grafik in 'Fenster'
//		Fill area white/black -> auch zum Löschen von Text
//		56px Schrift
//		function: oled_get_stringsize
//		function set_contrast
//		function display_off (gibt es power save modi?)
/////////////////////////////////////////////////////////////////////

/*
#### Difference SSD1306 <-> SH1106
The SSD1306 has an expanded command set over the SH1106.
The SH1106 only supports page addressing mode
The SSD1306 added special horizontal and vertical addressing modes.
The SSD1306 also has capability for automatic scrolling.
*/

#include "oled_ssd1306.h"
#include "i2cmaster.h"

#include <avr/pgmspace.h>

#define FIRSTCHAR 0x20

#ifdef FONTSIZE8
#include FONTFILE8		
#endif
#ifdef FONTSIZE16
#include FONTFILE16
#endif
#ifdef FONTSIZE24
#include FONTFILE24
#endif
#ifdef FONTSIZE32
#include FONTFILE32 
#endif
#ifdef FONTSIZE48
#include FONTFILE48
#endif
#ifdef FONTSIZE64
#include FONTFILE64
#endif


uint8_t oled_getsize(uint8_t c, uint8_t size);
void oled_offset (uint8_t offset);

const uint8_t ssd1306_init_cmd [] PROGMEM = {
COMMAND,
SET_DISPLAY_OFF,
COMMAND_NO_OPERATION,
SET_PAGE_START,
SET_COM_OUTPUT_REMAPPED, 
SET_DISPLAY_START_LINE,			
ENTIRE_DISPLAY_ON, 
SET_SEGMENT_REMAP_O2,			//Spiegelt Display horizontal -> 90° Modus!
SET_DISPLAY_NORMAL,	
SET_CONTRAST_CONTROL,	0x80,	//0x0..0xff ist möglich
SET_MULTIPLEX_RATIO,	0x3F,	//0x3F
SET_DISPLAY_OFFSET,		0x00, 
SET_DISPLAY_CLOCK,		0xF0,
SET_PRECHARGE_PERIOD,	0x22, 
SET_COM_PINS,			0x12, 
SET_VCOMH_DESELECT,		0x20, 
CHARGE_BUMB_SETTING,	0x14, 
SET_DISPLAY_ON };

//***************************************************************************************
void oled_init (void){
	// i2c_init();
	
	i2c_start_wait(I2C_ADDR_OLED);
	for (uint8_t tmp = 0; tmp < sizeof(ssd1306_init_cmd); tmp++) 
	{	
		i2c_write(pgm_read_byte(&ssd1306_init_cmd[tmp]));
		//i2c_byte(pgm_read_byte(&ssd1306_init_cmd[tmp]));
	}
	i2c_stop();
    oled_clear_screen();
}

//***************************************************************************************

void oled_clear_screen (void)
{
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(COMMAND);
	i2c_write(SET_MEMORY_ADDR_MODE);
	i2c_write(HORIZONTAL_ADDRESSING_MODE);		//activate Horizontal Addressing Mode
	
	i2c_write(SET_COLUMN_ADDRESS);
	i2c_write(0);		//Column 0 .. 127
	i2c_write(XRES - 1);
	
	i2c_write(SET_PAGE_ADDRESS);
	i2c_write(0);
	i2c_write(YRES / 8 - 1);	
	i2c_stop();
	
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(DATA);
	
	for (uint16_t i = 0; i < XRES * 8; i++)
	{	// 128 * 8 / Byte
		i2c_write(0x0);
	}
}

void oled_write_char (uint8_t c, uint8_t xPos, uint8_t yPos, uint8_t fontsize, uint8_t invers) {		//size = Fontsize [8,16,24,32,48, 64]

	//c = c - font_firstchar(fontsize); //für alten Font
	tImage *imagePtr;
	uint8_t *dataPtr;
	
	c = c - FIRSTCHAR;	//erstes Zeichen im Font = 0x20 / gleich für alle Fonts!
	uint8_t h = fontsize / 8;
	
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(COMMAND);
	
	i2c_write(SET_MEMORY_ADDR_MODE);			
	i2c_write(VERTICAL_ADDRESSING_MODE);		//set Vertical Addressing Mode
	
	i2c_write(SET_COLUMN_ADDRESS);			
	i2c_write(xPos);
	i2c_write(xPos + oled_getsize(c, fontsize) -1);

	i2c_write(SET_PAGE_ADDRESS);	
	i2c_write(yPos);
	i2c_write(yPos + h - 1);
	i2c_stop();
	
	i2c_start_wait(I2C_ADDR_OLED);		//jetzt die Font-Bitmap ausgeben
	i2c_write(DATA);	

	switch (fontsize)
	{
	#ifdef FONTSIZE8
		case  8:	imagePtr = (tImage*)pgm_read_word(&charset8[c]); break;
	#endif
	#ifdef FONTSIZE16
		case 16:	imagePtr = (tImage*)pgm_read_word(&charset16[c]); break;
	#endif
	#ifdef FONTSIZE24
		case 24:	imagePtr = (tImage*)pgm_read_word(&charset24[c]); break;
	#endif	
	#ifdef FONTSIZE32
		case 32:	imagePtr = (tImage*)pgm_read_word(&charset32[c]); break;
	#endif
	#ifdef FONTSIZE48	
		case 48:	imagePtr = (tImage*)pgm_read_word(&charset48[c]); break;
	#endif
	#ifdef FONTSIZE64	
		case 64:	imagePtr = (tImage*)pgm_read_word(&charset64[c]); break;
	#endif
	}
	dataPtr = (uint8_t*) pgm_read_word(&(imagePtr->data));

	for (uint16_t i = 0; i < (oled_getsize(c, fontsize) * h); i++)	//für Zeichen mit Bitmap < 256 Byte reicht uint8_t 
	{
		if (invers) i2c_write(~(pgm_read_byte(dataPtr+i)));
		else i2c_write(pgm_read_byte(dataPtr + i));
	}
	i2c_stop();
}

// Senkrechte Linie zeichnen -> Länge in ganzen Pages á 8 px [0..7]
void oled_line_v(uint8_t xstart, uint8_t ystart, uint8_t yend, uint8_t width, uint8_t col)
{
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(COMMAND);
		
	i2c_write(SET_MEMORY_ADDR_MODE);
	i2c_write(VERTICAL_ADDRESSING_MODE);		//set Vertical Addressing Mode
		
	i2c_write(SET_COLUMN_ADDRESS);
	i2c_write(xstart);
	i2c_write(xstart + width);
		
	i2c_write(SET_PAGE_ADDRESS);
	i2c_write(ystart);
	i2c_write(yend);
	i2c_stop();
		
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(DATA);
	for (int16_t i = 0; i < (yend - ystart + 1) * width; i++)
	{
		if (col == 1) i2c_write(0xff);
		else i2c_write(0x0);
	}
	i2c_stop();
}

//***************************************************************************************
void oled_write_str (char* str, int8_t xpos, int8_t ypos, uint8_t fontsize, uint8_t invers)
{	
	uint8_t xaktpos = xpos;
	while (*str)	//bis letztes Byte == 0 => Abbruch while()
	{		
		if (xaktpos + oled_getsize(*str - 0x20, fontsize) > XRES) break;	//clip horizontal
		else if	(ypos * 8 + fontsize > YRES) break;						//clip vertical
		else
		{
			oled_write_char(*str, xaktpos, ypos, fontsize, invers);
			xaktpos = xaktpos + oled_getsize(*str - 0x20, fontsize);
			*str ++;
		}
	}
}

void oled_write_str_right (char *str, int8_t xpos, int8_t ypos, uint8_t fontsize, uint8_t invers)
// gibt String rechtsbündig aus
// wenn String zu breit, links abschneiden
{	
	int8_t xaktpos = xpos;	
	char *xstr = str;
	uint8_t i;
	uint8_t len = 0;
	
	while (*xstr) {
		xstr++;
	}
	xstr--;
	xaktpos++;
	for (; xstr >= str; xstr--) {	//>= ?
		xaktpos = xaktpos - oled_getsize(*xstr-20, fontsize);
		if (xaktpos < 0) {
			//xaktpos = i;
			break;
		}
		else len = len + oled_getsize(*xstr-20, fontsize);
	}
	len--;
	xstr++;
	if (xstr > str) {
		for (i = 0; *xstr; xstr++, i++) {
			str[i] = *xstr;
		}
		str[i] = 0;
	}
	oled_write_str(str, xpos - len, ypos, fontsize, invers);
}

/****************************************************************************************
void oled_write_P (const char *Buffer,...)
// by Ulrich Radig
{
	va_list ap;
	va_start (ap, Buffer);	
	
	int format_flag;
	char str_buffer[10];
	char str_null_buffer[10];
	char move = 0;
	char Base = 0;
	int tmp = 0;
	char by;
	char *ptr;
		
	//Ausgabe der Zeichen
    for(;;){
		by = pgm_read_byte(Buffer++);
		if(by==0) break; // end of format string
            
		if (by == '%'){
            by = pgm_read_byte(Buffer++);
			if (isdigit(by)>0){                   
 				str_null_buffer[0] = by;
				str_null_buffer[1] = '\0';
				move = atoi(str_null_buffer);
                by = pgm_read_byte(Buffer++);
				}

			switch (by){
                case 's':
                    ptr = va_arg(ap,char *);
                    while(*ptr) { oled_write_char(*ptr++); }
                    break;
				case 'b':
					Base = 2;
					goto ConversionLoop;
				case 'c':
					//Int to char
					format_flag = va_arg(ap,int);
					oled_write_char (format_flag++);
					break;
				case 'i':
					Base = 10;
					goto ConversionLoop;
				case 'o':
					Base = 8;
					goto ConversionLoop;
				case 'x':
					Base = 16;
					// ****************************
					ConversionLoop:
					// ****************************
					itoa(va_arg(ap,int),str_buffer,Base);
					int b=0;
					while (str_buffer[b++] != 0){};
					b--;
					if (b<move){
						move -=b;
						for (tmp = 0;tmp<move;tmp++){
							str_null_buffer[tmp] = '0';
							}
						//tmp ++;
						str_null_buffer[tmp] = '\0';
						strcat(str_null_buffer,str_buffer);
						strcpy(str_buffer,str_null_buffer);
						}
					oled_write_str (str_buffer);
					move =0;
					break;
				}
		}else{
			oled_write_char (by);	
		}
	}
	va_end(ap);
}

*/

uint8_t oled_getsize(uint8_t c, uint8_t fontsize) 		//Gibt Breite eines Zeichens zurück
{
	switch (fontsize)
	{
		#ifdef FONTSIZE8
		case 8:	{	//imagePtr = (tImage*)pgm_read_word(&charset8[c]);
					//return (pgm_read_byte(&imagePtr->width ));
					return (pgm_read_byte(&((tImage*)pgm_read_word(&charset8[c]))->width ));
				}
		#endif
		
		#ifdef FONTSIZE16
			case 16:	return (pgm_read_byte(&((tImage*)pgm_read_word(&charset16[c]))->width ));
		#endif
		
		#ifdef FONTSIZE24
			case 24:    return (pgm_read_byte(&((tImage*)pgm_read_word(&charset24[c]))->width ));
		#endif
		
		#ifdef FONTSIZE32
			case 32:	 return (pgm_read_byte(&((tImage*)pgm_read_word(&charset32[c]))->width ));
		#endif
		
		#ifdef FONTSIZE48
			case 48:	return (pgm_read_byte(&((tImage*)pgm_read_word(&charset48[c]))->width ));
		#endif
		
		#ifdef FONTSIZE64
			case 64:	return (pgm_read_byte(&((tImage*)pgm_read_word(&charset64[c]))->width ));
		#endif
	
		default: return(0); break;
	}
}

// ermöglicht vertikales Scrolling
//offset periodisch 0..64 hochzählen = scrolling
void oled_offset(uint8_t offset)
{
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(COMMAND);
	i2c_write(SET_DISPLAY_OFFSET);
	i2c_write(offset);
	i2c_stop();
}

//Horizontales Scrolling 
//wenn aktiv, darf das GRAM nicht mehr beschrieben werden, sonst sind Darstellungsfehler möglich
void oled_scroll_right(uint8_t onoff, uint8_t startpage, uint8_t endpage)
{
	i2c_start_wait(I2C_ADDR_OLED);
	i2c_write(COMMAND);
	i2c_write(0x2e);			//deactivate scrolling to prevent that RAM content may be corrupted
	if (onoff == 1) {
		i2c_write(0x29);
		i2c_write(0);			//A: Dummy byte
		i2c_write(startpage);	//B: start page address
		i2c_write(0b001);		//C: time interval
		i2c_write(endpage);		//D: end page address
		i2c_write(0);			//E: Vertical scrolling offset
		i2c_write(0x2f);			//activate scrolling
	}
	i2c_stop();
}