// Feather esp8266
// Feather LCD
// Feather motor controler

#undef STASSID
#undef STAPSK

#define STASSID "hackaton"
#define STAPSK  "WifiLAAS2018"

#ifndef STASSID
#define STASSID         "laas-capture"
#define STAPSK          ""
#endif

#if !CORE_MOCK && !defined(ARDUINO_ESP8266_ADAFRUIT_HUZZAH)
#error select board Adafruit Feather HUZZAH ESP8266
#endif

#define NAME            "PousseSeringue"

#define OLED_HW         Adafruit128x32
#define BTNA            0               // gpio A adafruit feather oled 128x32
#define BTNB            16              // gpio B adafruit feather oled 128x32
#define BTNC            2               // gpio C adafruit feather oled 128x32
#define OLED_I2C        0x3c            // adafruit feather oled 128x32



#define COURSE          14              // (d5) fin de course


#define BAUDRATE 115200

// should this be configurable through a GUI ?
#define MOTOR_STEPS     200             // 17HS19
#define MICROSTEPS_CONF 16              // hardware configuration: A4988's MS1,MS2,MS3 tied up

#include <ESP8266WiFi.h>        // internal, wifi library
#include <DNSServer.h>          // internal, access point mode
#include <Schedule.h>
#include <interrupts.h> // InterruptLock


using InterruptLock = esp8266::InterruptLock;

#if !CORE_MOCK
#include <SSD1306AsciiWire.h>   // https://github.com/greiman/SSD1306Ascii
SSD1306AsciiWire oled;
#endif // !CORE_MOCK

#include "motor.h"
#include "Debouncer.h"          // local, debouncer, short counter, long detector
#include "syringe.h"
#include "syringefilled.h"
#include "cli.h"
#include "common.h"
#include "fs.h"



enum class Modes { TURN, TURNSetup, RPM, RPMSetup, INFO };

const char* msgHeader = MSGHEADER;

Debouncer bA, bB, bC;
ButtonFast bEmergency;

constexpr int DNS_PORT = 53;
DNSServer dnsServer;

char ssid[65];

Modes mode = Modes::RPM;

int move1 = 0;

// TURN mode
constexpr float step2deg = 360.0 / MOTOR_STEPS / MICROSTEPS_CONF;   // deg for 1 step
int steps = MOTOR_STEPS * MICROSTEPS_CONF / 4;                      // =1/4 turn

// RPM mode
int stepsPerSeconds = MOTOR_STEPS * MICROSTEPS_CONF / 2;
float stepsPerSeconds2rpm () { return 60.0 * stepsPerSeconds / (MOTOR_STEPS * MICROSTEPS_CONF); }
int rpming = 0; // -1, 0 or +1

// single syringe global instance
SyringeFilled syringefilled(stepper);
Cli console(syringe_filled);


const char* oledMode ()
{
    switch (mode)
    {
    case Modes::TURN: return "TURN";
    case Modes::TURNSetup: return "TURN Setup";
    case Modes::RPM: return "RPM";
    case Modes::RPMSetup: return "RPM Setup";
    case Modes::INFO: return "Informations";
    }
    return "mode?";
}

void oledPrintMode ()
{
#if !CORE_MOCK
    oled.printf("\n mode: %s", oledMode());
#endif // !CORE_MOCK
}

void oledRedisplay ()
{
#if !CORE_MOCK
    oled.clear();

    switch (mode)
    {
    case Modes::TURN:
        oled.printf("<%g dR <<360 dR", steps * step2deg);
        oledPrintMode();
        oled.printf("\n");
        oled.printf("\n<%g dL <<360 d ", steps * step2deg);
        break;
    case Modes::TURNSetup:
        oled.printf("<+1step << +1/16T");
        oledPrintMode();
        oled.printf("\n steps:%d (%g d)", steps, steps * step2deg);
        oled.printf("\n<-1step << -1/16T");
        break;
    case Modes::RPM:
        oled.printf("<stop <<start");
        oledPrintMode();
        oled.printf("\n %g rpm", stepsPerSeconds2rpm());
        oled.printf("\n<stop <<start");
        break;
    case Modes::RPMSetup:
        oled.printf("<+1s/s <<+1/4T/s");
        oledPrintMode();
        oled.printf("\ns/s:%d RPM:%g", stepsPerSeconds, stepsPerSeconds2rpm());
        oled.printf("\n<-1s/s <<-1/4T/s");
        break;
    case Modes::INFO:
        oled.printf("IP:%s", WiFi.localIP().toString().c_str());
        uint8_t mac[6];
        WiFi.macAddress(mac);
        oled.printf("\n%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        oled.printf("\nAPSSID & hostname:");
        oled.printf("\n%s", ssid);
        break;

    default:
        oled.printf("?");
    }
#endif // !CORE_MOCK
}

void changeMode ()
{
    switch (mode)
    {
    case Modes::TURN: mode = Modes::TURNSetup; break;
    case Modes::TURNSetup: mode = Modes::RPM; break;
    case Modes::RPM: mode = Modes::RPMSetup; break;
    case Modes::RPMSetup: mode = Modes::INFO; break;
    case Modes::INFO: mode = Modes::TURN; break;
    }
    oledRedisplay();
}


// button status management, per mode

void buttons (int shortp, int longp)
{
    // log debug on serial console
    Serial.printf("button change: short:%d long:%d\n", shortp, longp);

    syringe_filled.stop();

	auto oldrpming = rpming;
    rpming = 0; // anything stops rpming

    switch (mode)
    {
    case Modes::TURN:
        Serial.printf("move1: %d -> ", move1);
        move1 += shortp * steps;
        move1 += longp * MOTOR_STEPS * MICROSTEPS_CONF; // long: one turn
        Serial.printf("%d\n", move1);
#if !CORE_MOCK
        cli();
		stepper.moveTo(move1);
        sei();
#endif // !CORE_MOCK
        break;

    case Modes::TURNSetup:
        steps += shortp;
        steps += longp * MOTOR_STEPS * MICROSTEPS_CONF / 16;
        oledRedisplay();
#if !CORE_MOCK
        cli();
		stepper.setCurrentPosition(0);
        sei();
#endif // !CORE_MOCK
        break;

    case Modes::RPM:
        if (longp)
		{
            rpming = longp? (oldrpming > 1? -1: 1): 0;
			if (rpming)
			{
#if !CORE_MOCK
                cli();
				stepper.setSpeed(stepsPerSeconds);
                sei();
#endif // !CORE_MOCK
				Serial.printf("StepPerSecs=%d (%g RPM)\n", stepsPerSeconds, stepsPerSeconds2rpm());
				//stepper.setMaxSpeed(400);
			}
		}
        break;

    case Modes::RPMSetup:
        if ((stepsPerSeconds += longp * MOTOR_STEPS * MICROSTEPS_CONF / 16) < 1)
            stepsPerSeconds = 1;
        if ((stepsPerSeconds += shortp) < 1)
            stepsPerSeconds = 1;
        oledRedisplay();
        break;

    default:
        Serial.printf("press not handled in buttons()\n");
    }
}


#if !CORE_MOCK

IRAM_ATTR
void stepperRun ()
{
    stepper.runSpeed();
}

#endif // !CORE_MOCK

void setup()
{
    Serial.begin(BAUDRATE);
    Serial.setDebugOutput(true);
    Serial.println();
    Serial.println("Booting");

#if !CORE_MOCK
    Wire.begin();
    Wire.setClock(400000L);
#endif // !CORE_MOCK
    delay(500);

#if !CORE_MOCK
    // initialize hardware

    oled.begin(&OLED_HW, OLED_I2C);
    oled.setFont(System5x7);
    oled.setScrollMode(SCROLL_MODE_AUTO);
    oled.clear();
    oled.println("Booting...\n");

    syringe_filled.set_physical(MOTOR_STEPS * MICROSTEPS_CONF, M5_MMREV, /*forwardClockWise*/false);

    pinMode(SLEEP, OUTPUT);
    digitalWrite(SLEEP, 0);
#endif // !CORE_MOCK

#if 1

    uint8_t mac[6];
    WiFi.macAddress(mac);
    sprintf(ssid, NAME "-%02x%02x%02x", mac[3], mac[4], mac[5]);
#if 1
    if (strlen(STASSID) > 0)
    {
        WiFi.setPhyMode(WIFI_PHY_MODE_11G);
        WiFi.mode(WIFI_STA);
        //WiFi.softAP(ssid);
        WiFi.hostname(ssid);
        WiFi.begin(STASSID, STAPSK);
    }
#endif

    // redirect everything to my AP ip:
    dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
    dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());

    Serial.printf("AP: ssid='%s' (no password)\n", ssid);
    Serial.printf("---> Connect to this SSID '%s' and head to http://%s    <---\n", ssid, WiFi.softAPIP().toString().c_str());
    Serial.printf("---> Within SSID %s, connect to http://%s <----\n", STASSID, ssid);
    Serial.printf("--->        (or read IP address on screen)\n");
    Serial.printf("connecting to ssid '%s'\n", STASSID);

    web_setup();


#endif //0


    pinMode(BTNA, INPUT_PULLUP);
    pinMode(BTNB, INPUT_PULLUP);
    pinMode(BTNC, INPUT_PULLUP);
    pinMode(COURSE, INPUT_PULLUP);

    oledRedisplay();

#if 1

#if !CORE_MOCK
#ifdef ISR_ATTR
    timer1_isr_init();
    timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
    timer1_attachInterrupt(stepperRun);
    timer1_write(microsecondsToClockCycles(100));
    schedule_recurrent_function_us([](){ if (stepper.distanceToGo()) { InterruptLock lock; stepper.computeNewSpeed(); } return true; }, 1);
#else
    #warning ===================================================================================================
    #warning "Please use the ISR version of AccelStepper (git clone -b isr https://github.com/d-a-v/AccelStepper)"
    #warning ===================================================================================================
    schedule_recurrent_function_us([](){ stepperRun(); return true; }, 1);
#endif
#endif

#endif

    // check if emergency button is pressed at boot time
    syringe_filled.set_emergency(!!digitalRead(COURSE));

    if (fs_init(msgHeader, &Serial))
    {
        Serial.printf("FS initialized, dav http server started\n");
    }
    else
    {
        Serial.printf("FS: could not be started\n");
    }

    //Json 

    syringe_filled.read_Json();
    syringe.read_Json();



}


void loop()
{
    if (bEmergency.update(digitalRead(COURSE)))
    {
        syringe_filled.manage_emergency(bEmergency.pressed(), !!stepper.distanceToGo());
        Serial.printf("%sEmergency=%u (remaining steps = %lu)\n", msgHeader, bEmergency.pressed(), stepper.distanceToGo());
    }
    

    //Code pour les boutons de l'écran
  #if CORE_MOCK
    bool changeA = false;
    bool changeB = false;
    bool changeC = false;
  #else
    // button management
    bool changeA = bA.update(!digitalRead(BTNA));
    bool changeB = bB.update(!digitalRead(BTNB));
    bool changeC = bC.update(!digitalRead(BTNC));
  #endif

    if (changeA)
    {
        // button values (::shortCount(), ::longState())
        // are not cleared until they are read
        int shortA = bA.shortCount();
        bool longA = bA.longState();

        buttons(shortA, longA);
    }

    if (changeC)
    {
        int shortC = bC.shortCount();
        bool longC = bC.longState();

        buttons(-shortC, -longC);
    }

    if (changeB)
    {
        int shortB = bB.shortCount();
        bool longB = bB.longState();

        if (longB)
            changeMode();
        (void)shortB;
    }

    console.loop(Serial);
    web_loop();
    dnsServer.processNextRequest(); // access-point redirection
}