/*

RPN RULES MODULE
Use RPNLib library (https://github.com/xoseperez/rpnlib)
Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>

*/

#if RPN_RULES_SUPPORT

#include "relay.h"

#include <rpnlib.h>

// -----------------------------------------------------------------------------
// Custom commands
// -----------------------------------------------------------------------------

rpn_context _rpn_ctxt;
bool _rpn_run = false;
unsigned long _rpn_delay = RPN_DELAY;
unsigned long _rpn_last = 0;

// -----------------------------------------------------------------------------

bool _rpnWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
    return (strncmp(key, "rpn", 3) == 0);
}

void _rpnWebSocketOnConnected(JsonObject& root) {

    root["rpnSticky"] = getSetting("rpnSticky", 1).toInt();
    root["rpnDelay"] = getSetting("rpnDelay", RPN_DELAY).toInt();
    JsonArray& rules = root.createNestedArray("rpnRules");

    unsigned char i = 0;
    String rule = getSetting("rpnRule", i, "");
    while (rule.length()) {
        rules.add(rule);
        rule = getSetting("rpnRule", ++i, "");
    }

    #if MQTT_SUPPORT
        i=0;
        JsonArray& topics = root.createNestedArray("rpnTopics");
        JsonArray& names = root.createNestedArray("rpnNames");
        String rpn_topic = getSetting("rpnTopic", i, "");
        while (rpn_topic.length() > 0) {
            String rpn_name = getSetting("rpnName", i, "");
            topics.add(rpn_topic);
            names.add(rpn_name);
            rpn_topic = getSetting("rpnTopic", ++i, "");
        }
    #endif

}

#if MQTT_SUPPORT

void _rpnMQTTSubscribe() {
    unsigned char i = 0;
    String rpn_topic = getSetting("rpnTopic", i, "");
    while (rpn_topic.length()) {
        mqttSubscribeRaw(rpn_topic.c_str());
        rpn_topic = getSetting("rpnTopic", ++i, "");
    }
}

void _rpnMQTTCallback(unsigned int type, const char * topic, const char * payload) {

    if (type == MQTT_CONNECT_EVENT) {
        _rpnMQTTSubscribe();
    }

    if (type == MQTT_MESSAGE_EVENT) {
        unsigned char i = 0;
        String rpn_topic = getSetting("rpnTopic", i, "");
        while (rpn_topic.length()) {
            if (rpn_topic.equals(topic)) {
                String rpn_name = getSetting("rpnName", i, "");
                if (rpn_name.length()) {
                    rpn_variable_set(_rpn_ctxt, rpn_name.c_str(), atof(payload));
                    _rpn_last = millis();
                    _rpn_run = true;
                    break;
                }
            }
            rpn_topic = getSetting("rpnTopic", ++i, "");
        }
    }

}
#endif // MQTT_SUPPORT

void _rpnConfigure() {
    #if MQTT_SUPPORT
        if (mqttConnected()) _rpnMQTTSubscribe();
    #endif
    _rpn_delay = getSetting("rpnDelay", RPN_DELAY).toInt();
}

void _rpnBrokerCallback(const String& topic, unsigned char id, double value, const char*) {

    char name[32] = {0};

    snprintf(name, sizeof(name), "%s%u", topic.c_str(), id);
    rpn_variable_set(_rpn_ctxt, name, value);

    _rpn_last = millis();
    _rpn_run = true;

}

void _rpnBrokerStatus(const String& topic, unsigned char id, unsigned int value) {
    _rpnBrokerCallback(topic, id, double(value), nullptr);
}

void _rpnInit() {

    // Init context
    rpn_init(_rpn_ctxt);

    // Time functions
    rpn_operator_set(_rpn_ctxt, "now", 0, [](rpn_context & ctxt) {
        if (!ntpSynced()) return false;
        rpn_stack_push(ctxt, now());
        return true;
    });
    rpn_operator_set(_rpn_ctxt, "utc", 0, [](rpn_context & ctxt) {
        if (!ntpSynced()) return false;
        rpn_stack_push(ctxt, ntpLocal2UTC(now()));
        return true;
    });
    rpn_operator_set(_rpn_ctxt, "dow", 1, [](rpn_context & ctxt) {
        float a;
        rpn_stack_pop(ctxt, a);
        unsigned char dow = (weekday(int(a)) + 5) % 7;
        rpn_stack_push(ctxt, dow);
        return true;
    });
    rpn_operator_set(_rpn_ctxt, "hour", 1, [](rpn_context & ctxt) {
        float a;
        rpn_stack_pop(ctxt, a);
        rpn_stack_push(ctxt, hour(int(a)));
        return true;
    });
    rpn_operator_set(_rpn_ctxt, "minute", 1, [](rpn_context & ctxt) {
        float a;
        rpn_stack_pop(ctxt, a);
        rpn_stack_push(ctxt, minute(int(a)));
        return true;
    });

    // Debug
    rpn_operator_set(_rpn_ctxt, "debug", 0, [](rpn_context & ctxt) {
        _rpnDump();
        return true;
    });

    // Relay operators
    rpn_operator_set(_rpn_ctxt, "relay", 2, [](rpn_context & ctxt) {
        float a, b;
        rpn_stack_pop(ctxt, b); // relay number
        rpn_stack_pop(ctxt, a); // new status
        if (int(a) == 2) {
            relayToggle(int(b));
        } else {
            relayStatus(int(b), int(a) == 1);
        }
        return true;
    });

    // Channel operators
    #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT

        rpn_operator_set(_rpn_ctxt, "update", 0, [](rpn_context & ctxt) {
            lightUpdate(true, true);
            return true;
        });

        rpn_operator_set(_rpn_ctxt, "black", 0, [](rpn_context & ctxt) {
            lightColor((unsigned long) 0);
            return true;
        });

        rpn_operator_set(_rpn_ctxt, "channel", 2, [](rpn_context & ctxt) {
            float a, b;
            rpn_stack_pop(ctxt, b); // channel number
            rpn_stack_pop(ctxt, a); // new value
            lightChannel(int(b), int(a));
            return true;
        });

    #endif

}

#if TERMINAL_SUPPORT

void _rpnInitCommands() {

    terminalRegisterCommand(F("RPN.VARS"), [](Embedis* e) {
        unsigned char num = rpn_variables_size(_rpn_ctxt);
        if (0 == num) {
            DEBUG_MSG_P(PSTR("[RPN] No variables\n"));
        } else {
            DEBUG_MSG_P(PSTR("[RPN] Variables:\n"));
            for (unsigned char i=0; i<num; i++) {
                char * name = rpn_variable_name(_rpn_ctxt, i);
                float value;
                rpn_variable_get(_rpn_ctxt, name, value);
                DEBUG_MSG_P(PSTR("   %s: %s\n"), name, String(value).c_str());
            }
        }
        terminalOK();
    });

    terminalRegisterCommand(F("RPN.OPS"), [](Embedis* e) {
        unsigned char num = _rpn_ctxt.operators.size();
        DEBUG_MSG_P(PSTR("[RPN] Operators:\n"));
        for (unsigned char i=0; i<num; i++) {
            DEBUG_MSG_P(PSTR("   %s (%d)\n"), _rpn_ctxt.operators[i].name, _rpn_ctxt.operators[i].argc);
        }
        terminalOK();
    });

    terminalRegisterCommand(F("RPN.TEST"), [](Embedis* e) {
        if (e->argc == 2) {
            DEBUG_MSG_P(PSTR("[RPN] Running \"%s\"\n"), e->argv[1]);
            rpn_process(_rpn_ctxt, e->argv[1], true);
            _rpnDump();
            rpn_stack_clear(_rpn_ctxt);
            terminalOK();
        } else {
            terminalError(F("Wrong arguments"));
        }
    });

}
#endif

void _rpnDump() {
    float value;
    DEBUG_MSG_P(PSTR("[RPN] Stack:\n"));
    unsigned char num = rpn_stack_size(_rpn_ctxt);
    if (0 == num) {
        DEBUG_MSG_P(PSTR("      (empty)\n"));
    } else {
        unsigned char index = num - 1;
        while (rpn_stack_get(_rpn_ctxt, index, value)) {
            DEBUG_MSG_P(PSTR("      %02d: %s\n"), index--, String(value).c_str());
        }
    }
}

void _rpnRun() {

    unsigned char i = 0;
    String rule = getSetting("rpnRule", i, "");
    while (rule.length()) {
        //DEBUG_MSG_P(PSTR("[RPN] Running \"%s\"\n"), rule.c_str());
        rpn_process(_rpn_ctxt, rule.c_str(), true);
        //_rpnDump();
        rule = getSetting("rpnRule", ++i, "");
        rpn_stack_clear(_rpn_ctxt);
    }

    if (getSetting("rpnSticky", 1).toInt() == 0) {
        rpn_variables_clear(_rpn_ctxt);
    }

}

void _rpnLoop() {

    if (_rpn_run && (millis() - _rpn_last > _rpn_delay)) {
        _rpnRun();
        _rpn_run = false;
    }

}

void rpnSetup() {

    // Init context
    _rpnInit();

    // Load & cache settings
    _rpnConfigure();

    // Terminal commands
    #if TERMINAL_SUPPORT
        _rpnInitCommands();
    #endif

    // Websockets
    #if WEB_SUPPORT
        wsRegister()
            .onVisible([](JsonObject& root) { root["rpnVisible"] = 1; })
            .onConnected(_rpnWebSocketOnConnected)
            .onKeyCheck(_rpnWebSocketOnKeyCheck);
    #endif

    // MQTT
    #if MQTT_SUPPORT
        mqttRegister(_rpnMQTTCallback);
    #endif

    StatusBroker::Register(_rpnBrokerStatus);
    SensorBroker::Register(_rpnBrokerCallback);

    espurnaRegisterReload(_rpnConfigure);
    espurnaRegisterLoop(_rpnLoop);

}

#endif // RPN_RULES_SUPPORT
