/*
 * PhotoStepper -- https://www.0x50.de/rschaten/photostepper
 *
 * PhotoStepper © 2022 by Ronald Schaten is licensed under CC BY-NC 4.0.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
 *
 */

// this project makes heavy use of the following libraries:
// - https://www.arduino.cc/reference/en/libraries/arduinomenu-library/
// - https://www.arduino.cc/reference/en/libraries/stepperdriver/
// - https://www.arduino.cc/reference/en/libraries/analogkeypad/

#include <Arduino.h>
#include <AnalogKeypad.h>
#include <BasicStepperDriver.h>
#include <menu.h>
#include <menuIO/liquidCrystalOut.h>
#include <menuIO/serialIO.h>
#include "Jogrunner.h"
#include "Scenerunner.h"

// analog keypad
#define PIN_BUTTON A0
#define KEY_RIGHT 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_LEFT 3
#define KEY_SELECT 4
// 2x16 LCD
#define PIN_LCD_DB4 4
#define PIN_LCD_DB5 5
#define PIN_LCD_DB6 6
#define PIN_LCD_DB7 7
#define PIN_LCD_RS 8
#define PIN_LCD_EN 9
#define PIN_LCD_BL 10
// stepper motor
#define PIN_STEPPER_DIR A1
#define PIN_STEPPER_STEP A2
// camera
#define PIN_CAMERA_FOCUS A3
#define PIN_CAMERA_SHUTTER A4

// motor configuration
#define STEPPER_STEPS 200
#define STEPPER_RPM 120
#define STEPPER_MICROSTEPS 1
#define STEPPER_ACCEL 2000
#define STEPPER_DECEL 1000

#define MAX_DEPTH 2

const int KeypadMap[] = {0, 96, 250, 402, 636};
const uint16_t KeypadHoldTimeMs = 1000;
const uint8_t KeypadAnalogPin = PIN_BUTTON;
AnalogKeypad keypad(KeypadAnalogPin, KeypadMap, countof(KeypadMap), KeypadHoldTimeMs);

LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_EN, PIN_LCD_DB4, PIN_LCD_DB5, PIN_LCD_DB6, PIN_LCD_DB7);

BasicStepperDriver stepper(STEPPER_STEPS, PIN_STEPPER_DIR, PIN_STEPPER_STEP);

enum State { STATE_MENU, STATE_JOG, STATE_RUN };
State state = STATE_MENU;

Jogrunner jogrunner(&lcd, &stepper, STEPPER_RPM);
Scenerunner scenerunner(&lcd, &stepper, PIN_CAMERA_FOCUS, PIN_CAMERA_SHUTTER, PIN_LCD_BL);

// ===
// Order of menu entries looks chaotic. It makes sense on screen.
// This is because serial IO (which is used since ArduinoMenu doesn't support
// analog keypad) doesn't implement setFieldMode().

result enter_jog() {
  Serial.println("enter_jog()");
  state = STATE_JOG;
  jogrunner.start();
  return proceed;
}

result enter_run() {
  Serial.println("enter_run()");
  state = STATE_RUN;
  scenerunner.start();
  return proceed;
}

TOGGLE(scenerunner.bReturn, setReturn, "Return: ", doNothing, noEvent, wrapStyle
  ,VALUE("On", true, doNothing, noEvent)
  ,VALUE("Off", false, doNothing, noEvent)
);

TOGGLE(scenerunner.bDarkenLcd, setLcdBlackout, "DarkenLCD: ", doNothing, noEvent, wrapStyle
  ,VALUE("On", true, doNothing, noEvent)
  ,VALUE("Off", false, doNothing, noEvent)
);

MENU(configuration, "Configuration", doNothing, anyEvent, wrapStyle
  ,FIELD(scenerunner.tSettle, "tSettle", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
  ,EXIT("<Back")
  ,SUBMENU(setLcdBlackout)
  ,SUBMENU(setReturn)
  ,FIELD(scenerunner.tShutter, "tShutter", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
  ,FIELD(scenerunner.tFocus, "tFocus", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
);

MENU(scene, "Scene", doNothing, anyEvent, wrapStyle
  ,FIELD(scenerunner.nSteps, "nSteps", "steps", 0, 100, 10, 1, doNothing, noEvent, wrapStyle)
  ,EXIT("<Back")
  ,FIELD(scenerunner.distance, "distance", "mm", 0.1, 10, 1, 0.1, doNothing, noEvent, wrapStyle)
);

MENU(mainMenu, "PhotoStepper", doNothing, noEvent, wrapStyle
  ,OP("Jog", enter_jog, enterEvent)
  ,OP("Run", enter_run, enterEvent)
  ,SUBMENU(scene)
  ,SUBMENU(configuration)
);

MENU_OUTPUTS(out, MAX_DEPTH
  ,LIQUIDCRYSTAL_OUT(lcd, {0, 0, 16, 2})
  ,NONE
);

serialIn in(Serial);
NAVROOT(nav, mainMenu, MAX_DEPTH, in, out);

// ===

void ButtonHandler_menu(const ButtonParam& param) {
  Serial.print("ButtonHandler_menu(");
  Serial.print(param.button);
  Serial.print(") - ");
  switch (param.state) {
    case ButtonState_Click:
      Serial.println("Click");
      switch (param.button) {
        case KEY_UP:
          nav.doNav(upCmd);
          break;
        case KEY_DOWN:
          nav.doNav(downCmd);
          break;
        case KEY_SELECT:
          nav.doNav(enterCmd);
          break;
      }
      nav.doOutput();
      break;
    default:
      Serial.println(param.state);
  }
}

void ButtonHandler_jog(const ButtonParam& param) {
  Serial.print("ButtonHandler_jog(");
  Serial.print(param.button);
  Serial.print(") - ");
  switch (param.state) {
    case ButtonState_Click:
      Serial.println("Click");
      switch (param.button) {
        case KEY_SELECT:
          state = STATE_MENU;
          mainMenu.dirty = true;
          break;
      }
      nav.doOutput();
      break;
    case ButtonState_Up:
      Serial.println("Up");
      jogrunner.stop();
      break;
    case ButtonState_Down:
      Serial.println("Down");
      switch (param.button) {
        case KEY_UP:
          jogrunner.forward();
          break;
        case KEY_DOWN:
          jogrunner.backward();
          break;
      }
      break;
    case ButtonState_Hold:
      Serial.println("Hold");
      jogrunner.setSpeed(HIGH);
      switch (param.button) {
        case KEY_UP:
          jogrunner.forward();
          break;
        case KEY_DOWN:
          jogrunner.backward();
          break;
      }
      break;
    default:
      Serial.println();
  }
}

void ButtonHandler_run(const ButtonParam& param) {
  Serial.print("ButtonHandler_run(");
  Serial.print(param.button);
  Serial.print(") - ");
  switch (param.state) {
    default:
      Serial.println();
      stepper.stop();
      stepper.setRPM(STEPPER_RPM);
      state = STATE_MENU;
      mainMenu.dirty = true;
      nav.doOutput();
      break;
  }
}

void setup() {
  Serial.begin(115200);
  while(!Serial);
  Serial.println("PhotoStepper -- schatenseite.de");

  // control backlight
  pinMode(PIN_LCD_BL, OUTPUT);
  digitalWrite(PIN_LCD_BL, HIGH);

  // setup camera connection
  pinMode(PIN_CAMERA_FOCUS, OUTPUT);
  digitalWrite(PIN_CAMERA_FOCUS, LOW);
  pinMode(PIN_CAMERA_SHUTTER, OUTPUT);
  digitalWrite(PIN_CAMERA_SHUTTER, LOW);

  stepper.begin(STEPPER_RPM, STEPPER_MICROSTEPS);
  stepper.setSpeedProfile(stepper.LINEAR_SPEED, STEPPER_ACCEL, STEPPER_DECEL);

  lcd.begin(16,2);
  lcd.print("PhotoStepper");
  lcd.setCursor(0, 1);
  lcd.print("schatenseite.de");
  delay(2000);
  nav.doOutput();
}

void loop() {
  switch (state) {
    case STATE_MENU:
      keypad.loop(ButtonHandler_menu);
      break;
    case STATE_JOG:
      keypad.loop(ButtonHandler_jog);
      stepper.nextAction();
      break;
    case STATE_RUN:
      keypad.loop(ButtonHandler_run);
      stepper.nextAction();
      scenerunner.nextAction();
      if (!scenerunner.isBusy()) {
        state = STATE_MENU;
        mainMenu.dirty = true;
        nav.doOutput();
      }
      break;
  }
}
