#pragma once

#include "wled.h"
#ifndef WLED_ENABLE_MQTT
#error "This user mod requires MQTT to be enabled."
#endif

#ifndef MQTTSWITCHPINS
#error "Please define MQTTSWITCHPINS in platformio_override.ini. e.g. -D MQTTSWITCHPINS="12, 0, 2" "
// The following define helps Eclipse's C++ parser but is never used in production due to the #error statement on the line before
#define MQTTSWITCHPINS 12, 0, 2
#endif

// Default behavior: All outputs active high
#ifndef MQTTSWITCHINVERT
#define MQTTSWITCHINVERT
#endif

// Default behavior: All outputs off
#ifndef MQTTSWITCHDEFAULTS
#define MQTTSWITCHDEFAULTS
#endif

static const uint8_t switchPins[] = { MQTTSWITCHPINS };
//This is a hack to get the number of pins defined by the user
#define NUM_SWITCH_PINS (sizeof(switchPins))
static const bool switchInvert[NUM_SWITCH_PINS] = { MQTTSWITCHINVERT};
//Make settings in config file more readable
#define ON 1
#define OFF 0
static const bool switchDefaults[NUM_SWITCH_PINS] = { MQTTSWITCHDEFAULTS};
#undef ON
#undef OFF

class UsermodMqttSwitch: public Usermod
{
private:
    bool mqttInitialized;
    bool switchState[NUM_SWITCH_PINS];

public:
    UsermodMqttSwitch() :
            mqttInitialized(false)
    {
    }

    void setup()
    {
        for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) {
            setState(pinNr, switchDefaults[pinNr]);
            pinMode(switchPins[pinNr], OUTPUT);
        }
    }

    void loop()
    {
        if (!mqttInitialized) {
            mqttInit();
            return; // Try again in next loop iteration
        }
    }

    void mqttInit()
    {
        if (!mqtt)
            return;
        mqtt->onMessage(
                std::bind(&UsermodMqttSwitch::onMqttMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
                          std::placeholders::_5, std::placeholders::_6));
        mqtt->onConnect(std::bind(&UsermodMqttSwitch::onMqttConnect, this, std::placeholders::_1));
        mqttInitialized = true;
    }

    void onMqttConnect(bool sessionPresent);

    void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total);
    void updateState(uint8_t pinNr);

    void setState(uint8_t pinNr, bool active)
    {
        if (pinNr > NUM_SWITCH_PINS)
            return;
        switchState[pinNr] = active;
        digitalWrite((char) switchPins[pinNr], (char) (switchInvert[pinNr] ? !active : active));
        updateState(pinNr);
    }
};

inline void UsermodMqttSwitch::onMqttConnect(bool sessionPresent)
{
    if (mqttDeviceTopic[0] == 0)
        return;

    for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) {
        char buf[128];
        StaticJsonDocument<1024> json;
        sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1);
        json[F("name")] = buf;

        sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr);
        json["~"] = buf;
        strcat(buf, "/set");
        mqtt->subscribe(buf, 0);

        json[F("stat_t")] = "~/state";
        json[F("cmd_t")] = "~/set";
        json[F("pl_off")] = F("OFF");
        json[F("pl_on")] = F("ON");

        char uid[16];
        sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr);
        json[F("unique_id")] = uid;

        strcpy(buf, mqttDeviceTopic);
        strcat(buf, "/status");
        json[F("avty_t")] = buf;
        json[F("pl_avail")] = F("online");
        json[F("pl_not_avail")] = F("offline");
        //TODO: dev
        sprintf(buf, "homeassistant/switch/%s/config", uid);
        char json_str[1024];
        size_t payload_size = serializeJson(json, json_str);
        mqtt->publish(buf, 0, true, json_str, payload_size);
        updateState(pinNr);
    }
}

inline void UsermodMqttSwitch::onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
    //Note: Payload is not necessarily null terminated. Check "len" instead.
    for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) {
        char buf[64];
        sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr);
        if (strcmp(topic, buf) == 0) {
            //Any string starting with "ON" is interpreted as ON, everything else as OFF
            setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N');
            break;
        }
    }
}

inline void UsermodMqttSwitch::updateState(uint8_t pinNr)
{
    if (!mqttInitialized)
        return;

    if (pinNr > NUM_SWITCH_PINS)
        return;

    char buf[64];
    sprintf(buf, "%s/switch/%d/state", mqttDeviceTopic, pinNr);
    if (switchState[pinNr]) {
        mqtt->publish(buf, 0, false, "ON");
    } else {
        mqtt->publish(buf, 0, false, "OFF");
    }
}
