/*
 * 自然写智能点阵笔嵌入式固件软件 V1.0
 * coordinate_task.c - 坐标计算任务
 *
 * 功能说明：
 * 1. 从图像帧中解码Anoto点阵图案
 * 2. 计算笔尖在纸面的物理坐标
 * 3. 坐标滤波与去抖（卡尔曼滤波）
 * 4. 坐标打包为BLE传输格式
 */

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "dot_decoder.h"

/* ========== 坐标数据包结构 ========== */

typedef struct {
    uint32_t raw_x;                 /* 原始X坐标（20位精度） */
    uint32_t raw_y;                 /* 原始Y坐标 */
    uint16_t pressure;              /* 压力值（12位） */
    uint32_t timestamp_ms;          /* 时间戳 */
    uint32_t page_id;               /* 页面ID */
    uint8_t  pen_state;             /* 笔状态：0=书写中, 1=笔落下, 2=笔抬起 */
} CoordinatePacket;

/* ========== 卡尔曼滤波器 ========== */

typedef struct {
    float x_estimate;               /* X状态估计值 */
    float y_estimate;               /* Y状态估计值 */
    float x_error;                  /* X估计误差协方差 */
    float y_error;                  /* Y估计误差协方差 */
    float process_noise;            /* 过程噪声 Q */
    float measurement_noise;        /* 测量噪声 R */
    bool initialized;               /* 是否已初始化 */
} KalmanFilter2D;

/* ========== 外部引用 ========== */

extern QueueHandle_t g_image_data_queue;
extern QueueHandle_t g_coordinate_queue;

/* ========== 图像帧元数据结构（与image_capture_task一致） ========== */

typedef struct {
    uint8_t *pixel_buffer;
    uint32_t frame_id;
    uint32_t timestamp_ms;
    uint8_t quality_score;
    uint8_t exposure_value;
    uint16_t pressure_raw;
} ImageFrameMetadata;

/* ========== 静态变量 ========== */

/* 卡尔曼滤波器实例 */
static KalmanFilter2D s_kalman;

/* 上一次有效坐标（用于抖动检测） */
static float s_last_valid_x = 0;
static float s_last_valid_y = 0;

/* 点阵码解码工作缓冲区 */
static uint8_t s_decode_buffer[128];

/* 统计信息 */
static uint32_t s_total_decoded = 0;
static uint32_t s_decode_failures = 0;

/* ========== 卡尔曼滤波实现 ========== */

/**
 * 初始化卡尔曼滤波器
 * @param kf     滤波器实例
 * @param q      过程噪声（越大跟踪越快，噪声越多）
 * @param r      测量噪声（越大滤波越强，延迟越大）
 */
static void kalman_init(KalmanFilter2D *kf, float q, float r) {
    kf->x_estimate = 0;
    kf->y_estimate = 0;
    kf->x_error = 1.0f;
    kf->y_error = 1.0f;
    kf->process_noise = q;
    kf->measurement_noise = r;
    kf->initialized = false;
}

/**
 * 卡尔曼滤波更新
 * @param kf            滤波器实例
 * @param measured_x    测量X值
 * @param measured_y    测量Y值
 * @param out_x         滤波后X输出
 * @param out_y         滤波后Y输出
 */
static void kalman_update(KalmanFilter2D *kf, float measured_x, float measured_y,
                           float *out_x, float *out_y) {
    if (!kf->initialized) {
        /* 第一次测量，直接使用测量值 */
        kf->x_estimate = measured_x;
        kf->y_estimate = measured_y;
        kf->initialized = true;
        *out_x = measured_x;
        *out_y = measured_y;
        return;
    }

    /* 预测步骤：状态不变模型（笔的位置预测 = 上一次估计） */
    float x_pred = kf->x_estimate;
    float y_pred = kf->y_estimate;
    float x_err_pred = kf->x_error + kf->process_noise;
    float y_err_pred = kf->y_error + kf->process_noise;

    /* 更新步骤：计算卡尔曼增益 */
    float kx = x_err_pred / (x_err_pred + kf->measurement_noise);
    float ky = y_err_pred / (y_err_pred + kf->measurement_noise);

    /* 融合预测与测量 */
    kf->x_estimate = x_pred + kx * (measured_x - x_pred);
    kf->y_estimate = y_pred + ky * (measured_y - y_pred);

    /* 更新误差协方差 */
    kf->x_error = (1.0f - kx) * x_err_pred;
    kf->y_error = (1.0f - ky) * y_err_pred;

    *out_x = kf->x_estimate;
    *out_y = kf->y_estimate;
}

/**
 * 重置卡尔曼滤波器（新笔画开始时调用）
 */
static void kalman_reset(KalmanFilter2D *kf) {
    kf->initialized = false;
    kf->x_error = 1.0f;
    kf->y_error = 1.0f;
}

/* ========== 抖动检测 ========== */

/**
 * 检测坐标是否为抖动（笔静止时传感器的微小抖动）
 * 如果两次坐标之间的距离小于阈值，视为抖动并丢弃
 *
 * @param x             当前X坐标
 * @param y             当前Y坐标
 * @param threshold     抖动阈值（坐标单位）
 * @return true表示是抖动，应丢弃
 */
static bool is_jitter(float x, float y, float threshold) {
    float dx = x - s_last_valid_x;
    float dy = y - s_last_valid_y;
    float distance_sq = dx * dx + dy * dy;

    return (distance_sq < threshold * threshold);
}

/* ========== 点阵码图像解码 ========== */

/**
 * 从32x32灰度图像中解码Anoto点阵图案
 *
 * 解码步骤：
 * 1. 二值化：将灰度图转为黑白图
 * 2. 点检测：定位图案中的各个墨点位置
 * 3. 网格对齐：将检测到的点对齐到规则网格
 * 4. 编码读取：根据点相对于网格中心的偏移方向读取编码值
 * 5. 坐标计算：将编码序列映射为全局坐标
 *
 * @param pixels       32x32灰度图像数据
 * @param quality      图像质量评分
 * @param out_x        解码输出X坐标
 * @param out_y        解码输出Y坐标
 * @param out_page_id  解码输出页面ID
 * @return 0成功, -1解码失败
 */
static int decode_dot_pattern(const uint8_t *pixels, uint8_t quality,
                               uint32_t *out_x, uint32_t *out_y,
                               uint32_t *out_page_id) {
    /* 步骤1：自适应二值化 */
    uint8_t threshold = 128;

    /* 根据图像亮度动态调整阈值（Otsu方法简化版） */
    uint32_t histogram[256] = {0};
    uint16_t i;
    for (i = 0; i < SENSOR_PIXELS; i++) {
        histogram[pixels[i]]++;
    }

    /* 寻找双峰之间的谷值作为阈值 */
    uint32_t total = SENSOR_PIXELS;
    uint32_t sum = 0;
    for (i = 0; i < 256; i++) {
        sum += i * histogram[i];
    }

    uint32_t sum_bg = 0;
    uint32_t weight_bg = 0;
    float max_variance = 0;

    for (i = 0; i < 256; i++) {
        weight_bg += histogram[i];
        if (weight_bg == 0) continue;

        uint32_t weight_fg = total - weight_bg;
        if (weight_fg == 0) break;

        sum_bg += i * histogram[i];
        float mean_bg = (float)sum_bg / weight_bg;
        float mean_fg = (float)(sum - sum_bg) / weight_fg;

        float diff = mean_bg - mean_fg;
        float variance = (float)weight_bg * weight_fg * diff * diff;

        if (variance > max_variance) {
            max_variance = variance;
            threshold = (uint8_t)i;
        }
    }

    /* 步骤2：二值化并检测墨点中心 */
    uint8_t dot_count = 0;
    int16_t dot_x[64], dot_y[64];  /* 最多检测64个点 */

    for (int row = 1; row < SENSOR_HEIGHT - 1; row++) {
        for (int col = 1; col < SENSOR_WIDTH - 1; col++) {
            uint8_t center = pixels[row * SENSOR_WIDTH + col];
            if (center < threshold) {
                /* 暗像素，检查是否为局部极小值（简单的点中心检测） */
                uint8_t up    = pixels[(row - 1) * SENSOR_WIDTH + col];
                uint8_t down  = pixels[(row + 1) * SENSOR_WIDTH + col];
                uint8_t left  = pixels[row * SENSOR_WIDTH + (col - 1)];
                uint8_t right = pixels[row * SENSOR_WIDTH + (col + 1)];

                if (center <= up && center <= down &&
                    center <= left && center <= right) {
                    if (dot_count < 64) {
                        dot_x[dot_count] = col;
                        dot_y[dot_count] = row;
                        dot_count++;
                    }
                }
            }
        }
    }

    /* 至少需要检测到4个点才能解码 */
    if (dot_count < 4) {
        return -1;
    }

    /* 步骤3-5：调用点阵码解码器（核心算法在dot_decoder模块中） */
    DotDecodeResult result;
    int ret = dot_decoder_process(dot_x, dot_y, dot_count, &result);

    if (ret == 0) {
        *out_x = result.coordinate_x;
        *out_y = result.coordinate_y;
        *out_page_id = result.page_id;
        return 0;
    }

    return -1;
}

/* ========== 压力值处理 ========== */

/**
 * 处理原始ADC压力值
 * 12位ADC → 归一化并应用非线性映射
 *
 * @param raw_adc  原始ADC值（0-4095）
 * @return 处理后的压力值（0-4095，非线性映射后）
 */
static uint16_t process_pressure(uint16_t raw_adc) {
    /* 去除静态偏移（笔尖自重产生的基础压力） */
    const uint16_t offset = 200;
    if (raw_adc < offset) {
        return 0;
    }
    uint16_t adjusted = raw_adc - offset;

    /* 非线性映射（平方根曲线，使轻触更灵敏） */
    float normalized = (float)adjusted / (4095.0f - offset);
    float mapped = sqrtf(normalized);

    return (uint16_t)(mapped * 4095);
}

/* ========== 坐标计算主任务 ========== */

/**
 * 坐标计算任务（FreeRTOS任务函数）
 *
 * 运行流程：
 * 1. 从图像数据队列接收帧元数据
 * 2. 解码点阵图案获得原始坐标
 * 3. 卡尔曼滤波去噪
 * 4. 抖动检测
 * 5. 坐标打包并放入BLE发送队列
 */
void coordinate_task(void *pvParameters) {
    (void)pvParameters;

    ImageFrameMetadata frame;
    CoordinatePacket packet;

    /* 初始化卡尔曼滤波器 */
    /* Q=0.1 跟踪速度适中, R=0.5 中等滤波强度 */
    kalman_init(&s_kalman, 0.1f, 0.5f);

    /* 抖动检测阈值（坐标单位，约0.1mm） */
    const float jitter_threshold = 3.0f;

    while (1) {
        /* 阻塞等待图像帧数据 */
        if (xQueueReceive(g_image_data_queue, &frame, portMAX_DELAY) != pdTRUE) {
            continue;
        }

        /* 解码点阵图案 */
        uint32_t raw_x, raw_y, page_id;
        int decode_ret = decode_dot_pattern(frame.pixel_buffer, frame.quality_score,
                                             &raw_x, &raw_y, &page_id);

        if (decode_ret != 0) {
            s_decode_failures++;
            continue;
        }
        s_total_decoded++;

        /* 卡尔曼滤波 */
        float filtered_x, filtered_y;
        kalman_update(&s_kalman, (float)raw_x, (float)raw_y,
                      &filtered_x, &filtered_y);

        /* 抖动检测 */
        if (is_jitter(filtered_x, filtered_y, jitter_threshold)) {
            continue;   /* 丢弃抖动数据 */
        }

        /* 更新最后有效坐标 */
        s_last_valid_x = filtered_x;
        s_last_valid_y = filtered_y;

        /* 处理压力值 */
        uint16_t pressure = process_pressure(frame.pressure_raw);

        /* 构建坐标数据包 */
        packet.raw_x = (uint32_t)filtered_x;
        packet.raw_y = (uint32_t)filtered_y;
        packet.pressure = pressure;
        packet.timestamp_ms = frame.timestamp_ms;
        packet.page_id = page_id;
        packet.pen_state = 0;  /* 书写中 */

        /* 放入BLE发送队列（非阻塞，满则丢弃最老的） */
        if (xQueueSend(g_coordinate_queue, &packet, 0) != pdTRUE) {
            /* 队列满，读出一个旧数据再写入 */
            CoordinatePacket dummy;
            xQueueReceive(g_coordinate_queue, &dummy, 0);
            xQueueSend(g_coordinate_queue, &packet, 0);
        }
    }
}
