#include <LiquidCrystal.h>
#include <Arduino.h>

const String nameUkraine = "ЯРОСЛАВ"; // Use a String to store Ukrainian characters

//const char nameUkraine[] = {'Я', 'Р', 'О', 'С', 'Л', 'А', 'В'};
//const char nameUkraine[] = {'\u042F', '\u0420', '\u041E', '\u0421', '\u041B', '\u0410', '\u0412'};

int dotDuration = 300; // Change the value based on your requirement
int ledPin = 1;     // Change the pin based on your assignment
const char characters[] = {'\u0410', '\u0411', '\u0412', '\u0413', '\u0491', '\u0414', '\u0415', '\u0404', '\u0416', '\u0417', '\u0418', '\u0406', '\u0407', '\u0419', '\u041A', '\u041B', '\u041C', '\u041D', '\u041E', '\u041F', '\u0420', '\u0421', '\u0422', '\u0423', '\u0424', '\u0425', '\u0426', '\u0427', '\u0428', '\u0429', '\u042C', '\u042E', '\u042F'};

const char cyrillicToAsciiMap[] = {
    'A', // For '\u0410' (А)
    'B', // For '\u0411' (Б)
    'V', // For '\u0412' (В)
    'G', // For '\u0413' (Г)
    'G', // For '\u0491' (ґ, similar to Г)
    'D', // For '\u0414' (Д)
    'E', // For '\u0415' (Е)
    'E', // For '\u0404' (Є)
    'Zh', // For '\u0416' (Ж)
    'Z', // For '\u0417' (З)
    'I', // For '\u0418' (И)
    'I', // For '\u0406' (І)
    'Yi', // For '\u0407' (Ї)
    'Y', // For '\u0419' (Й)
    'K', // For '\u041A' (К)
    'L', // For '\u041B' (Л)
    'M', // For '\u041C' (М)
    'N', // For '\u041D' (Н)
    'O', // For '\u041E' (О)
    'P', // For '\u041F' (П)
    'R', // For '\u0420' (Р)
    'S', // For '\u0421' (С)
    'T', // For '\u0422' (Т)
    'U', // For '\u0423' (У)
    'F', // For '\u0424' (Ф)
    'Kh', // For '\u0425' (Х)
    'Ts', // For '\u0426' (Ц)
    'Ch', // For '\u0427' (Ч)
    'Sh', // For '\u0428' (Ш)
    'Shch', // For '\u0429' (Щ)
    ' ', // For '\u042C' (Ь, soft sign, no direct ASCII equivalent)
    'Yu', // For '\u042E' (Ю)
    'Ya' // For '\u042F' (Я)
};

// Define Morse code for Ukrainian alphabet
const String morseCodeUkraine[] = {
  ".-",   // А
  "-...", // Б
  ".--",  // В
  "....", // Г
  "--.",  // Ґ
  "-..",  // Д
  ".",    // Е
  "..-..",// Є
  "...-", // Ж
  "--..", // З
  "-.--", // И
  "..",   // І
  ".---.",// Ї
  ".---", // Й
  "-.-",  // К
  ".-..", // Л
  "--",   // М
  "-.",   // Н
  "---",  // О
  ".--.", // П
  ".-.",  // Р
  "...",  // С
  "-",    // Т
  "..-",  // У
  "..-.", // Ф
  "----", // Х
  "-.-.", // Ц 
  "---.", // Ч
  "--.-", // Ш
  "--.--",// Щ
  "-..-"  // Ь
  "..--", // Ю
  ".-.-", // Я
  };

template <typename K, typename V>
struct KeyValuePair {
  K key;
  V value;
  KeyValuePair* next;
};

template <typename K, typename V>
class HashMap {
public:
  HashMap() : tableSize(10), numElements(0) {
    table = new KeyValuePair<K, V>*[tableSize]();

    for (int i = 0; i < tableSize; i++) {
      table[i] = nullptr;
    }
  }

  ~HashMap() {
    Clear();
    delete[] table;
  }

  void insert(const K& key, const V& value) {
    int index = hash(key);
    Serial.print("Hash: ");
    Serial.println(index);
    KeyValuePair<K, V>* newPair = new KeyValuePair<K, V>{key, value, nullptr};

    if (table[index] == nullptr) {
      table[index] = newPair;
    } else {
      KeyValuePair<K, V>* current = table[index];
      while (current->next != nullptr) {
        current = current->next;
      }
      current->next = newPair;
    }

    numElements++;

    // Check if the load factor exceeds the threshold (e.g., 75%)
    if (static_cast<double>(numElements) / tableSize > LOAD_FACTOR_THRESHOLD) {
      resizeTable();
    }
  }

  V get(const K& key) const {
    int index = hash(key);
    KeyValuePair<K, V>* current = table[index];
    while (current != nullptr) {
      if (current->key == key) {
        return current->value;
      }
      current = current->next;
    }
    return V();
  }

  int getTableSize() {
    return tableSize;
  }

  int getNumElements() {
    return numElements;
  }

  void displayHashMap() {
    for (int i = 0; i < numElements; i++) {
      KeyValuePair<K, V>* current = table[i];
      Serial.print(i);
      Serial.print(" ");
      if (current == nullptr) {
        Serial.println(" element in map is null");
      }
      while (current != nullptr) {
        Serial.print("Key: ");
        Serial.print(current->key);
        Serial.print(", Value: ");
        Serial.println(current->value);
        current = current->next;
      }
    }
  }

private:
  static const double LOAD_FACTOR_THRESHOLD;

  int hash(const K& key) const {
     const int charactersValues[] = {
        0x0410, 0x0411, 0x0412, 0x0413, 0x0491, 0x0414, 0x0415, 0x0404, 
        0x0416, 0x0417, 0x0418, 0x0406, 0x0407, 0x0419, 0x041A, 0x041B, 
        0x041C, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 
        0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042C, 0x042E, 0x042F
    };

    int keyIntValue = static_cast<int>(key);
    Serial.print("Key Integer Value: ");
    Serial.println(keyIntValue, HEX); // Debugging

    keyIntValue = static_cast<int>(key);
    for (unsigned int i = 0; i < sizeof(charactersValues) / sizeof(charactersValues[0]); ++i) {
        if (charactersValues[i] == keyIntValue) {
            return i; // Return the index of the character as its hash value
        }
    }

    return -1; // Return -1 if character not found
}

  void resizeTable() {
    int oldSize = tableSize;
    tableSize *= 2;  // Double the size
    KeyValuePair<K, V>** newTable = new KeyValuePair<K, V>*[tableSize]();

    // Rehash all existing elements into the new table
    for (int i = 0; i < oldSize; i++) {
      KeyValuePair<K, V>* current = table[i];
      while (current != nullptr) {
        int newIndex = hash(current->key);
        KeyValuePair<K, V>* next = current->next;
        current->next = newTable[newIndex];
        newTable[newIndex] = current;
        current = next;
      }
    }

    delete[] table;
    table = newTable;
  }

  void Clear() {
    for (int i = 0; i < tableSize; i++) {
      KeyValuePair<K, V>* current = table[i];
      while (current != nullptr) {
        KeyValuePair<K, V>* next = current->next;
        delete current;
        current = next;
      }
      table[i] = nullptr;
    }
    numElements = 0;
  }

  int tableSize;
  int numElements;
  KeyValuePair<K, V>** table;
};

template <typename K, typename V>
const double HashMap<K, V>::LOAD_FACTOR_THRESHOLD = 0.75;

HashMap<char, String> morseCodeMap;
void initUkraineMorseCodeMap() {
  for (int i = 0; i < 32; i++) {
    morseCodeMap.insert(cyrillicToAsciiMap[i], morseCodeUkraine[i]);
  }
}

void setup()
{
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  initUkraineMorseCodeMap();
}

void loop()
{
  morseCodeMap.displayHashMap();
  Serial.println(morseCodeMap.getNumElements());
  Serial.println(morseCodeMap.getTableSize());
  // Serial.println(nameUkraine);
  // Serial.println("Number of Elements: " + String(nameUkraine.length()));
  // for (int i = 0; i < nameUkraine.length(); i++) {
  //   if (i % 2 != 0) {
  //   Serial.println(i);  
  //   convertAndShowMorseCode(nameUkraine[i]); // Use charAt to get characters from the String
  //   }
  // }
 
   delay(10000);
}

void convertAndShowMorseCode(char letter) {  
  Serial.println(letter);
  String moreCodeRepresentation = morseCodeMap.get(letter);

  if (moreCodeRepresentation != "") {
      Serial.println("Correct letter.");
      Serial.println(moreCodeRepresentation);
    showMorseCode(moreCodeRepresentation.c_str());
  } else {
    Serial.println("Invalid letter.");
  }
}

void showMorseCode(const char* morseCode) {
  for (int i = 0; i < sizeof(morseCode)/2; i++) {
    if (morseCode[i] == '.') {
      digitalWrite(ledPin, HIGH);  // LED ON for dot duration
      delay(dotDuration);
      digitalWrite(ledPin, LOW);   // LED OFF
    } else if (morseCode[i] == '-') {
      digitalWrite(ledPin, HIGH);  // LED ON for 3 times dot duration (dash)
      delay(dotDuration * 3);
      digitalWrite(ledPin, LOW);   // LED OFF
    }
  }

  // Gap between letters
  delay(dotDuration * 2);
}