// knihovny pro LCD přes I2C
#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// ------------------------------------------------ KEYPAD ------------------------------------------------ 
const byte numRows = 4; //number of rows on the keypad
const byte numCols = 3; //number of columns on the keypad

char keymap[numRows][numCols]= 
{
  {'1', '2', '3'}, 
  {'4', '5', '6'}, 
  {'7', '8', '9'},
  {'*', '0', '#'}
};

// http://learning.grobotronics.com/2013/07/using-a-3x4-keypad/
/*
Keypad Pin 1 ?> Arduino Pin 7
Keypad Pin 2 ?> Arduino Pin 5
Keypad Pin 3 ?> Arduino Pin 8
Keypad Pin 4 ?> Arduino Pin 2
Keypad Pin 5 ?> Arduino Pin 6
Keypad Pin 6 ?> Arduino Pin 3
Keypad Pin 7 ?> Arduino Pin 4
 */
byte rowPins[numRows] = {5, 4, 3, 2};   //connect to the row pinouts of the keypad
byte colPins[numCols]= {8, 7, 6};      //connect to the column pinouts of the keypad

Keypad keypad = Keypad(makeKeymap(keymap), rowPins, colPins, numRows, numCols);

char key_2[4] = {'A', 'B', 'C', '2'};
char key_3[4] = {'D', 'E', 'F', '3'};
char key_4[4] = {'G', 'H', 'I', '4'};
char key_5[4] = {'J', 'K', 'L', '5'};
char key_6[4] = {'M', 'N', 'O', '6'};
char key_7[5] = {'P', 'Q', 'R', 'S', '7'};
char key_8[4] = {'T', 'U', 'V', '8'};
char key_9[5] = {'W', 'X', 'Y', 'Z', '9'};

// ------------------------------------------------ LCD ------------------------------------------------ 
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x3F for a 16 chars and 2 line display

byte lineVertical[8] = {
    B00100,
    B00100,
    B00100,
    B00100,
    B00100,
    B00100,
    B00100,
    B00100
};

byte lineHorizontal[8] = {
    B00000,
    B00000,
    B00000,
    B11111,
    B00000,
    B00000,
    B00000,
    B00000
};

byte cornerUpperRigth[8] = {
    B00000,
    B00000,
    B00000,
    B11100,
    B00100,
    B00100,
    B00100,
    B00100
};

byte cornerUpperLeft[8] = {
    B00000,
    B00000,
    B00000,
    B00111,
    B00100,
    B00100,
    B00100,
    B00100
};

byte cornerLowerRigth[8] = {
    B00100,
    B00100,
    B00100,
    B11100,
    B00000,
    B00000,
    B00000,
    B00000
};

byte cornerLowerLeft[8] = {
    B00100,
    B00100,
    B00100,
    B00111,
    B00000,
    B00000,
    B00000,
    B00000
};


// ----------------------------------------------- GLOBALS ------------------------------------------------
const String password_1 = "A";
const String password_2 = "B";
const String password_3 = "C";
const String password_4 = "D";
byte pwdToCheck = 1;              // n-th password to check
char newKey = ' ';                // key to be added to input_password
char previousKey = ' ';           // previously pushed button
String input_password = ""; 
String prev_input = "";
bool blUpdateInput = false;
bool keyProcessed = false;
bool showMsg = true;
int sameBtnPushedCounter = 0;     // how many times the same button was pushed
const int pushLimit = 1200;       // time limit for pushing the same key
const int inputMaxLen = 18;
unsigned long pushDelay = millis();

// Prototyping
void waitForButton();
char blink();
void drawRectangle();
void initMsg();
void finalMsg();
void task1();
void task2();
void task3();
void task4();
boolean checkPwd(String pwdToCompare);


void setup() {
    Serial.begin(9600);
    //
    input_password.reserve(inputMaxLen);
    //
    lcd.init();
    lcd.createChar(0, lineVertical);
    lcd.createChar(1, lineHorizontal);
    lcd.createChar(2, cornerUpperRigth);
    lcd.createChar(3, cornerUpperLeft);
    lcd.createChar(4, cornerLowerRigth);
    lcd.createChar(5, cornerLowerLeft);
    lcd.clear();         
    lcd.noBacklight();
    //
    lcd.setCursor(2,1);
    lcd.print("MATRIX - GCAxxxx");
    drawRectangle();
    delay(2500);
    initMsg();
}

void loop() {
    // Display message with task
    if (showMsg) { // start true
        switch (pwdToCheck) { // start 1
        case 1:
            task1();
            break;
        case 2:
            task2();
            break;
        case 3:
            task3();
            break;
        case 4:
            task4();
            break;
        default:
            break;
        }
        showMsg = false;
    }

    // Read key
    char key = keypad.getKey();

    if (key) {
        if (key == '*') {
            // smazat posledni znak: https://arduino.stackexchange.com/questions/20174/arduino-subtracting-chars-from-strings
            if (input_password.length() > 0) {
                int lastIndex = input_password.length() - 1;
                input_password.remove(lastIndex);
            }
        } else if (key == '#') {
            // zkontrolovat heslo
            Serial.println(F("Checking password"));
            switch (pwdToCheck) {
            case 1:
                if (checkPwd(password_1)) {
                    pwdToCheck++;
                }
                break;
            case 2:
                if (checkPwd(password_2)) {
                    pwdToCheck++;
                }
                break;
            case 3:
                if (checkPwd(password_3)) {
                    pwdToCheck++;
                }
                break;
            case 4:
                if (checkPwd(password_4)) {
                    // final message
                    finalMsg();
                    delay(1000);
                    exit(0);
                }
                break;
            default:
                break;
            }
            showMsg = true;
        } else {
            // jine klavesy
            if (key == '1' || key == '0') {
                // klavesa jen s jednim znakem
                newKey = key;
                sameBtnPushedCounter = 0;
                blUpdateInput = true;
            } else {
                // klavesa s vice znaky
                if (key == previousKey) {
                    // Time to update 
                    if (millis() - pushDelay > pushLimit) {
                        sameBtnPushedCounter = 0;
                        previousKey = ' ';
                        blUpdateInput = true;
                    }else{
                        sameBtnPushedCounter++;
                        blUpdateInput = false;
                    }
                } else {
                    sameBtnPushedCounter = 0;
                }
                //
                if (key == '2') {
                    newKey = key_2[sameBtnPushedCounter % sizeof(key_2) / sizeof(key_2[0])];
                } else if (key == '3') {
                    newKey = key_3[sameBtnPushedCounter % sizeof(key_3) / sizeof(key_3[0])];
                } else if (key == '4') {
                    newKey = key_4[sameBtnPushedCounter % sizeof(key_4) / sizeof(key_4[0])];
                } else if (key == '5') {
                    newKey = key_5[sameBtnPushedCounter % sizeof(key_5) / sizeof(key_5[0])];
                } else if (key == '6') {
                    newKey = key_6[sameBtnPushedCounter % sizeof(key_6) / sizeof(key_6[0])];
                } else if (key == '7') {
                    newKey = key_7[sameBtnPushedCounter % sizeof(key_7) / sizeof(key_7[0])];
                } else if (key == '8') {
                    newKey = key_8[sameBtnPushedCounter % sizeof(key_8) / sizeof(key_8[0])];
                } else if (key == '9') {
                    newKey = key_9[sameBtnPushedCounter % sizeof(key_9) / sizeof(key_9[0])];
                }
                //
            }
            keyProcessed = true;
        }
        //
        pushDelay = millis();
        previousKey = key;
    }

    // Add key
    if (keyProcessed) {
        if (blUpdateInput) {
            input_password += newKey;
        } else {
            if (sameBtnPushedCounter != 0) {
                int lastIndex = input_password.length() - 1;
                input_password.remove(lastIndex);
                input_password += newKey;
            } else {
                input_password += newKey;
            }
        }
        keyProcessed = false;
    }

    // Update input
    if (prev_input != input_password) {
        Serial.print(F("Password: "));
        Serial.println(input_password);
        prev_input = input_password;
        // LCD
        lcd.setCursor(0, 3);
        lcd.print(">                   ");
        lcd.setCursor(1, 3);
        lcd.print(input_password);
    }

    // Max len
    if (input_password.length() > inputMaxLen) {
        Serial.println(F("Password too long"));
        input_password = "";
        // LCD
        lcd.setCursor(0, 3);
        lcd.print(">                   ");
        lcd.setCursor(1, 3);
    }
}

void initMsg() {
    lcd.clear();
    drawRectangle();
    lcd.setCursor(2,1);
    lcd.print("Pro pokracovani");
    lcd.setCursor(2,2);
    lcd.print("vzdy stiskni #");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Vedma te nyni prijme");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("VEDMA: Vim, ze jsi");
    lcd.setCursor(0, 1);
    lcd.print("Kacer. Vis, proc te");
    lcd.setCursor(0, 2);
    lcd.print("ke mne Morfeus");
    lcd.setCursor(0, 3);
    lcd.print("privedl?");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("VEDMA: Tak co si");
    lcd.setCursor(0, 1);
    lcd.print("myslis? Jsi");
    lcd.setCursor(0, 2);
    lcd.print("Vyvoleny?");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("VEDMA: Podivam se");
    lcd.setCursor(0, 1);
    lcd.print("na tebe.");
    lcd.setCursor(0, 2);
    lcd.print("Zajimave ...");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("VEDMA: Zkus mi");
    lcd.setCursor(0, 1);
    lcd.print("nejprve odpovedet");
    lcd.setCursor(0, 2);
    lcd.print("na par otazek.");
    waitForButton();
}

void finalMsg() {
    Serial.println(F("printing final message"));
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("VEDMA: Vyborne. Zda");
    lcd.setCursor(0, 1);
    lcd.print("se, ze bys opravdu");
    lcd.setCursor(0, 2);
    lcd.print("mohl byt On!");
    lcd.setCursor(0, 3);
    lcd.print("Nyni se vrat ...");
    waitForButton();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Cislo, ktere hledas,");
    lcd.setCursor(0, 1);
    lcd.print("je 1473.");
}

void task1() {
    Serial.println(F("printing task 1"));
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("OTAZKA C.1: Co v");
    lcd.setCursor(0, 1);
    lcd.print("cestine znamena");
    lcd.setCursor(0, 2);
    lcd.print("slovo na skrini?");
    lcd.setCursor(0, 3);
    lcd.print(">");
}

void task2() {
    Serial.println(F("printing task 2"));
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("OTAZKA C.2: Co v");
    lcd.setCursor(0, 1);
    lcd.print("cestine znamena");
    lcd.setCursor(0, 2);
    lcd.print("slovo na skrini?");
    lcd.setCursor(0, 3);
    lcd.print(">");
}

void task3() {
    Serial.println(F("printing task 3"));
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("OTAZKA C.3: Co v");
    lcd.setCursor(0, 1);
    lcd.print("cestine znamena");
    lcd.setCursor(0, 2);
    lcd.print("slovo na skrini?");
    lcd.setCursor(0, 3);
    lcd.print(">");
}

void task4() {
    Serial.println(F("printing task 4"));
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("OTAZKA C.4: Co v");
    lcd.setCursor(0, 1);
    lcd.print("cestine znamena");
    lcd.setCursor(0, 2);
    lcd.print("slovo na skrini?");
    lcd.setCursor(0, 3);
    lcd.print(">");
}

boolean checkPwd(String pwdToCompare) {
    bool result = false;
    if (input_password.length() < 1) {
        return result;
    }
    lcd.clear();
    drawRectangle();
    if (input_password == pwdToCompare) {
        lcd.setCursor(6, 1);
        lcd.print("SPRAVNE");
        result = true;
    } else {
        lcd.setCursor(6, 1);
        lcd.print("SPATNE");
    }
    delay(2000);
    input_password = ""; // reset the input password
    lcd.clear();
    return result;
}

void drawRectangle() {
    lcd.setCursor(0,0);
    lcd.write(byte(3));
    lcd.setCursor(19,0);
    lcd.write(byte(2));
    lcd.setCursor(0,3);
    lcd.write(byte(5));
    lcd.setCursor(19,3);
    lcd.write(byte(4));
    for (int i=1;i<19;i++){
        lcd.setCursor(i,0);
        lcd.write(byte(1));
        lcd.setCursor(i,3);
        lcd.write(byte(1));
    }
    lcd.setCursor(0,1);
    lcd.write(byte(0));
    lcd.setCursor(19,1);
    lcd.write(byte(0));
    lcd.setCursor(0,2);
    lcd.write(byte(0));
    lcd.setCursor(19,2);
    lcd.write(byte(0));
}

void waitForButton() {
    delay(500);
    while (1) {
        char key = keypad.getKey();
        if (key == '#') {
            return;
        }
    }
}

char blink() {
    unsigned long previousMillis = 0;
    const long interval = 500;
    bool blinkOn = true;
    while (1) {
        char key = keypad.getKey();
        if (key) {
            lcd.noCursor();
            return key;
        }
        unsigned long currentMillis = millis();
        if (currentMillis - previousMillis >= interval) {
            previousMillis = currentMillis;
            if (blinkOn) {
                lcd.noCursor();
                blinkOn = false;
            } else {
                lcd.cursor();
                blinkOn = true;
            }
        }
    }
}