#include "meter_zigbee.h"

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_system.h"
#include "driver/uart.h"
#include "driver/gpio.h"

#define TAG "meter_zigbee"

// UART config
#define UART_PORT       UART_NUM_1
#define TXD_PIN         GPIO_NUM_17
#define RXD_PIN         GPIO_NUM_16
#define UART_BUF_SIZE   128
#define RX_FRAME_SIZE   14

// Zigbee Attribute IDs
#define ATTR_CURRENT_L1         0x0006
#define ATTR_CURRENT_L2         0x0007
#define ATTR_CURRENT_L3         0x0008

#define ATTR_VOLTAGE_L1         0x0266
#define ATTR_CURRENT_L1_ALT     0x0267
#define ATTR_POWER_L1           0x0268

#define ATTR_VOLTAGE_L2         0x0269
#define ATTR_CURRENT_L2_ALT     0x026A
#define ATTR_POWER_L2           0x026B

#define ATTR_VOLTAGE_L3         0x026C
#define ATTR_CURRENT_L3_ALT     0x026D
#define ATTR_POWER_L3           0x026E

#define ATTR_FREQUENCY          0x0265
#define ATTR_POWER_FACTOR       0x020F
#define ATTR_TOTAL_ENERGY       0x0201

#define PHASE_COUNT             3
#define PHASE_L1                0
#define PHASE_L2                1
#define PHASE_L3                2

// Internal meter state
typedef struct {
    float vrms[PHASE_COUNT];
    float irms[PHASE_COUNT];
    int watt[PHASE_COUNT];
    int var[PHASE_COUNT];
    int va[PHASE_COUNT];
    float frequency;
    float power_factor;
    float total_energy;
} meter_zigbee_data_t;

static meter_zigbee_data_t meter_data = {0};
static SemaphoreHandle_t meter_mutex = NULL;
static TaskHandle_t meter_task = NULL;

// ---------- Utils ----------

static inline float decode_float(const uint8_t *buf) {
    return (buf[9] + (buf[8] << 8) + (buf[7] << 16)) / 100.0f;
}

static float meter_data_get_float(const float *arr, uint8_t phase) {
    float val = 0.0f;
    if (phase >= PHASE_COUNT) return 0;
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        val = arr[phase];
        xSemaphoreGive(meter_mutex);
    }
    return val;
}

static int meter_data_get_int(const int *arr, uint8_t phase) {
    int val = 0;
    if (phase >= PHASE_COUNT) return 0;
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        val = arr[phase];
        xSemaphoreGive(meter_mutex);
    }
    return val;
}

static void meter_data_clear(void) {
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        memset(&meter_data, 0, sizeof(meter_data));
        xSemaphoreGive(meter_mutex);
    }
}

// ---------- Frame Handler ----------

static void handle_zigbee_frame(const uint8_t *buf) {
    uint16_t attr = buf[1] | (buf[2] << 8);
    uint8_t size = buf[4];

    if (size != 8) {
        ESP_LOGW(TAG, "Unexpected data size: %d", size);
        return;
    }

    float value = decode_float(buf);
    ESP_LOGI(TAG, "Attr 0x%04X = %.2f", attr, value);

    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        switch (attr) {
            case ATTR_CURRENT_L1:
            case ATTR_CURRENT_L1_ALT:
                meter_data.irms[0] = value;
                break;

            case ATTR_CURRENT_L2:
            case ATTR_CURRENT_L2_ALT:
                meter_data.irms[1] = value;
                break;

            case ATTR_CURRENT_L3:
            case ATTR_CURRENT_L3_ALT:
                meter_data.irms[2] = value;
                break;

            case ATTR_VOLTAGE_L1: meter_data.vrms[0] = value; break;
            case ATTR_VOLTAGE_L2: meter_data.vrms[1] = value; break;
            case ATTR_VOLTAGE_L3: meter_data.vrms[2] = value; break;

            case ATTR_POWER_L1: meter_data.watt[0] = (int)value; break;
            case ATTR_POWER_L2: meter_data.watt[1] = (int)value; break;
            case ATTR_POWER_L3: meter_data.watt[2] = (int)value; break;

            case ATTR_POWER_FACTOR: meter_data.power_factor = value; break;
            case ATTR_FREQUENCY: meter_data.frequency = value; break;
            case ATTR_TOTAL_ENERGY: meter_data.total_energy = value; break;

            default:
                ESP_LOGW(TAG, "Unknown attr: 0x%04X", attr);
                break;
        }
        xSemaphoreGive(meter_mutex);
    }
}

// ---------- Task ----------

static void meter_task_func(void *param) {
    uint8_t *buf = malloc(RX_FRAME_SIZE);
    if (!buf) {
        ESP_LOGE(TAG, "Memory allocation failed");
        vTaskDelete(NULL);
        return;
    }

    ESP_LOGI(TAG, "Zigbee meter task started");

    while (1) {
        int len = uart_read_bytes(UART_PORT, buf, RX_FRAME_SIZE, pdMS_TO_TICKS(1000));
        if (len >= 10) {
            handle_zigbee_frame(buf);
        }
    }

    free(buf);
    vTaskDelete(NULL);
}

// ---------- Public API (meter.h) ----------

esp_err_t meter_init(void) {
    ESP_LOGI(TAG, "Initializing Zigbee meter");

    if (!meter_mutex) {
        meter_mutex = xSemaphoreCreateMutex();
        if (!meter_mutex) return ESP_ERR_NO_MEM;
    }

    meter_data_clear();

    uart_config_t config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT
    };

    ESP_ERROR_CHECK(uart_param_config(UART_PORT, &config));
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT, UART_BUF_SIZE * 2, 0, 0, NULL, 0));

    return ESP_OK;
}

esp_err_t meter_start(void) {
    if (meter_task) return ESP_ERR_INVALID_STATE;

    xTaskCreate(meter_task_func, "meter_zigbee_task", 4096, NULL, 5, &meter_task);
    return ESP_OK;
}

void meter_stop(void) {
    if (meter_task) {
        vTaskDelete(meter_task);
        meter_task = NULL;
    }

    uart_driver_delete(UART_PORT);

    if (meter_mutex) {
        vSemaphoreDelete(meter_mutex);
        meter_mutex = NULL;
    }
}

bool meter_is_running(void) {
    return meter_task != NULL;
}

void meter_clear_data(void) {
    meter_data_clear();
}

// ---------- RMS Current ----------
float meter_get_irms_l1(void) { return meter_data_get_float(meter_data.irms, PHASE_L1); }
float meter_get_irms_l2(void) { return meter_data_get_float(meter_data.irms, PHASE_L2); }
float meter_get_irms_l3(void) { return meter_data_get_float(meter_data.irms, PHASE_L3); }

// ---------- RMS Voltage ----------
float meter_get_vrms_l1(void) { return meter_data_get_float(meter_data.vrms, PHASE_L1); }
float meter_get_vrms_l2(void) { return meter_data_get_float(meter_data.vrms, PHASE_L2); }
float meter_get_vrms_l3(void) { return meter_data_get_float(meter_data.vrms, PHASE_L3); }

// ---------- Active Power ----------
int meter_get_watt_l1(void) { return meter_data_get_int(meter_data.watt, PHASE_L1); }
int meter_get_watt_l2(void) { return meter_data_get_int(meter_data.watt, PHASE_L2); }
int meter_get_watt_l3(void) { return meter_data_get_int(meter_data.watt, PHASE_L3); }

// ---------- Reactive Power ----------
int meter_get_var_l1(void) { return meter_data_get_int(meter_data.var, PHASE_L1); }
int meter_get_var_l2(void) { return meter_data_get_int(meter_data.var, PHASE_L2); }
int meter_get_var_l3(void) { return meter_data_get_int(meter_data.var, PHASE_L3); }

// ---------- Apparent Power ----------
int meter_get_va_l1(void) { return meter_data_get_int(meter_data.va, PHASE_L1); }
int meter_get_va_l2(void) { return meter_data_get_int(meter_data.va, PHASE_L2); }
int meter_get_va_l3(void) { return meter_data_get_int(meter_data.va, PHASE_L3); }

// ---------- Extra Data ----------
float meter_get_frequency(void) {
    float v = 0;
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        v = meter_data.frequency;
        xSemaphoreGive(meter_mutex);
    }
    return v;
}

float meter_get_power_factor(void) {
    float v = 0;
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        v = meter_data.power_factor;
        xSemaphoreGive(meter_mutex);
    }
    return v;
}

float meter_get_total_energy(void) {
    float v = 0;
    if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        v = meter_data.total_energy;
        xSemaphoreGive(meter_mutex);
    }
    return v;
}
