// Based on the work by DFRobot

#include "LCDisplay2004.h"
#include <inttypes.h>
#if defined(ARDUINO) && ARDUINO >= 100

#include "Arduino.h"

#define printIIC(args)	Wire.write(args)
inline size_t CLCDisplay2004::write(uint8_t value) {
	send(value, Rs);
	return 1;
}

#else
#include "WProgram.h"

#define printIIC(args)	Wire.send(args)
inline void CLCDisplay2004::write(uint8_t value) {
	send(value, Rs);
}

#endif
#include "Wire.h"



// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set: 
//    DL = 1; 8-bit interface data 
//    N = 0; 1-line display 
//    F = 0; 5x8 dot character font 
// 3. Display on/off control: 
//    D = 0; Display off 
//    C = 0; Cursor off 
//    B = 0; Blinking off 
// 4. Entry mode set: 
//    I/D = 1; Increment by 1
//    S = 0; No shift 
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).

CLCDisplay2004::CLCDisplay2004(uint8_t lcdFAddress,uint8_t lcdFColCount,uint8_t lcdFRowCount)
{
  FAddress = lcdFAddress;
  FColCount = lcdFColCount;
  FRowCount = lcdFRowCount;
  _backlightval = LCD_NOBACKLIGHT;
}

void CLCDisplay2004::OledInit()
{
  _oled = true;
  init_priv();
}

void CLCDisplay2004::Init()
{
	init_priv();
}

void CLCDisplay2004::init_priv()
{
	Wire.begin();
	FDisplayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
	Open(FColCount, FRowCount);  
}

void CLCDisplay2004::Open(uint8_t cols, uint8_t lines, uint8_t dotsize) {
	if (lines > 1) {
		FDisplayFunction |= LCD_2LINE;
	}
	FLineCount = lines;

	// for some 1 line displays you can select a 10 pixel high font
	if ((dotsize != 0) && (lines == 1)) {
		FDisplayFunction |= LCD_5x10DOTS;
	}

	// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
	// according to datasheet, we need at least 40ms after power rises above 2.7V
	// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
	delay(50); 
  
	// Now we pull both RS and R/W low to begin commands
	expanderWrite(_backlightval);	// reset expanderand turn backlight off (Bit 8 =1)
	delay(1000);

  	//put the LCD into 4 bit mode
	// this is according to the hitachi HD44780 datasheet
	// figure 24, pg 46
	
	  // we start in 8bit mode, try to set 4 bit mode
   write4bits(0x03 << 4);
   delayMicroseconds(4500); // wait min 4.1ms
   
   // second try
   write4bits(0x03 << 4);
   delayMicroseconds(4500); // wait min 4.1ms
   
   // third go!
   write4bits(0x03 << 4); 
   delayMicroseconds(150);
   
   // finally, set to 4-bit interface
   write4bits(0x02 << 4); 


	// set # lines, font size, etc.
	Command(LCD_FUNCTIONSET | FDisplayFunction);  
	
	// turn the display on with no cursor or blinking default
	FDisplayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
	DisplayOn();
	
	// clear it off
	Clear();
	
	// Initialize to default text direction (for roman languages)
	FDisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
	
	// set the entry mode
	Command(LCD_ENTRYMODESET | FDisplayMode);
	
	CursorHome();
  
}

/********** high level commands, for the user! */
void CLCDisplay2004::Clear()
{
	Command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
	delayMicroseconds(2000);  // this command takes a long time!
  if (_oled) SetCursor(0,0);
}

void CLCDisplay2004::CursorHome()
{
	Command(LCD_RETURNHOME);  // set cursor position to zero
	delayMicroseconds(2000);  // this command takes a long time!
}

void CLCDisplay2004::SetCursor(uint8_t col, uint8_t row)
{
	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
	if ( row > FLineCount ) {
		row = FLineCount-1;    // we count rows starting w/0
	}
	Command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

// Turn the display on/off (quickly)
void CLCDisplay2004::DisplayOff()
{
	FDisplayControl &= ~LCD_DISPLAYON;
	Command(LCDFDisplayControl | FDisplayControl);
}
void CLCDisplay2004::DisplayOn() 
{
	FDisplayControl |= LCD_DISPLAYON;
	Command(LCDFDisplayControl | FDisplayControl);
}

// Turns the underline cursor on/off
void CLCDisplay2004::CursorOff()
{
	FDisplayControl &= ~LCD_CURSORON;
	Command(LCDFDisplayControl | FDisplayControl);
}
void CLCDisplay2004::CursorOn() 
{
	FDisplayControl |= LCD_CURSORON;
	Command(LCDFDisplayControl | FDisplayControl);
}

// Turn on and off the blinking cursor
void CLCDisplay2004::BlinkOff() 
{
	FDisplayControl &= ~LCD_BLINKON;
	Command(LCDFDisplayControl | FDisplayControl);
}
void CLCDisplay2004::BlinkOn() 
{
	FDisplayControl |= LCD_BLINKON;
	Command(LCDFDisplayControl | FDisplayControl);
}

// These commands scroll the display without changing the RAM
void CLCDisplay2004::ScrollDisplayLeft(void) 
{
	Command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void CLCDisplay2004::ScrollDisplayRight(void) 
{
	Command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right
void CLCDisplay2004::LeftToRight(void) 
{
	FDisplayMode |= LCD_ENTRYLEFT;
	Command(LCD_ENTRYMODESET | FDisplayMode);
}

// This is for text that flows Right to Left
void CLCDisplay2004::RightToLeft(void)
{
	FDisplayMode &= ~LCD_ENTRYLEFT;
	Command(LCD_ENTRYMODESET | FDisplayMode);
}

// This will 'right justify' text from the cursor
void CLCDisplay2004::AutoscrollOn(void) 
{
	FDisplayMode |= LCD_ENTRYSHIFTINCREMENT;
	Command(LCD_ENTRYMODESET | FDisplayMode);
}

// This will 'left justify' text from the cursor
void CLCDisplay2004::AutoscrollOff(void) 
{
	FDisplayMode &= ~LCD_ENTRYSHIFTINCREMENT;
	Command(LCD_ENTRYMODESET | FDisplayMode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters
void CLCDisplay2004::CreateChar(uint8_t location, uint8_t charmap[]) 
{
	location &= 0x7; // we only have 8 locations 0-7
	Command(LCD_SETCGRAMADDR | (location << 3));
	for (int i=0; i<8; i++) 
	{
		write(charmap[i]);
	}
}

//createChar with PROGMEM input
void CLCDisplay2004::CreateChar(uint8_t location, const char *charmap) 
{
	location &= 0x7; // we only have 8 locations 0-7
	Command(LCD_SETCGRAMADDR | (location << 3));
	for (int i=0; i<8; i++) 
	{
	    	write(pgm_read_byte_near(charmap++));
	}
}

// Turn the (optional) backlight off/on
void CLCDisplay2004::BacklightOff(void) 
{
	_backlightval=LCD_NOBACKLIGHT;
	expanderWrite(0);
}

void CLCDisplay2004::BacklightOn(void) 
{
	_backlightval=LCD_BACKLIGHT;
	expanderWrite(0);
}



/*********** mid level commands, for sending data/cmds */

inline void CLCDisplay2004::Command(uint8_t value) 
{
	send(value, 0);
}


/************ low level data pushing commands **********/

// write either command or data
void CLCDisplay2004::send(uint8_t value, uint8_t mode) 
{
	uint8_t highnib=value&0xf0;
	uint8_t lownib=(value<<4)&0xf0;
       write4bits((highnib)|mode);
	write4bits((lownib)|mode); 
}

void CLCDisplay2004::write4bits(uint8_t value) 
{
	expanderWrite(value);
	pulseEnable(value);
}

void CLCDisplay2004::expanderWrite(uint8_t _data){                                        
	Wire.beginTransmission(FAddress);
	printIIC((int)(_data) | _backlightval);
	Wire.endTransmission();   
}

void CLCDisplay2004::pulseEnable(uint8_t _data){
	expanderWrite(_data | En);	// En high
	delayMicroseconds(1);		// enable pulse must be >450ns
	
	expanderWrite(_data & ~En);	// En low
	delayMicroseconds(50);		// commands need > 37us to settle
} 


// Alias functions
/*
void CLCDisplay2004::CursorOn()
{
	Cursor();
}

void CLCDisplay2004::CursorOff()
{
	NoCursor();
}

void CLCDisplay2004::Blink_on()
{
	blink();
}

void CLCDisplay2004::Blink_off()
{
	noBlink();
}

void CLCDisplay2004::load_custom_character(uint8_t char_num, uint8_t *rows){
		createChar(char_num, rows);
}

void CLCDisplay2004::SetBacklight(uint8_t new_val){
	if(new_val){
		backlight();		// turn backlight on
	}else{
		noBacklight();		// turn backlight off
	}
}

void CLCDisplay2004::printstr(const char c[]){
	//This function is not identical to the function used for "real" I2C displays
	//it's here so the user sketch doesn't have to be changed 
	print(c);
}
*/
/*
// unsupported API functions
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
void CLCDisplay2004::off(){}
void CLCDisplay2004::on(){}
void CLCDisplay2004::setDelay (int cmdDelay,int charDelay) {}
uint8_t CLCDisplay2004::status(){return 0;}
uint8_t CLCDisplay2004::keypad (){return 0;}
uint8_t CLCDisplay2004::init_bargraph(uint8_t graphtype){return 0;}
void CLCDisplay2004::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end){}
void CLCDisplay2004::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_row_end){}
void CLCDisplay2004::setContrast(uint8_t new_val){}
#pragma GCC diagnostic pop
*/	
