////////////////////////////////////////////////////////////////////////////////
//                                                                            //
// Programname: HSA_LCD_Shield - Code                                         //
// Date: 16.11.2018                                                           //
// Description: Code-File of the LCD-Shield, which was designed and built in  //
//              the Modul "Elektronikdesign". In this file you can find the   //
//              code of all functions mentioned in the Header-File of the     //
//              class.                                                        //
//                                                                            //
// Author: Tobias Müller, M. Eng.                                             //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
////////////////////          Include Header-File           ////////////////////
////////////////////////////////////////////////////////////////////////////////

#include "HSA_LCD_Shield.h"

////////////////////////////////////////////////////////////////////////////////
////////////////////          Protected Functions           ////////////////////
////////////////////////////////////////////////////////////////////////////////

////////////////////           GPIO Configuration           ////////////////////

bool HSA_LCD_Shield::__gpioConfig(void) {

  // Save Pin configuration, depending of LCD-Shield version
  if(this->__version == LCD_VERSION_5) {

    // Save Pin configuration for buttons
    this->__buttonUp = BUTTON_UP_V5;
    this->__buttonRight = BUTTON_RIGHT_V5;
    this->__buttonDown = BUTTON_DOWN_V5;
    this->__buttonLeft = BUTTON_LEFT_V5;

    // Save Pin configuration for LEDs
    this->__ledGreen = LED_GREEN_V5;
    this->__ledRed = LED_RED_V5;

    // Save Pin configuration for LCD-Backlight
    this->__lcdBacklight = LCD_BACKLIGHT_V5;

  }

  else if(this->__version == LCD_VERSION_6) {

    // Save Pin configuration for buttons
    this->__buttonUp = BUTTON_UP_V6;
    this->__buttonRight = BUTTON_RIGHT_V6;
    this->__buttonDown = BUTTON_DOWN_V6;
    this->__buttonLeft = BUTTON_LEFT_V6;

    // Save Pin configuration for LEDs
    this->__ledGreen = LED_GREEN_V6;
    this->__ledRed = LED_RED_V6;

    // Save Pin configuration for LCD-Backlight
    this->__lcdBacklight = LCD_BACKLIGHT_V6;

  }

  // If LCD-Shield version is unknown, exit function with error
  else return false;

  // Check config-value and setup the LEDs, depending of config-value
  if(strcmp(this->__config,CONFIG_L1B0) == false || 
    strcmp(this->__config,CONFIG_L1B1) == false) {
    
    // Setup LEDs
    pinMode(this->__ledGreen,OUTPUT);
    pinMode(this->__ledRed,OUTPUT);
    digitalWrite(this->__ledGreen,HIGH);
    digitalWrite(this->__ledRed,HIGH);

    // Store the value "true", if LEDs are configured
    this->__leds = true;
    
  }
  else {
    
    // Store the value "false", if LEDs are not configured
    this->__leds = false;

  }

  // Check config-value and setup the Buttons depending of config-value
  if(strcmp(this->__config,CONFIG_L0B1) == false || 
    strcmp(this->__config,CONFIG_L1B1) == false) {
    
    // Setup Buttons
    pinMode(this->__buttonUp,INPUT);
    pinMode(this->__buttonRight,INPUT);
    pinMode(this->__buttonDown,INPUT);
    pinMode(this->__buttonLeft,INPUT);

    // Store the value "true", if buttons are configured
    this->__buttons = true;

  }
  else {
    
    // Store the value "false", if buttons are not configured
    this->__buttons = false;
    
  }

  // If config-value is unknown, quit configuration, otherwise configure Display
  if(strcmp(this->__config,CONFIG_L1B1) != false &&
    strcmp(this->__config,CONFIG_L0B1) != false &&
    strcmp(this->__config,CONFIG_L1B0) != false &&
    strcmp(this->__config,CONFIG_L0B0) != false) return false;

  else {

    // Setup LCD-Backlight
    pinMode(this->__lcdBacklight,OUTPUT);

    // Initialize I²C as Master
    Wire.begin();

    // Define an array of Bytes for configure the Display
    byte buffer[] = {CONTROL_BYTE_CB,
                     LCD_PARA_FUNC1,
                     LCD_PARA_EXT_FUNC,
                     LCD_PARA_ENTRY_MODE,
                     LCD_PARA_BIAS_SET,
                     LCD_PARA_FUNC2,
                     LCD_PARA_INT_OSC,
                     LCD_PARA_FOL_CON,
                     LCD_PARA_POW_CON,
                     LCD_PARA_CONTRAST,
                     LCD_PARA_FUNC3,
                     LCD_PARA_DIS_CON,
                     LCD_PARA_CLR_DIS};

    // Send array of bytes
    _sendMessage(buffer, sizeof(buffer));

    // Exit function with positive feedback
    return true;

  }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////           Privat Functions             ////////////////////
////////////////////////////////////////////////////////////////////////////////

////////////////////               Send Message             ////////////////////

void HSA_LCD_Shield::_sendMessage(byte* bytes, byte sizeBytes) {

  // Small timeout before access the I²C-Bus
  delayMicroseconds(DELAY_TRANS_US);

  // Start I²C transmission
  Wire.beginTransmission(this->__i2cAddress);

  // Send an array of Bytes over I²C, depending of its size
  for(byte i = 0x00; i < sizeBytes; i++) Wire.write(bytes[i]);

  // Stop I²C transmission
  Wire.endTransmission();

  // Small timeout after access the I²C-Bus
  delayMicroseconds(DELAY_TRANS_US);

  // Exit function
  return;

}

////////////////////////////////////////////////////////////////////////////////
////////////////////           Public Functions             ////////////////////
////////////////////////////////////////////////////////////////////////////////

////////////////////               Constructor              ////////////////////

HSA_LCD_Shield::HSA_LCD_Shield(byte version, const char config[CONFIG_SIZE]) {
  
  // Save LCD-Shield version
  this->__version = version;

  // Save the configuration of Buttons/LEDs
  strcpy(this->__config,config);

  // Exit Constructor
  return;

}

HSA_LCD_Shield::HSA_LCD_Shield(byte version) {

  // Save LCD-Shield version
  this->__version = version;

  // Save the configuration of Buttons/LEDs
  strcpy(this->__config,CONFIG_L1B1);

  // Exit Constructor
  return;

}

HSA_LCD_Shield::HSA_LCD_Shield(const char config[CONFIG_SIZE]){

  // Save LCD-Shield version
  this->__version = LCD_VERSION_6;

  // Save the configuration of Buttons/LEDs
  strcpy(this->__config,config);

  // Exit Constructor
  return;

}

HSA_LCD_Shield::HSA_LCD_Shield(){

  // Save LCD-Shield version
  this->__version = LCD_VERSION_6;

  // Save the configuration of Buttons/LEDs
  strcpy(this->__config,CONFIG_L1B1);

  // Exit Constructor
  return;

}

////////////////////             Deconstructor              ////////////////////

HSA_LCD_Shield::~HSA_LCD_Shield(void) {

  // Exit function
  return;

}

////////////////////          Start Configuration           ////////////////////

bool HSA_LCD_Shield::begin(void) {

  // Save standard I²C-Address of LC-Display
  this->__i2cAddress = I2C_ADDRESS;

  // Configure GPIOs and exit
  return __gpioConfig();
  
}

bool HSA_LCD_Shield::begin(byte address) {

  // Save I²C-Address of LC-Display
  this->__i2cAddress = address;

  // Configure GPIOs and exit
  return __gpioConfig();
  
}

////////////////////             LCD-Backlight              ////////////////////

void HSA_LCD_Shield::lcdBacklight(bool value) {

  // Turning on/off LCD-Backlight
  digitalWrite(this->__lcdBacklight,value);

  // Exit function
  return;

}

////////////////////             Clear Display              ////////////////////

void HSA_LCD_Shield::clearDisplay(void) {

  // Define an array of Bytes for clearing the Display
  byte buffer[] = {CONTROL_BYTE_CB,
                   LCD_PARA_CLR_DIS};
  
  // Send array of bytes
  _sendMessage(buffer, sizeof(buffer));

  // Exit function
  return;

}

///////////////////             Return Version              ////////////////////

byte HSA_LCD_Shield::returnVersion(void) {

  // Exit function and return the configured Version for the LCD-Shield
  return this->__version;

}

////////////////////             Return Config              ////////////////////

char* HSA_LCD_Shield::returnConfig(void) {

  // Exit function and return the configuration value
  return this->__config;

}

////////////////////            Return I²C-Address          ////////////////////

byte HSA_LCD_Shield::returnAddress(void){

  // Exit function and return the value of the I²C-Address
  return this->__i2cAddress;

}

////////////////////               Control LEDs             ////////////////////

bool HSA_LCD_Shield::controlLED(char ledPin, bool state) {

  // Checks, whether LED-Pins are configured
  if(this->__leds) {

    // Checks, whether LED-Color is known and control LED
    if(ledPin == LED_GREEN) {
      digitalWrite(this->__ledGreen,!state); 
      return true;
    }

    if(ledPin == LED_RED) {
      digitalWrite(this->__ledRed,!state); 
      return true; 
    }
  }
  
  // Exit function with error, if LED-Pins are not configured or LED-Color is 
  // unknown
  return false;

}

////////////////////               Get LED state            ////////////////////

bool HSA_LCD_Shield::getLED(char ledPin) {

  // Exit function and return the value of chosen LED
  if(ledPin == LED_GREEN) return !digitalRead(this->__ledGreen);
  else if(ledPin == LED_RED) return !digitalRead(this->__ledRed);
  else return false;

}

////////////////////          Get the pressed Button        ////////////////////

byte HSA_LCD_Shield::getButton() {

  // return 1, if only button "Up" is pressed and configured
  if(digitalRead(this->__buttonUp) &&
     !digitalRead(this->__buttonRight) &&
     !digitalRead(this->__buttonDown) &&
     !digitalRead(this->__buttonLeft) &&
     this->__buttons)
     return BUTTON_UP;
  
  // return 2, if only button "Right" is pressed and configured
  if(!digitalRead(this->__buttonUp) &&
     digitalRead(this->__buttonRight) &&
     !digitalRead(this->__buttonDown) &&
     !digitalRead(this->__buttonLeft) &&
     this->__buttons)
     return BUTTON_RIGHT;

  // return 3, if only button "Down" is pressed and configured
  if(!digitalRead(this->__buttonUp) &&
     !digitalRead(this->__buttonRight) &&
     digitalRead(this->__buttonDown) &&
     !digitalRead(this->__buttonLeft) &&
     this->__buttons)
     return BUTTON_DOWN;

  // return 4, if only button "Left" is pressed and configured
  if(!digitalRead(this->__buttonUp) &&
     !digitalRead(this->__buttonRight) &&
     !digitalRead(this->__buttonDown) &&
     digitalRead(this->__buttonLeft) &&
     this->__buttons)
     return BUTTON_LEFT;

  // return false, if no button or more than one button is pressed or
  // buttons are not configured
  return false;

}

////////////////////                 Write Row              ////////////////////

bool HSA_LCD_Shield::writeRow(byte row, const char* text) {

  // If chosen Row does not exist, exit function with error
  if(!(row >= 0x01 && row <= 0x04)) return false;

  // Define buffer for test Message
  byte buffer[ARRAY_SIZE_ROW] = ""; 

  // Define variable to store a boolean value, if a control-byte was found
  bool controlByte = false; 

  // Create data-array with message and send the message at the end
  for(byte i = 0x00; i < ARRAY_SIZE_ROW; i++) {

    // Set control-byte for changing parameter in the first iteration
    if(i == 0x00) buffer[i] = CONTROL_BYTE_CB;

    // Change the cursor position to row in the second iteration.
    if(i == 0x01 && row == 0x01) buffer[i] = LCD_PARA_DIS_ROW1;
    if(i == 0x01 && row == 0x02) buffer[i] = LCD_PARA_DIS_ROW2;
    if(i == 0x01 && row == 0x03) buffer[i] = LCD_PARA_DIS_ROW3;
    if(i == 0x01 && row == 0x04) buffer[i] = LCD_PARA_DIS_ROW4;

    // Set control-byte for writing to the display in the third iteration
    if(i == 0x02) buffer[i] = CONTROL_BYTE_DCB;

    // Copy text into the buffer between iteration 2 to 13
    if(i > 0x02 && i < ARRAY_SIZE_ROW) {

      // If no control-byte was found, copy byte into buffer
      if(!controlByte) {

        // Check byte for control-byte
        if(isControl(text[i - 0x03])) controlByte = true;

        // if not, copy byte into buffer
        else buffer[i] = text[i - 0x03];
      }

      // If control-byte was found, fill message with free space
      if(controlByte) {
        
        // Fill Buffer with free space
        buffer[i] = FREE_SPACE;
      }
    }
  }

  // Send message 
  _sendMessage(buffer,sizeof(buffer));

  // Exit function with success
  return true;

}

bool HSA_LCD_Shield::writeRow(const char* text) {

    // Define buffer for test Message
  char buffer[ARRAY_SIZE_ROW] = ""; 

  // Define variable to store the actual row position
  byte rowPosition = LCD_PARA_DIS_ROW1; 

  // Define variable, which count the copied bytes
  byte countBytes = 0x00;

  // Create data-array with messages for 4 rows and send the messages
  for(byte i = 0x00; i < 0x28; i++){

    // If the position of the row is not selected, skip following code
    if(rowPosition == false) continue; 

    // If first row is selected
    if(rowPosition == LCD_PARA_DIS_ROW1) {

      // If counted bytes has not reach max value, copy byte into buffer
      if(countBytes < 0x0A) buffer[countBytes] = text[i];

      // Check, if copied byte was control-byte
      if(isControl(text[i])) {
        
        // Check if control-byte was \n or \r or end of string, 
        // set counted bytes to max value
        if(text[i] == BACKSLASH_N || 
           text[i] == BACKSLASH_R ||
           text[i] == STRING_END) 
           countBytes = 0x0A;

        // Check if control-byte was end of the string, deactivate row position
        if(text[i] == STRING_END) rowPosition = false;
        
      }

      // Otherwise increase counted bytes
      else countBytes++;

      // If count bytes is max value
      if(countBytes == 0x0A) {

        // Reset count bytes 
        countBytes = 0x00;

        // write first row
        writeRow(0x01, buffer);

        // If row position is deactivated
        if(rowPosition == false) {

          // clear following rows
          writeRow(0x02, &text[i]);
          writeRow(0x03, &text[i]);
          writeRow(0x04, &text[i]);

        }
        
        // Otherwise set row position the row 2
        else rowPosition = LCD_PARA_DIS_ROW2;

        // skip the rest of the code in this iteration
        continue;

      }
    }

    // If second row is selected
    if(rowPosition == LCD_PARA_DIS_ROW2) {

      // If counted bytes has not reach max value, copy byte into buffer
      if(countBytes < 0x0A) buffer[countBytes] = text[i];

      // Check, if copied byte was control-byte
      if(isControl(text[i])) {
        
        // Check if control-byte was \n or \r or end of string, 
        // set counted bytes to max value
        if(text[i] == BACKSLASH_N || 
           text[i] == BACKSLASH_R ||
           text[i] == STRING_END) 
           countBytes = 0x0A;

        // Check if control-byte was end of the string, deactivate row position
        if(text[i] == STRING_END) rowPosition = false;
        
      }

      // Otherwise increase counted bytes
      else countBytes++;

      // If count bytes is max value
      if(countBytes == 0x0A) {

        // Reset count bytes 
        countBytes = 0x00;

        // write second row
        writeRow(0x02, buffer);

        // If row position is deactivated
        if(rowPosition == false) {

          // clear following rows
          writeRow(0x03, &text[i]);
          writeRow(0x04, &text[i]);

        }
        
        // Otherwise set row position the row 3
        else rowPosition = LCD_PARA_DIS_ROW3;

        // skip the rest of the code in this iteration
        continue;

      }
    }

    // If third row is selected
    if(rowPosition == LCD_PARA_DIS_ROW3) {

      // If counted bytes has not reach max value, copy byte into buffer
      if(countBytes < 0x0A) buffer[countBytes] = text[i];

      // Check, if copied byte was control-byte
      if(isControl(text[i])) {
        
        // Check if control-byte was \n or \r or end of string, 
        // set counted bytes to max value
        if(text[i] == BACKSLASH_N || 
           text[i] == BACKSLASH_R ||
           text[i] == STRING_END) 
           countBytes = 0x0A;

        // Check if control-byte was end of the string, deactivate row position
        if(text[i] == STRING_END) rowPosition = false;
        
      }

      // Otherwise increase counted bytes
      else countBytes++;

      // If count bytes is max value
      if(countBytes == 0x0A) {

        // Reset count bytes 
        countBytes = 0x00;

        // write third row
        writeRow(0x03, buffer);

        // If row position is deactivated, clear following row
        if(rowPosition == false) writeRow(0x04, &text[i]);

        // Otherwise set row position the row 4
        else rowPosition = LCD_PARA_DIS_ROW4;

        // skip the rest of the code in this iteration
        continue;

      }
    }

    // If fourth row is selected
    if(rowPosition == LCD_PARA_DIS_ROW4) {

      // If counted bytes has not reach max value, copy byte into buffer
      if(countBytes < 0x0A) buffer[countBytes] = text[i];

      // Check, if copied byte was control-byte
      if(isControl(text[i])) {
        
        // Check if control-byte was \n or \r or end of string, 
        // set counted bytes to max value
        if(text[i] == BACKSLASH_N || 
           text[i] == BACKSLASH_R ||
           text[i] == STRING_END) 
           countBytes = 0x0A;

        // Check if control-byte was end of the string, deactivate row position
        if(text[i] == STRING_END) rowPosition = false;
        
      }

      // Otherwise increase counted bytes
      else countBytes++;

      // If count bytes is max value
      if(countBytes == 0x0A) {

        // Reset count bytes 
        countBytes = 0x00;

        // write fourth row
        writeRow(0x04, buffer);

        // If row position is not deactivated, deaktivate row position
        if(rowPosition != false) rowPosition = false;

        // skip the rest of the code in this iteration
        continue;

      }
    }
  }

  // Exit function
  return true;

}

////////////////////             Write Position XY          ////////////////////

bool HSA_LCD_Shield::writeXY(byte row, byte column, const char* text) {

  // If chosen Row does not exist, exit function with error
  if(!(row >= 0x01 && row <= 0x04)) return false;

  // If chosen Column does not exist, exit function with error
  if(!(column >= 0x01 && column <= 0x0A)) return false;

  // create variable to store count of chars
  byte count = 0x00;

  // Count chars inside of text
  for(byte i = 0x00; i < 0x0A && !isControl(text[i]); i++) count++;

  // Define buffer for text Message
  byte buffer[ARRAY_SIZE_ROW - (0x0A - count)] = ""; 

  // Create data-array with message and send the message at the end
  for(byte i = 0x00; i < sizeof(buffer); i++) {

    // Set control-byte for changing parameter in the first iteration
    if(i == 0x00) buffer[i] = CONTROL_BYTE_CB;

    // Change the cursor position to row/column in the second iteration.
    if(i == 0x01 && row == 0x01) buffer[i] = LCD_PARA_DIS_ROW1 | (column - 0x01);
    if(i == 0x01 && row == 0x02) buffer[i] = LCD_PARA_DIS_ROW2 | (column - 0x01);
    if(i == 0x01 && row == 0x03) buffer[i] = LCD_PARA_DIS_ROW3 | (column - 0x01);
    if(i == 0x01 && row == 0x04) buffer[i] = LCD_PARA_DIS_ROW4 | (column - 0x01);

    // Set control-byte for writing to the display
     if(i == 0x02) buffer[i] = CONTROL_BYTE_DCB;

    // Copy text into the buffer between iteration 2 to 13
    if(i >= 0x03 && i < sizeof(buffer)) buffer[i] = text[i - 0x03];
  }

  // Send message 
  _sendMessage(buffer,sizeof(buffer));

  // Exit function with success
  return true;

}