#include "bcm500ds.h"


namespace esphome {
namespace tuya_cover {


// // Forward declarations
// bool read_command();
// void write_command(TUYACOVERCommandType command, const uint8_t *value, uint16_t length);
// uint8_t checksum();

/*
     * Attempt to read an entire command from the serial UART into the command struct.
     * Will fail early if unable to find the two-byte header in the current
     * data stream. If the header is found, it will contine to read the complete
     * TLV+checksum sequence off the port. If the entire sequence can be read
     * and the checksum is valid, it will return true.
     */
bool bcm500ds::read_command()
{
    // Shift bytes through until we find a valid header
    bool valid_header = false;
    while (Serial.available() >= 1)
    {
        uart_buffer_[0] = uart_buffer_[1];
        uart_buffer_[1] = Serial.read();
        command_.header = (uart_buffer_[0] << 8) + uart_buffer_[1];
        if (command_.header == TUYA_COVER_HEADER)
        {
            valid_header = true;
            break;
        }
    }

    // Read the next 4 bytes (Version, Command, Data length)
    // Read n bytes (Data length)
    // Read the checksum byte
    if (valid_header)
    {
        Serial.readBytes(uart_buffer_ + TUYA_COVER_HEADER_LEN, TUYA_COVER_BUFFER_LEN - TUYA_COVER_HEADER_LEN);
        command_.version = uart_buffer_[2];
        command_.command = uart_buffer_[3];
        command_.length = (uart_buffer_[4] << 8) + uart_buffer_[5];
        ESP_LOGV(TAG, "RX: Header = 0x%04X, Version = 0x%02X, Command = 0x%02X, Data length = 0x%04X", command_.header, command_.version, command_.command, command_.length);

        if (command_.length < TUYA_COVER_MAX_LEN)
        {
            Serial.readBytes(command_.value, command_.length);
            ESP_LOGV(TAG, "RX_RAW:");
            for (size_t i = 0; i < command_.length; i++)
            {
                ESP_LOGV(TAG, "%02d: 0x%02X", i, command_.value[i]);
            }
            while (Serial.available() == 0) // Dirty
            {
                //Wait 
            }
            command_.checksum = Serial.read();
            
            ESP_LOGV(TAG, "RX_CHK: 0x%02X", command_.checksum);
            uint8_t calc_checksum = checksum();
            if (calc_checksum == command_.checksum)
            {
                // Clear buffer contents to start with beginning of next command
                memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
                return true;
            }
            else
            {
                memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
                ESP_LOGE(TAG, "Checksum error: Read = 0x%02X != Calculated = 0x%02X", command_.checksum, calc_checksum);
            }
        }
        else
        {
            memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
            ESP_LOGE(TAG, "Command length exceeds limit: %d >= %d", command_.length, TUYA_COVER_MAX_LEN);
        }
    }

    // Do not clear buffer to allow for resume in case of reading partway through header RX
    return false;
}

/*
     * Store the given type, value, and length into the command struct and send
     * it out the serial port. Automatically calculates the checksum as well.
     */
void bcm500ds::write_command(TUYACOVERCommandType command, const uint8_t *value, uint16_t length)
{
    // Copy params into command struct
    command_.header = TUYA_COVER_HEADER;
    command_.version = TUYA_COVER_VERSION;
    command_.command = command;
    command_.length = length;
    ESP_LOGV(TAG, "TX: Header = 0x%04X, Version = 0x%02X, Command = 0x%02X, Data length = 0x%04X", command_.header, command_.version, command_.command, command_.length);
    memcpy(&command_.value, value, length);
    ESP_LOGV(TAG, "TX_RAW");
    for (size_t i = 0; i < command_.length; i++)
    {
        ESP_LOGV(TAG, "%02d: 0x%02X", i, command_.value[i]);
    }
    // Copy struct values into buffer, converting longs to big-endian
    uart_buffer_[0] = command_.header >> 8;
    uart_buffer_[1] = command_.header & 0xFF;
    uart_buffer_[2] = command_.version;
    uart_buffer_[3] = command_.command;
    uart_buffer_[4] = command_.length >> 8;
    uart_buffer_[5] = command_.length & 0xFF;
    command_.checksum = checksum();
    ESP_LOGV(TAG, "TX_CHK: 0x%02X", command_.checksum);
    // Send buffer out via UART
    Serial.write(uart_buffer_, TUYA_COVER_BUFFER_LEN);
    Serial.write(command_.value, command_.length);
    Serial.write(command_.checksum);
    // Clear buffer contents to avoid re-reading our own payload
    memset(uart_buffer_, 0, TUYA_COVER_BUFFER_LEN);
}

/*
     * Calculate checksum from current UART buffer (header+type+length) plus command value.
     */
uint8_t bcm500ds::checksum()
{
    uint8_t checksum = 0;
    for (size_t i = 0; i < TUYA_COVER_BUFFER_LEN; i++)
    {
        checksum += uart_buffer_[i];
    }
    for (size_t i = 0; i < command_.length; i++)
    {
        checksum += command_.value[i];
    }
    return checksum;
}

void bcm500ds::setup()
{
    write_command(TUYA_COVER_QUERY_STATUS, 0, 0);
}

void bcm500ds::loop()
{
    unsigned long currentHeartbeatMillis = millis();
    if (currentHeartbeatMillis - previousHeartbeatMillis >= HEARTBEAT_INTERVAL_MS)
    {
        previousHeartbeatMillis += HEARTBEAT_INTERVAL_MS;
        ESP_LOGI(TAG, "TUYA_COVER_COMMAND = HEARTBEAT");
        write_command(TUYA_COVER_HEARTBEAT, 0, 0);
    }
}

void bcm500ds::command_close()
{
    ESP_LOGI(TAG, "TUYA_COVER_COMMAND = CLOSE");
    write_command(TUYA_COVER_COMMAND, tuya_cover_close, sizeof(tuya_cover_close));
}

void bcm500ds::command_open()
{
    ESP_LOGI(TAG, "TUYA_COVER_COMMAND = OPEN");
    write_command(TUYA_COVER_COMMAND, tuya_cover_open, sizeof(tuya_cover_open));  
}

void bcm500ds::command_gotoPos(uint8_t pos)
{
    tuya_cover_pos[7] = pos;
    ESP_LOGI(TAG, "TUYA_COVER_COMMAND = POS = %d%%", pos);
    write_command(TUYA_COVER_COMMAND, tuya_cover_pos, sizeof(tuya_cover_pos)); 
}

void bcm500ds::command_stop()
{
    write_command(TUYA_COVER_COMMAND, tuya_cover_stop, sizeof(tuya_cover_stop));
}

uint8_t bcm500ds::query_pos()
{
    return (1-((command_.value[7]) / 100.0f));
}

uint8_t bcm500ds::query_value(uint8_t index)
{
    if(index <= TUYA_COVER_MAX_LEN)
    {
        return command_.value[index];
    }
    return 0;
}

uint8_t bcm500ds::query_cmd()
{
    return command_.command;
}



}  // namespace tuya_cover
}  // namespace esphome