#include <Vector.h>
#include <Streaming.h>
#include <Arduino.h>

#include "app.h"
#include "features.h"

#include <MemoryFree.h>
//#ifdef MODBUS_BRIDGE
#include "ModbusBridge.h"

//#endif

static Addon *addonsArray[10];

#ifdef HAS_DIP
int dipSwitchPins[] = {48, 49};
#endif

short App::ok()
{
    if (now - dt > 100)
    {
        // motor should spin
        /*
        if (vfd->direction &&
            mLoad->lastIdle > MOTOR_MIN_DT &&
            mLoad->dt > MOTOR_MIN_DT  &&
            mLoad->currentState == MotorLoad::MSTATE::NONE)
        {
            return E_VFD_RUN;
        }

        // motor should not spin
        if (!vfd->direction &&
            vfd->dt > MOTOR_MIN_DT &&
            mLoad->lastLoad > MOTOR_MIN_DT * 4  &&
            vfd->direction == VFD::DIRECTION::STOP &&
            mLoad->currentState != MotorLoad::MSTATE::NONE)
        {
            return E_VFD_LOSS;
        }
        */
        dt = now;
    }
    return E_OK;
}

App::App() : Addon("APP", APP, 1 << STATE),
             shredButton(ProximitySensor(CONTROLLINO_A15)),
#ifdef HAS_DIRECTION_SWITCH
             dirSwitch(new DirectionSwitch()),
#endif
#ifdef ENCLOSURE_SENSOR
             enclosureSensor(new EnclosureSensor()),
#endif
#ifdef HAS_VFD
             vfd(new VFD()),
#endif
#ifdef MOTOR_HAS_TEMPERTURE
             mHeat(new MotorTemperature()),
#endif
#ifdef HAS_MOTOR_IR_SENSOR
             mSpeed(new MotorSpeed()),
#endif
#ifdef HAS_DIP
             _dipSwitch(new _DipSwitch(2, dipSwitchPins)),
#endif
#ifdef CARTRIDGE_FULL_1
             cartridgeFull(new CartridgeFull()),
#endif
#ifdef HAS_HOPPER_LOADED
#ifdef HOPPER_LOADED_1
             hopperLoaded(new HopperLoaded(HOPPER_LOADED_0, HOPPER_LOADED_1)),
#else
             hopperLoaded(new HopperLoaded(HOPPER_LOADED_0)),
#endif
#endif
#ifdef MOTOR_LOAD_PIN
             mLoad(new MotorLoad(MOTOR_LOAD_PIN)),
#endif
#ifdef HAS_AUTOREVERSE
             autoReverse(new AutoReverse(this)),
#endif
#ifdef HAS_PLUNGER
             plunger(new Plunger(this, (AddonFnPtr)&App::plungerCB)),
#endif
#ifdef HAS_SERIAL
             serialBridge(new PPSerial(Serial)),
#endif
#ifdef HAS_POWER
#if defined(POWER_0) && defined(POWER_1)
             powerSwitch(new Power(POWER_0, POWER_1)),
#else
             powerSwitch(new Power(POWER_0, POWER_0)),
#endif
#endif
#ifdef HAS_OP_MODE_SWITCH
#ifdef OP_MODE_ANALOG
             opModeSwitch(new OperationModeSwitch(OP_MODE_1_PIN, 120, 60, 30)),
#else
             opModeSwitch(new OperationModeSwitch(OP_MODE_1_PIN, OP_MODE_2_PIN, OP_MODE_3_PIN)),
#endif
#endif
#ifdef HAS_MODBUS_BRIDGE
             modbusBridge(new ModbusBridge()),
#endif
             shredStateLast(0),
             shredCancelState(0)
{
    //#if defined(MODBUS_BRIDGE) && defined(HAS_VFD)
    //    vfd->modbus = modbusBridge;
    //#endif
    /// modbusBridge->debug(&Serial);
}
#ifdef HAS_STATES
String App::state()
{
    const int capacity = JSON_OBJECT_SIZE(6);
    StaticJsonDocument<capacity> doc;
    doc["0"] = id;
    doc["1"] = _state;
    doc["2"] = shredState;
    doc["3"] = overloaded;
    doc["4"] = hopperLoaded->ok();
    doc["5"] = opModeSwitch->value();
    Serial.println(analogRead(CONTROLLINO_A15));
    return doc.as<String>();
}
#endif

short App::getAppState(short val)
{
    return _state;
}
void (*resetFunction)(void) = 0; // Self reset (to be used with watchdog)

short App::setAppState(short newState)
{
    switch (newState)
    {
    case App::APP_STATE::RESET:
    {

        _state = App::APP_STATE::STANDBY;
        shredStateLast = 0;
        shredCancelState = 0;
        shredState = DONE;
        plunger->reset();
        vfd->stop();
        resetFunction();
        return;
    }
    case App::APP_STATE::STANDBY:
    {
        switch (_state)
        {
        case App::APP_STATE::SHREDDING:
        {
            shredCancelState = App::SHRED_STATE::INIT;
            shredState = App::SHRED_STATE::CANCELLING;
            plunger->reset();
            break;
        }
        }
    }
    }
    _state = newState;
}

void printMem()
{
    Serial.print("mem: ");
    Serial.print(freeMemory());
    Serial.println('--');
}
short App::setup()
{
    Serial.begin(DEBUG_BAUD_RATE);

    Serial.println("Booting Firmware ...................... ");

    addons.setStorage(addonsArray);
    setup_addons();

#ifdef MEARSURE_PERFORMANCE
    printPerfTS = 0;
    addonLoopTime = 0;
    bridgeLoopTime = 0;
#endif
    debugTS = 0;
    comTS = 0;
    loopTS = 0;
    shredState = 0;
    overloaded = 0;
    _state = 0;
    jamCounter = 0;
    bootTime = millis();
    shredButton.setup();

    /*
    timer.every(5000, [](App *app) -> void {
        printMem();
    },
                this);
                */
}

void App::loop_service()
{
#ifdef HAS_POWER
    powerSwitch->on(POWER_PRIMARY);
#endif
    // _loop_motor_manual();
}
void App::_loop_motor_manual()
{
    loop_auto_reverse();
#if defined(HAS_DIRECTION_SWITCH) && defined(HAS_VFD)
    uchar sw = this->dirSwitch->loop();
    if (sw == 2)
    {
        this->vfd->fwd(true);
    }
    else if (sw == 1)
    {
        this->vfd->rev(true);
    }
    else
    {
        this->vfd->stop();
    }
#endif
}

void App::loop_normal()
{
#ifdef HAS_HOPPER_LOADED
    loop_auto_reverse();
    if (hopperLoaded->ok())
    {
        powerSwitch->on(POWER_PRIMARY);
        vfd->fwd(true);
        plunger->plunge();
    }
    else
    {
        this->vfd->stop();
        //  plunger->home();
        powerSwitch->off(POWER_PRIMARY);
    }
#endif
}
void App::debug_mode_loop()
{
    uchar s = addons.size();
    for (uchar i = 0; i < s; i++)
    {
        Addon *addon = addons[i];
        if (addon->hasFlag(LOOP))
        {
            addon->loop();
        }
    }

#if defined(HAS_POWER) && defined(HAS_HOPPER_LOADED)
    uchar sw = this->dirSwitch->loop();
    if (sw)
    {
        powerSwitch->on(POWER_PRIMARY);
    }
    if (sw == 2)
    {
        this->vfd->fwd(true);
    }
    else if (sw == 1)
    {
        this->vfd->rev(true);
    }
    else
    {
        this->vfd->stop();
        powerSwitch->off(POWER_PRIMARY);
    }

    debug();
    if (sw)
    {
        delay(LOOP_DELAY);
        return;
    }
    ///////////////////////////////////////////////////
    if (hopperLoaded->ok())
    {
        powerSwitch->on(POWER_PRIMARY);
        this->vfd->fwd(true);
        delay(3000);
    }
    else
    {
        this->vfd->stop();
        powerSwitch->off(POWER_PRIMARY);
    }

    delay(LOOP_DELAY);
#endif
}

short App::loop()
{
    loop_addons();
    loop_com();
    timer.tick();
    now = millis();
    shredButton.loop();

    if (now - bootTime < BOOT_DELAY)
    {
        return;
    }

    if (shredState == STUCK)
    {
        _error = E_STUCK;
        setAppState(App::SHRED_STATE::CANCELLING);
        return;
    }

    short error = ok();
    if (error)
    {
        _error = error;
        return;
    }

#ifdef HAS_OP_MODE_SWITCH
    short op = opModeSwitch->value();
    op = 0;
    switch (op)
    {
        //pos 1
    case OP_DEBUG:
    {
#ifdef HAS_POWER
        // powerSwitch->on(POWER_PRIMARY);
#endif
        break;
    }
    // pos2
    case OP_NORMAL:
    {
#ifdef HAS_POWER
        // powerSwitch->on(POWER_PRIMARY);
        // powerSwitch->on(POWER_SECONDARY);
#endif
        loop_normal();
        debug();
        break;
    }
    //pos 0
    case OP_NONE:
    {
#ifdef HAS_POWER
        // powerSwitch->off(POWER_PRIMARY);
#endif
        // vfd->stop();
        //  plunger->stop();

        if (hopperLoaded->ok() && _state != SHREDDING)
        {
            Serial.println("hopper loaded");
            shred();
        }
        loopShred();
        break;
    }
    case OP_SERVICE:
    {
        // loop_normal();
        powerSwitch->on(POWER_PRIMARY);
        vfd->rev(true);

        break;
    }
    }
#endif
}

void App::loop_com()
{
    if (millis() - comTS > 300)
    {
#if defined(HAS_BRIDGE) && defined(HAS_SERIAL)
        PPSerial::Message *msg = serialBridge->read();
        if (msg)
        {
            switch (msg->verb)
            {

            case Bridge::EC_METHOD:
            {
                char *strings[3];
                char *ptr = NULL;
                byte index = 0;

                ptr = strtok(msg->payload, ":");

                while (ptr != NULL && index < 4)
                {
                    strings[index] = ptr;
                    index++;
                    ptr = strtok(NULL, ":");
                }

                int id = atoi(strings[0]);
                char *_method = strings[1];

                SKeyVal *method = VSL::instance()->hasMethod(id, _method);
                if (method)
                {
                    int arg = atoi(strings[2]);
                    Addon *addon = (Addon *)method->instance;
                    AddonFnPtr ptr = method->mPtr;
                    short ret = (addon->*ptr)(arg);

                    if (TEST(msg->flags, Bridge::STATE))
                    {
#ifdef HAS_STATES
                        this->appState(0);
#endif
                    }
                    else if (TEST(msg->flags, Bridge::RECEIPT))
                    {
#ifdef BRIDGE_HAS_RESPONSE
                        const char *response = Bridge::CreateResponse(msg->id, 0, ret);
                        Serial.write(response);
#endif
                    }
                    if (TEST(msg->flags, Bridge::DEBUG))
                    {
                        Serial.println("Called command");
                    }
                    Serial.println("Called command");
                    Serial.println(addon->name);
                    Serial.println(_method);
                }
                else
                {
                    VSL::instance()->debug();
                    if (TEST(msg->flags, Bridge::DEBUG))
                    {

                        Serial.print("Incoming message, cant find class & method ");
                        /*
                        Serial.print(_class);
                        Serial.print(":");
                        Serial.print(_method);
                        */
                        Serial.print(_method);
                        Serial.print("\n");
                    }
                }
                break;
            }
            }
            msg->payload = NULL;
        }
#endif
        comTS = millis();
    }
}
