/**
 * @file nfc_manager.c
 * @brief NFC Manager Implementation - COMPLETE PN532 Driver!
 * 
 * Full implementation:
 * - PN532 I2C communication
 * - Tag detection & reading
 * - ROM mapping from JSON
 * - UID to ROM path lookup
 */

#include <string.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "cJSON.h"
#include "esp_vfs_fat.h"
#include "nfc_manager.h"
#include "hardware_config.h"

static const char *TAG = "NFC";

// PN532 I2C Commands
#define PN532_PREAMBLE                      0x00
#define PN532_STARTCODE1                    0x00
#define PN532_STARTCODE2                    0xFF
#define PN532_POSTAMBLE                     0x00

#define PN532_HOSTTOPN532                   0xD4
#define PN532_PN532TOHOST                   0xD5

// PN532 Commands
#define PN532_COMMAND_GETFIRMWAREVERSION    0x02
#define PN532_COMMAND_SAMCONFIGURATION      0x14
#define PN532_COMMAND_INLISTPASSIVETARGET   0x4A

// PN532 Mifare Commands
#define MIFARE_CMD_AUTH_A                   0x60
#define MIFARE_CMD_AUTH_B                   0x61
#define MIFARE_CMD_READ                     0x30
#define MIFARE_CMD_WRITE                    0xA0

#define PN532_I2C_TIMEOUT_MS                1000
#define PN532_I2C_READY                     0x01

// ROM mapping storage
#define MAX_ROM_MAPPINGS                    32
static nfc_rom_mapping_t rom_mappings[MAX_ROM_MAPPINGS];
static int rom_mapping_count = 0;

/**
 * @brief Initialize I2C for PN532
 */
static esp_err_t pn532_i2c_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = NFC_I2C_SDA,
        .scl_io_num = NFC_I2C_SCL,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = NFC_I2C_FREQ_HZ,
    };
    
    ESP_ERROR_CHECK(i2c_param_config(NFC_I2C_NUM, &conf));
    ESP_ERROR_CHECK(i2c_driver_install(NFC_I2C_NUM, conf.mode, 0, 0, 0));
    
    return ESP_OK;
}

/**
 * @brief Wait for PN532 to be ready
 */
static esp_err_t pn532_wait_ready(uint32_t timeout_ms)
{
    uint8_t status;
    uint32_t start = xTaskGetTickCount() * portTICK_PERIOD_MS;
    
    while ((xTaskGetTickCount() * portTICK_PERIOD_MS - start) < timeout_ms) {
        if (i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR, 
                                        &status, 1, pdMS_TO_TICKS(100)) == ESP_OK) {
            if (status == PN532_I2C_READY) {
                return ESP_OK;
            }
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    
    return ESP_ERR_TIMEOUT;
}

/**
 * @brief Write command to PN532
 */
static esp_err_t pn532_write_command(const uint8_t *cmd, size_t cmd_len)
{
    uint8_t checksum = 0;
    uint8_t frame[256];
    size_t frame_len = 0;
    
    // Build frame
    frame[frame_len++] = PN532_PREAMBLE;
    frame[frame_len++] = PN532_STARTCODE1;
    frame[frame_len++] = PN532_STARTCODE2;
    
    frame[frame_len++] = cmd_len + 1;  // Length
    frame[frame_len++] = ~(cmd_len + 1) + 1;  // Length checksum
    
    frame[frame_len++] = PN532_HOSTTOPN532;
    checksum += PN532_HOSTTOPN532;
    
    for (size_t i = 0; i < cmd_len; i++) {
        frame[frame_len++] = cmd[i];
        checksum += cmd[i];
    }
    
    frame[frame_len++] = ~checksum + 1;  // Data checksum
    frame[frame_len++] = PN532_POSTAMBLE;
    
    return i2c_master_write_to_device(NFC_I2C_NUM, NFC_I2C_ADDR, 
                                     frame, frame_len, pdMS_TO_TICKS(100));
}

/**
 * @brief Read response from PN532
 */
static esp_err_t pn532_read_response(uint8_t *response, size_t *response_len, 
                                     uint32_t timeout_ms)
{
    esp_err_t ret;
    uint8_t frame[256];
    
    // Wait for ready
    ret = pn532_wait_ready(timeout_ms);
    if (ret != ESP_OK) {
        return ret;
    }
    
    // Read frame
    ret = i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR, 
                                     frame, 255, pdMS_TO_TICKS(100));
    if (ret != ESP_OK) {
        return ret;
    }
    
    // Skip RDY byte
    size_t idx = 1;
    
    // Check preamble
    if (frame[idx++] != PN532_PREAMBLE ||
        frame[idx++] != PN532_STARTCODE1 ||
        frame[idx++] != PN532_STARTCODE2) {
        return ESP_ERR_INVALID_RESPONSE;
    }
    
    // Get length
    uint8_t len = frame[idx++];
    uint8_t len_checksum = frame[idx++];
    
    if ((len + len_checksum) != 0) {
        return ESP_ERR_INVALID_RESPONSE;
    }
    
    // Check TFI
    if (frame[idx++] != PN532_PN532TOHOST) {
        return ESP_ERR_INVALID_RESPONSE;
    }
    
    // Copy response data
    *response_len = len - 1;
    memcpy(response, &frame[idx], *response_len);
    
    return ESP_OK;
}

/**
 * @brief Get PN532 firmware version
 */
static esp_err_t pn532_get_firmware_version(uint32_t *version)
{
    uint8_t cmd[] = {PN532_COMMAND_GETFIRMWAREVERSION};
    uint8_t response[32];
    size_t response_len;
    
    ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
    ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
    
    if (response_len < 5 || response[0] != (PN532_COMMAND_GETFIRMWAREVERSION + 1)) {
        return ESP_FAIL;
    }
    
    *version = (response[1] << 24) | (response[2] << 16) | 
               (response[3] << 8) | response[4];
    
    return ESP_OK;
}

/**
 * @brief Configure PN532 SAM (Security Access Module)
 */
static esp_err_t pn532_sam_configuration(void)
{
    uint8_t cmd[] = {
        PN532_COMMAND_SAMCONFIGURATION,
        0x01,  // Normal mode
        0x14,  // Timeout 50ms * 20 = 1 second
        0x01   // Use IRQ pin
    };
    uint8_t response[32];
    size_t response_len;
    
    ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
    ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
    
    return ESP_OK;
}

/**
 * @brief Detect passive NFC target (tag)
 */
static esp_err_t pn532_read_passive_target(uint8_t *uid, uint8_t *uid_len)
{
    uint8_t cmd[] = {
        PN532_COMMAND_INLISTPASSIVETARGET,
        0x01,  // Max 1 card
        0x00   // 106 kbps type A (ISO14443A)
    };
    uint8_t response[32];
    size_t response_len;
    
    esp_err_t ret = pn532_write_command(cmd, sizeof(cmd));
    if (ret != ESP_OK) {
        return ret;
    }
    
    ret = pn532_read_response(response, &response_len, 1000);
    if (ret != ESP_OK) {
        return ret;
    }
    
    // Check if tag found
    if (response_len < 8 || response[0] != (PN532_COMMAND_INLISTPASSIVETARGET + 1)) {
        return ESP_ERR_NOT_FOUND;
    }
    
    uint8_t num_tags = response[1];
    if (num_tags != 1) {
        return ESP_ERR_NOT_FOUND;
    }
    
    // Get UID length
    *uid_len = response[6];
    if (*uid_len > 7) {
        *uid_len = 7;
    }
    
    // Copy UID
    memcpy(uid, &response[7], *uid_len);
    
    return ESP_OK;
}

/**
 * @brief Load ROM mappings from JSON file
 */
static esp_err_t nfc_load_mappings(void)
{
    FILE *f = fopen("/sdcard/nfc_roms.json", "r");
    if (f == NULL) {
        ESP_LOGW(TAG, "nfc_roms.json not found, using empty mappings");
        return ESP_OK;
    }
    
    // Read file
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);
    
    char *json_str = malloc(fsize + 1);
    if (json_str == NULL) {
        fclose(f);
        return ESP_ERR_NO_MEM;
    }
    
    fread(json_str, 1, fsize, f);
    json_str[fsize] = 0;
    fclose(f);
    
    // Parse JSON
    cJSON *root = cJSON_Parse(json_str);
    free(json_str);
    
    if (root == NULL) {
        ESP_LOGE(TAG, "Failed to parse nfc_roms.json");
        return ESP_FAIL;
    }
    
    cJSON *mappings_array = cJSON_GetObjectItem(root, "mappings");
    if (!cJSON_IsArray(mappings_array)) {
        cJSON_Delete(root);
        return ESP_FAIL;
    }
    
    // Load mappings
    rom_mapping_count = 0;
    cJSON *mapping = NULL;
    cJSON_ArrayForEach(mapping, mappings_array) {
        if (rom_mapping_count >= MAX_ROM_MAPPINGS) {
            break;
        }
        
        cJSON *uid_str = cJSON_GetObjectItem(mapping, "tag_uid");
        cJSON *rom_path = cJSON_GetObjectItem(mapping, "rom_path");
        
        if (cJSON_IsString(uid_str) && cJSON_IsString(rom_path)) {
            // Parse UID (format: "04:12:34:56:78:9A:B0")
            const char *uid = uid_str->valuestring;
            uint8_t uid_bytes[7] = {0};
            int uid_len = 0;
            
            for (int i = 0; i < 7 && *uid; i++) {
                sscanf(uid, "%02hhx", &uid_bytes[i]);
                uid_len++;
                uid += 2;
                if (*uid == ':') uid++;
            }
            
            // Store mapping
            memcpy(rom_mappings[rom_mapping_count].tag_uid, uid_bytes, 7);
            strncpy(rom_mappings[rom_mapping_count].rom_path, 
                   rom_path->valuestring, NFC_MAX_ROM_NAME - 1);
            rom_mapping_count++;
        }
    }
    
    cJSON_Delete(root);
    ESP_LOGI(TAG, "Loaded %d ROM mappings", rom_mapping_count);
    
    return ESP_OK;
}

/**
 * @brief Find ROM path for given UID
 */
static const char* nfc_find_rom_for_uid(const uint8_t *uid, uint8_t uid_len)
{
    for (int i = 0; i < rom_mapping_count; i++) {
        if (memcmp(rom_mappings[i].tag_uid, uid, uid_len) == 0) {
            return rom_mappings[i].rom_path;
        }
    }
    return NULL;
}

// ===========================================
// Public API
// ===========================================

esp_err_t nfc_manager_init(void)
{
    ESP_LOGI(TAG, "Initializing NFC Manager...");
    
    // Initialize I2C
    ESP_ERROR_CHECK(pn532_i2c_init());
    
    // Wait a bit for PN532 to wake up
    vTaskDelay(pdMS_TO_TICKS(100));
    
    // Get firmware version
    uint32_t version;
    esp_err_t ret = pn532_get_firmware_version(&version);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to communicate with PN532!");
        return ret;
    }
    
    ESP_LOGI(TAG, "PN532 Firmware v%d.%d", 
             (version >> 16) & 0xFF, (version >> 8) & 0xFF);
    
    // Configure SAM
    ESP_ERROR_CHECK(pn532_sam_configuration());
    
    // Load ROM mappings
    nfc_load_mappings();
    
    ESP_LOGI(TAG, "✓ NFC Manager initialized");
    
    return ESP_OK;
}

bool nfc_manager_tag_present(void)
{
    uint8_t uid[7];
    uint8_t uid_len;
    
    return (pn532_read_passive_target(uid, &uid_len) == ESP_OK);
}

esp_err_t nfc_manager_get_rom(char *rom_path, size_t max_len)
{
    uint8_t uid[7];
    uint8_t uid_len;
    
    // Read tag
    esp_err_t ret = pn532_read_passive_target(uid, &uid_len);
    if (ret != ESP_OK) {
        return ret;
    }
    
    // Log UID
    ESP_LOGI(TAG, "Tag UID: %02X:%02X:%02X:%02X:%02X:%02X:%02X",
             uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6]);
    
    // Find ROM mapping
    const char *mapped_rom = nfc_find_rom_for_uid(uid, uid_len);
    if (mapped_rom == NULL) {
        ESP_LOGW(TAG, "No ROM mapping found for this tag");
        return ESP_ERR_NOT_FOUND;
    }
    
    // Copy ROM path
    strncpy(rom_path, mapped_rom, max_len - 1);
    rom_path[max_len - 1] = '\0';
    
    ESP_LOGI(TAG, "ROM: %s", rom_path);
    
    return ESP_OK;
}

