/*

ITEAD RF BRIDGE MODULE

Copyright (C) 2017 by Xose PĂ©rez <xose dot perez at gmail dot com>

*/

#ifdef SONOFF_RFBRIDGE

// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------

#define RF_MESSAGE_SIZE     9
#define RF_CODE_START       0xAA
#define RF_CODE_ACK         0xA0
#define RF_CODE_LEARN       0xA1
#define RF_CODE_LEARN_KO    0xA2
#define RF_CODE_LEARN_OK    0xA3
#define RF_CODE_RFIN        0xA4
#define RF_CODE_RFOUT       0xA5
#define RF_CODE_STOP        0x55

// -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE
// -----------------------------------------------------------------------------

unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
unsigned char _uartpos = 0;
unsigned char _learnId = 0;
bool _learnState = true;

// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------

void _rfbAck() {
    DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
    Serial.println();
    Serial.write(RF_CODE_START);
    Serial.write(RF_CODE_ACK);
    Serial.write(RF_CODE_STOP);
    Serial.flush();
    Serial.println();
}

void _rfbLearn() {
    DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
    Serial.println();
    Serial.write(RF_CODE_START);
    Serial.write(RF_CODE_LEARN);
    Serial.write(RF_CODE_STOP);
    Serial.flush();
    Serial.println();
}

void _rfbSend(byte * message) {
    Serial.println();
    Serial.write(RF_CODE_START);
    Serial.write(RF_CODE_RFOUT);
    for (unsigned char j=0; j<RF_MESSAGE_SIZE; j++) {
        Serial.write(message[j]);
    }
    Serial.write(RF_CODE_STOP);
    Serial.flush();
    Serial.println();
}

void _rfbSend(byte * message, int times) {

    char buffer[RF_MESSAGE_SIZE];
    _rfbToChar(message, buffer);
    DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending MESSAGE '%s' %d time(s)\n"), buffer, times);

    for (int i=0; i<times; i++) {
        if (i>0) {
            unsigned long start = millis();
            while (millis() - start < RF_SEND_DELAY) delay(1);
        }
        _rfbSend(message);
    }

}

void _rfbDecode() {

    byte action = _uartbuf[0];
    char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
    DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);

    if (action == RF_CODE_LEARN_KO) {
        _rfbAck();
        DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
    }

    if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
        _rfbToChar(&_uartbuf[1], buffer);
        mqttSend(MQTT_TOPIC_RFIN, buffer);
        _rfbAck();
    }

    if (action == RF_CODE_LEARN_OK) {
        // TODO: notify websocket
        _rfbStore(_learnId, _learnState, buffer);
        DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success. Storing %d-%s => '%s'\n"), _learnId, _learnState ? "ON" : "OFF", buffer);
    }

    if (action == RF_CODE_RFIN) {
        DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
    }

}

void _rfbReceive() {

    static bool receiving = false;

    while (Serial.available()) {

        yield();
        byte c = Serial.read();
        //DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);

        if (receiving) {
            if (c == RF_CODE_STOP) {
                _rfbDecode();
                receiving = false;
            } else {
                _uartbuf[_uartpos++] = c;
            }
        } else if (c == RF_CODE_START) {
            _uartpos = 0;
            receiving = true;
        }

    }


}

/*
From an hexa char array ("A220EE...") to a byte array (half the size)
 */
bool _rfbToArray(const char * in, byte * out) {
    if (strlen(in) != RF_MESSAGE_SIZE * 2) return false;
    char tmp[3] = {0};
    for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
        memcpy(tmp, &in[p*2], 2);
        out[p] = strtol(tmp, NULL, 16);
    }
    return true;
}

/*
From a byte array to an hexa char array ("A220EE...", double the size)
 */
bool _rfbToChar(byte * in, char * out) {
    for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
        sprintf(&out[p*2], "%02X", in[p]);
    }
    return true;
}

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

    if (type == MQTT_CONNECT_EVENT) {
        char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
        sprintf(buffer, "%s/+", MQTT_TOPIC_RFLEARN);
        mqttSubscribe(buffer);
        mqttSubscribe(MQTT_TOPIC_RFOUT);
    }

    if (type == MQTT_MESSAGE_EVENT) {

        // Match topic
        String t = mqttSubtopic((char *) topic);

        // Check if should go into learn mode
        if (t.startsWith(MQTT_TOPIC_RFLEARN)) {

            _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
            if (_learnId >= relayCount()) {
                DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
                return;
            }
            _learnState = (char)payload[0] != '0';
            _rfbLearn();

        }

        if (t.equals(MQTT_TOPIC_RFOUT)) {
            byte message[RF_MESSAGE_SIZE];
            if (_rfbToArray(payload, message)) {
                _rfbSend(message, RF_SEND_TIMES);
            }
        }

    }

}

void _rfbStore(unsigned char id, bool status, char * code) {
    char key[8] = {0};
    sprintf(key, "rfb%d%s", id, status ? "on" : "off");
    setSetting(key, code);
}

String _rfbRetrieve(unsigned char id, bool status) {
    char key[8] = {0};
    sprintf(key, "rfb%d%s", id, status ? "on" : "off");
    return getSetting(key);
}

// -----------------------------------------------------------------------------
// PUBLIC
// -----------------------------------------------------------------------------

void rfbState(unsigned char id, bool status) {
    String value = _rfbRetrieve(id, status);
    DEBUG_MSG_P(PSTR("[RFBRIDGE] Retrieving value for %d-%s => %s\n"), id, status ? "ON" : "OFF", value.c_str());
    if (value.length() > 0) {
        byte message[RF_MESSAGE_SIZE];
        _rfbToArray(value.c_str(), message);
        _rfbSend(message, RF_SEND_TIMES);
    }
}

void rfbLearn(unsigned char id, bool status) {
    _learnId = id;
    _learnState = status;
    _rfbLearn();
}

void rfbForget(unsigned char id, bool status) {
    char key[8] = {0};
    sprintf(key, "rfb%d%s", id, status ? "on" : "off");
    delSetting(key);
}

// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------

void rfbSetup() {
    mqttRegister(_rfbMqttCallback);
}

void rfbLoop() {
    _rfbReceive();
}

#endif