/*
  Automatic Turntable
  Automatic turntable for 3D scanning using photogrammetry
  https://palmacas.com/mesa-automatica
*/
#include <Wire.h>
#include <U8g2lib.h>
#include <EEPROM.h>
// SSD1306 constructor using I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0);
// Rotary encoder pins and varibles
#define CLK 2
#define DAT 3
#define BTN 4
int relative_position = 0;
unsigned long last_press = 0;
// Buzzer pin
#define BUZ 9
//Stepper drivers pins
#define STEP1_EN A3
#define STEP1 7
#define DIR1 8
#define STEP2_EN A2
#define STEP2 13
#define DIR2 12
#define CW HIGH
#define CCW LOW
// Menu structure
int menu_index = 0;
int menu_items = 0;
int main_lvl = -1;
int step_lvl = -1;
int auto_lvl = -1;
int man_lvl = -1;
int flag;
// Variables
char *project = "Automatic Turntable";
char *version = "v1.0";
char *author = "palmacas 2025";
char *main_menu[] = {"Steps", "Auto Rotation", "Manual", "Settings"};
char *step_menu[] = {"Degrees:", "Delay:", "Rotations:", "Start", "Back", "Hola", "Prueba"};
char *auto_menu[] = {"Rotations:", "Time:", "RPM:", "Start", "Back"};
char *manual_menu[] = {"Degrees:", "Clockwise", "Counterclockwise", "Back"};
char *settings_menu[] = {"Sound", "Save"};
int step_degree = 35;
int step_delay = 1000;
int step_rotations = 2;
int auto_rotations = 12;
int auto_time = 234;
int auto_rpm = 180;
int manual_degree;
int min_deg = 1;
int max_deg = 360;
int min_delay = 1;
int max_delay = 60;
int min_rot = 1;
int max_rot = 10;
int min_time = 1;
int max_time = 60;
int min_rpm = 1;
int max_rpm = 60;
void setup() {
  // Starts serial port
  Serial.begin(115200);
  // Project data
  Serial.println(project);
  Serial.println(version);
  Serial.println(author);
  // Project data
  display.begin();
  display.firstPage();
  do {
    display.setFont(u8g2_font_t0_15_tr);
    display.drawStr(64 - display.getStrWidth(project)/2, 15, project);
    display.drawStr(64 - display.getStrWidth(version)/2, 45, version);
    display.drawStr(64 - display.getStrWidth(author)/2, 60, author);
  } while (display.nextPage());
  // Startup sound
  pinMode(BUZ, OUTPUT);
  tone(BUZ, 1568, 100); // G6
  delay(250);
  tone(BUZ, 1047, 100); // C6
  delay(250);
  tone(BUZ, 1568, 100); // G6
  delay(100);
  noTone(BUZ);
  delay(100);
  // Splash screen
  /*display.firstPage();
  do {
    display.drawXBM(0, 0, 128, 64, splash_screen);
  } while (display.nextPage());*/
  delay(1000);
  // Encoder pins
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DAT, INPUT_PULLUP);
  pinMode(BTN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), isr, FALLING);
  // Stepper driver pins
  pinMode(STEP1_EN, OUTPUT);
  pinMode(STEP1, OUTPUT);
  pinMode(DIR1, OUTPUT);
  digitalWrite(STEP1_EN, HIGH);
  // Reads previous settings
  /*EEPROM.get(1, step_degree);
  EEPROM.get(3, step_delay);
  EEPROM.get(5, step_rotations);
  EEPROM.get(7, auto_rotations);
  EEPROM.get(9, auto_time);
  EEPROM.get(11, auto_rpm);
  EEPROM.get(13, manual_degree);*/
}
void loop() {
  switch (main_lvl) {
    case -1:
      menu_items = sizeof(step_menu)/sizeof(step_menu[0]);
      Serial.println(menu_items);
      relative_position = relativePositionLimit(relative_position, menu_items - 1);
      menu_index = relative_position;
      Serial.println(menu_index);
      char step_degree_char[] = {"1000"};
      char step_delay_char[] = {"1000"};
      char step_rotations_char[] = {"1000"};
      itoa(step_degree, step_degree_char, 10);
      itoa(step_delay, step_delay_char, 10);
      itoa(step_rotations, step_rotations_char, 10);
      char *step_values[] = {step_degree_char, step_delay_char, step_rotations_char, "", "", "", ""};
      showMainMenu(step_menu, step_values, menu_items, menu_index);
      
      if (readButton() == 1) {
        main_lvl = relative_position;
        relative_position = 0;
      }
      break;
    /*case 0:
      while (main_lvl == 0) {
        StepMenu();
      }
      relative_position = 0;
      break;
    case 1:
      while (main_lvl == 1) {
        AutoMenu();
      }
      relative_position = 1;
      break;
    case 2:
      while (main_lvl == 2) {
        ManualMenu();
      }
      relative_position = 2;*/
  }
  delay(2);
}
void isr() {
  static unsigned long last_time = 0;
  if (millis() - last_time > 5) {
    if (!digitalRead(DAT)) {
      relative_position --;
    }
    else {
      relative_position ++;
    }
  }
  last_time = millis();
}
void StepMenu() {
  delay(200);
  switch (step_lvl) {
    case -1:
      flag = 1;
      menu_items = 4;
      showMenu(step_menu, step_degree, step_delay, step_rotations);
      relativePositionLimit(0, menu_items);
      menu_index = relative_position;
      if (readButton() == 1) {
        step_lvl = relative_position;
        flag = 0;
      }
      break;
    case 0:
      if (flag == 0) {
        relative_position = step_degree;
        flag = 1;
      }
      showMenu(step_menu, step_degree, step_delay, step_rotations);
      relativePositionLimit(min_deg, max_deg);
      step_degree = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(1, step_degree);
        relative_position = step_lvl;
        step_lvl = -1;
      }
      break;
    case 1:
      if (flag == 0) {
        relative_position = step_delay;
        flag = 1;
      }
      showMenu(step_menu, step_degree, step_delay, step_rotations);
      relativePositionLimit(min_delay, max_delay);
      step_delay = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(3, step_delay);
        relative_position = step_lvl;
        step_lvl = -1;
      }
      break;
    case 2:
      if (flag == 0) {
        relative_position = step_rotations;
        flag = 1;
      }
      showMenu(step_menu, step_degree, step_delay, step_rotations);
      relativePositionLimit(min_rot, max_rot);
      step_rotations = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(5, step_rotations);
        relative_position = step_lvl;
        step_lvl = -1;
      }
      break;
    case 3:
      step_menu[4] = "STOP";
      showMenu(step_menu, step_degree, step_delay, step_rotations);
      moveMotor(CW, 10, 500);
      step_menu[4] = "START";
      relative_position = step_lvl;
      step_lvl = -1;
      break;
    case 4:
      step_lvl = -1;
      main_lvl = -1;
      break;
  }
}
void AutoMenu() {
  switch (auto_lvl) {
    case -1:
      flag = 1;
      menu_items = 4;
      showMenu(auto_menu, auto_rotations, auto_time, auto_rpm);
      relativePositionLimit(0, menu_items);
      menu_index = relative_position;
      if (readButton() == 1) {
        auto_lvl = relative_position;
        flag = 0;
      }
      break;
    case 0:
      if (flag == 0) {
        relative_position = auto_rotations;
        flag = 1;
      }
      showMenu(auto_menu, auto_rotations, auto_time, auto_rpm);
      relativePositionLimit(min_rot, max_rot);
      auto_rotations = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(7, auto_rotations);
        relative_position = auto_lvl;
        auto_lvl = -1;
      }
      break;
    case 1:
      if (flag == 0) {
        relative_position = auto_time;
        flag = 1;
      }
      showMenu(auto_menu, auto_rotations, auto_time, auto_rpm);
      relativePositionLimit(min_time, max_time);
      auto_time = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(9, auto_time);
        relative_position = auto_lvl;
        auto_lvl = -1;
      }
      break;
    case 2:
      if (flag == 0) {
        relative_position = auto_rpm;
        flag = 1;
      }
      showMenu(auto_menu, auto_rotations, auto_time, auto_rpm);
      relativePositionLimit(min_rpm, max_rpm);
      auto_rpm = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(11, auto_rpm);
        relative_position = auto_lvl;
        auto_lvl = -1;
      }
      break;
    case 3:
      auto_menu[4] = "STOP";
      showMenu(auto_menu, auto_rotations, auto_time, auto_rpm);
      moveMotor(CCW, 10, 500);
      auto_menu[4] = "START";
      relative_position = auto_lvl;
      auto_lvl = -1;
      break;
    case 4:
      auto_lvl = -1;
      main_lvl = -1;
      break;
  }
}
void ManualMenu() {
  switch (man_lvl) {
    case -1:
      flag = 1;
      menu_items = 3;
      //showManualMenu();
      relativePositionLimit(0, menu_items);
      menu_index = relative_position;
      if (readButton() == 1) {
        man_lvl = relative_position;
        flag = 0;
      }
      break;
    case 0:
      if (flag == 0) {
        relative_position = manual_degree;
        flag = 1;
      }
      //showManualMenu();
      relativePositionLimit(min_deg, max_deg);
      manual_degree = relative_position;
      if (readButton() == 1) {
        //EEPROM.put(13, manual_degree);
        relative_position = man_lvl;
        man_lvl = -1;
      }
      break;
    case 1:
      manual_menu[2] = "STOP";
      //showManualMenu();
      moveMotor(CW, manual_degree, 500);
      manual_menu[2] = "CLOCKWISE";
      man_lvl = -1;
      break;
    case 2:
      manual_menu[3] = "STOP";
      //showManualMenu();
      moveMotor(CCW, manual_degree, 500);
      manual_menu[3] = "ANTICLOCKWISE";
      man_lvl = -1;
      break;
    case 3:
      man_lvl = -1;
      main_lvl = -1;
      break;
  }
}
// Allows loops on the menus
int relativePositionLimit(int position, int max_position) {
  int rel_position;
  if (position < 0) { rel_position = max_position; }
  else if (position > max_position) { rel_position = 0; }
  else { rel_position = position; }
  return rel_position;
}
// Displays main menu
void showMainMenu(char *menu[], char *values[], int num_items, int index) {
  int menu_div = 64.0/num_items + 0.5;
  int index_offset = 0;
  if (index >= 4){
    index_offset = index - 3;
    Serial.println(index_offset);
  }
  int entero1 = 234;
  display.firstPage();
  do {
    // Draws the whole menu
    display.setFontMode(1);
    //display.setFont(u8g2_font_9x15_tr);
    display.drawStr(2, 13, menu[0 + index_offset]);
    display.drawStr(4 + display.getStrWidth(menu[0 + index_offset]), 13, entero1);
    display.drawStr(2, 29, menu[1 + index_offset]);
    display.drawStr(4 + display.getStrWidth(menu[1 + index_offset]), 29, values[1 + index_offset]);
    display.drawStr(2, 45, menu[2 + index_offset]);
    display.drawStr(4 + display.getStrWidth(menu[2 + index_offset]), 45, values[2 + index_offset]);
    display.drawStr(2, 61, menu[3 + index_offset]);
    display.drawStr(4 + display.getStrWidth(menu[3 + index_offset]), 61, values[3 + index_offset]);    
    // Draws the scrollbar
    display.setDrawColor(1);
    display.drawLine(126, 0, 126, 63);
    display.drawBox(125, menu_div * index , 3, menu_div);
    // Draws the selected item
    display.drawRBox(0, ((index - index_offset) * 16), 123, 16, 3);
    display.setDrawColor(2);
    display.drawStr(2, ((index - index_offset) * 16) + 13, menu[index]);
    display.drawStr(4 + display.getStrWidth(menu[index]), ((index - index_offset) * 16) + 13, values[index]);
  } while (display.nextPage());
}
// Displays menus
void showMenu(char *menu[], int16_t param1, int16_t param2, int16_t param3) {
  int16_t param[] = {param1, param2, param3};
  display.firstPage();
  do {
    display.setFont(u8g2_font_7x14_mr);
    display.drawRBox(0, 0, 124, 16, 2);
    display.drawStr(1, 10, menu[0]);
    display.drawStr(1, 22, menu[1]);
    display.setCursor(15 + display.getStrWidth(menu[1]) ,22);
    display.print("56.57");
    display.drawStr(1, 34, menu[2]);
    display.drawStr(1, 46, menu[3]);
    display.drawStr(1, 58, menu[4]);
    display.drawStr(1, (menu_index * 8) + 15, ">");
  } while (display.nextPage());
}
// Displays menu to setup MANUAL mode
/*void showManualMenu() {
  display.clearBuffer();
  //display.setTextSize(1);
  //display.fillRect(0, 0, SCREEN_WIDTH, 10, WHITE);
  //display.setTextColor(SSD1306_BLACK);
  display.setCursor(tab, 1);
  display.print(manual_menu[0]);
  //display.setTextColor(SSD1306_WHITE);
  display.setCursor(tab, 12);
  display.print(manual_menu[1]); display.print(manual_degree);
  display.setCursor(tab, 23);
  display.print(manual_menu[2]);
  display.setCursor(tab, 34);
  display.print(manual_menu[3]);
  display.setCursor(tab, 45);
  display.print(manual_menu[4]);
  display.setCursor(2, (menu_index * spacing) + 12);
  display.print(">");
  display.sendBuffer();
  }*/
void moveMotor(bool mot_dir, int mot_deg, int time) {
  int mot_step = mot_deg / 0.1125; // Divide over 1.8 for full step, 0.9 for half step and so on
  Serial.println(mot_step);
  digitalWrite(STEP1_EN, LOW);
  digitalWrite(DIR1, mot_dir);
  for (int i = 0; i < mot_step; i++) {
    digitalWrite(STEP1, HIGH);
    if (readButton() == 1) {
      digitalWrite(STEP1_EN, HIGH);
      break;
    }
    delayMicroseconds(time);
    digitalWrite(STEP1, LOW);
    if (readButton() == 1) {
      digitalWrite(STEP1_EN, HIGH);
      break;
    }
    delayMicroseconds(time);
  }
  digitalWrite(STEP1_EN, HIGH);
}
int readButton() {
  int btn_state = 0;
  if (digitalRead(BTN) == LOW) {
    if (millis() - last_press > 100) {
      btn_state = 1;
      playTone();
    }
    last_press = millis();
  }
  delay(5);
  return btn_state;
}
int playTone() {
  tone(BUZ, 1047, 100); // C6
  delay(100);
  noTone(BUZ);
}