#include <avr/io.h>
#include <util/delay.h>
#include <inttypes.h>
#include <avr/pgmspace.h>
#include "lcd_lib.h"
#include <stdbool.h>
#include <stdint.h>

#define NUM_BUTTONS 7
#define BUTTON_MASK 0x7F

void LCDsend2digitNumber(int8_t num) {
    if (num < 0) {
        num *= -1;
        LCDsendChar((uint8_t)'-');
    }
    LCDsendChar((uint8_t)('0' + num / 10));
    LCDsendChar((uint8_t)('0' + num % 10));
}


enum MODE {
    PLUS = 0,
    MINUS = 1,
    MULTIPLY = 2,
    DIVIDE = 3
};

int16_t plus(int8_t a, int8_t b)     { return a + b; }

int16_t minus(int8_t a, int8_t b)    { return a - b; }

int16_t multiply(int8_t a, int8_t b) { return a * b; }

int16_t divide(int8_t a, int8_t b)   { return (a * 10) / b; }

int16_t(*ops[])(int8_t, int8_t) = { plus, minus, multiply, divide };

int main(void) {
  DDRD &= ~BUTTON_MASK;
  PORTD |= BUTTON_MASK;

  uint8_t prev_state = 0xFF;  // Initial: all buttons not pressed (HIGH)

  bool reverse = false;
  int8_t a = 0, b = 0;
  int16_t c = 0;
  enum MODE mode = 0;
  char mode_chars[] = {'+', '-', '*', '/'};

  LCDinit();
  LCDGotoXY(0, 0);
  LCDsend2digitNumber(a);
  LCDsendChar((uint8_t)mode_chars[mode]);
  LCDsend2digitNumber(b);

  while (1) {
    uint8_t curr_state = PIND & BUTTON_MASK;

    for (uint8_t bit = 0; bit < NUM_BUTTONS; bit++) {
      uint8_t bit_mask = (1 << bit);
      if ((curr_state & bit_mask) && !(prev_state & bit_mask)) {
        LCDclr();
        switch (bit) {
          case 2: ++mode; mode %= 4; break;
          case 0: a += reverse ? -10 : 10; break;
          case 1: a += reverse ? -1  : 1;  break;
          case 3: b += reverse ? -10 : 10; break;
          case 4: b += reverse ? -1  : 1;  break;
          case 6: reverse = !reverse; break;
          default: break;
        }
        if (a > 99 || a < -99) a = 0;
        if (b > 99 || b < -99) b = 0;
        LCDGotoXY(0, 0);
        LCDsend2digitNumber(a);
        LCDsendChar((uint8_t)mode_chars[mode]);
        LCDsend2digitNumber(b);
        LCDsendChar((uint8_t)'=');
		
        LCDGotoXY(0, 1);
        if (b == 0 && mode == DIVIDE) LCDstring((uint8_t*)"Error", 5);
        else {
          c = ops[mode](a, b); 
          if (c < 0) {
            c *= -1;
            LCDsendChar((uint8_t)'-');
          }
          if (mode == DIVIDE) {
            LCDsendChar((uint8_t)('0' + c / 10000));
          }
          LCDsendChar((uint8_t)('0' + c / 1000 % 10));
          LCDsendChar((uint8_t)('0' + c / 100 % 10));
          LCDsendChar((uint8_t)('0' + c / 10 % 10));
          if (mode == DIVIDE) {
            LCDsendChar((uint8_t)'.');
            LCDsendChar((uint8_t)('0' + c % 10));
          }
          else {
            LCDsendChar((uint8_t)('0' + c % 10));
          }
        }
        // if (reverse) {
        //   LCDGotoXY(0, 1);
        //   LCDsendChar((uint8_t)'R');
        // }
        _delay_ms(20);
      }
    }

    prev_state = curr_state;
    _delay_ms(10);
  }

  return 0;
}


void LCDsendChar(uint8_t ch)		//Sends Char to LCD
{
	LDP=(ch&0b11110000);
	LCP|=1<<LCD_RS;
	LCP|=1<<LCD_E;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);	
	LCP&=~(1<<LCD_RS);
	_delay_ms(1);
	LDP=((ch&0b00001111)<<4);
	LCP|=1<<LCD_RS;
	LCP|=1<<LCD_E;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);	
	LCP&=~(1<<LCD_RS);
	_delay_ms(1);
}
void LCDsendCommand(uint8_t cmd)	//Sends Command to LCD
{
	LDP=(cmd&0b11110000);
	LCP|=1<<LCD_E;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);
	_delay_ms(1);
	LDP=((cmd&0b00001111)<<4);	
	LCP|=1<<LCD_E;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);
	_delay_ms(1);
}
void LCDinit(void)//Initializes LCD
{
	_delay_ms(15);
	LDP=0x00;
	LCP=0x00;
	LDDR|=1<<LCD_D7|1<<LCD_D6|1<<LCD_D5|1<<LCD_D4;
	LCDR|=1<<LCD_E|1<<LCD_RW|1<<LCD_RS;
   //---------one------
	LDP=0<<LCD_D7|0<<LCD_D6|1<<LCD_D5|1<<LCD_D4; //4 bit mode
	LCP|=1<<LCD_E|0<<LCD_RW|0<<LCD_RS;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);
	_delay_ms(1);
	//-----------two-----------
	LDP=0<<LCD_D7|0<<LCD_D6|1<<LCD_D5|1<<LCD_D4; //4 bit mode
	LCP|=1<<LCD_E|0<<LCD_RW|0<<LCD_RS;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);
	_delay_ms(1);
	//-------three-------------
	LDP=0<<LCD_D7|0<<LCD_D6|1<<LCD_D5|0<<LCD_D4; //4 bit mode
	LCP|=1<<LCD_E|0<<LCD_RW|0<<LCD_RS;		
	_delay_ms(1);
	LCP&=~(1<<LCD_E);
	_delay_ms(1);
	//--------4 bit--dual line---------------
	LCDsendCommand(0b00101000);
   //-----increment address, cursor shift------
	LCDsendCommand(0b00001110);


}			
void LCDclr(void)				//Clears LCD
{
	LCDsendCommand(1<<LCD_CLR);
}
void LCDhome(void)			//LCD cursor home
{
	LCDsendCommand(1<<LCD_HOME);
}
void LCDstring(uint8_t* data, uint8_t nBytes)	//Outputs string to LCD
{
register uint8_t i;

	// check to make sure we have a good pointer
	if (!data) return;

	// print data
	for(i=0; i<nBytes; i++)
	{
		LCDsendChar(data[i]);
	}
}
void LCDGotoXY(uint8_t x, uint8_t y)	//Cursor to X Y position
{
	register uint8_t DDRAMAddr;
	// remap lines into proper order
	switch(y)
	{
	case 0: DDRAMAddr = LCD_LINE0_DDRAMADDR+x; break;
	case 1: DDRAMAddr = LCD_LINE1_DDRAMADDR+x; break;
	case 2: DDRAMAddr = LCD_LINE2_DDRAMADDR+x; break;
	case 3: DDRAMAddr = LCD_LINE3_DDRAMADDR+x; break;
	default: DDRAMAddr = LCD_LINE0_DDRAMADDR+x;
	}
	// set data address
	LCDsendCommand(1<<LCD_DDRAM | DDRAMAddr);
	
}
//Copies string from flash memory to LCD at x y position
//const uint8_t welcomeln1[] PROGMEM="AVR LCD DEMO\0";
//CopyStringtoLCD(welcomeln1, 3, 1);	
void CopyStringtoLCD(const uint8_t *FlashLoc, uint8_t x, uint8_t y)
{
	uint8_t i;
	LCDGotoXY(x,y);
	for(i=0;(uint8_t)pgm_read_byte(&FlashLoc[i]);i++)
	{
		LCDsendChar((uint8_t)pgm_read_byte(&FlashLoc[i]));
	}
}
//defines char symbol in CGRAM
/*
const uint8_t backslash[] PROGMEM= 
{
0b00000000,//back slash
0b00010000,
0b00001000,
0b00000100,
0b00000010,
0b00000001,
0b00000000,
0b00000000
};
LCDdefinechar(backslash,0);
*/
void LCDdefinechar(const uint8_t *pc,uint8_t char_code){
	uint8_t a, pcc;
	uint16_t i;
	a=(char_code<<3)|0x40;
	for (i=0; i<8; i++){
		pcc=pgm_read_byte(&pc[i]);
		LCDsendCommand(a++);
		LCDsendChar(pcc);
		}
}

void LCDshiftLeft(uint8_t n)	//Scrol n of characters Right
{
	for (uint8_t i=0;i<n;i++)
	{
		LCDsendCommand(0x1E);
	}
}
void LCDshiftRight(uint8_t n)	//Scrol n of characters Left
{
	for (uint8_t i=0;i<n;i++)
	{
		LCDsendCommand(0x18);
	}
}
void LCDcursorOn(void) //displays LCD cursor
{
	LCDsendCommand(0x0E);
}
void LCDcursorOnBlink(void)	//displays LCD blinking cursor
{
	LCDsendCommand(0x0F);
}
void LCDcursorOFF(void)	//turns OFF cursor
{
	LCDsendCommand(0x0C);
}
void LCDblank(void)		//blanks LCD
{
	LCDsendCommand(0x08);
}
void LCDvisible(void)		//Shows LCD
{
	LCDsendCommand(0x0C);
}
void LCDcursorLeft(uint8_t n)	//Moves cursor by n poisitions left
{
	for (uint8_t i=0;i<n;i++)
	{
		LCDsendCommand(0x10);
	}
}
void LCDcursorRight(uint8_t n)	//Moves cursor by n poisitions left
{
	for (uint8_t i=0;i<n;i++)
	{
		LCDsendCommand(0x14);
	}
}
