#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <U8g2_for_Adafruit_GFX.h>
#include <WiFi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <math.h>
#include "ad7606.h"
#include "init.h"
#include "ui.h"
#include "utils.h"

U8G2_FOR_ADAFRUIT_GFX u8g2;  // 给 Adafruit_GFX 套一层 U8g2 的“文字引擎”
using namespace esp32;

#define TFT_MOSI 16
#define TFT_SCLK 15
#define TFT_CS 18
#define TFT_DC 17
#define TFT_RST -1

// 屏幕尺寸为 128x160
Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST);

using namespace esp32::utils;
using namespace esp32::ui;

const int BTN_LEFT = 9;    // 左按钮
const int BTN_RIGHT = 11;  // 右按钮
const int BTN_UP = 10;     // 上按钮

// 颜色宏兼容已由 utils.h 提供

// 应用状态
struct AppState {
    // 环形缓冲：宽度=屏幕宽；时间窗口=3秒
    static const int GraphW = 128;  // 与屏幕宽度一致
    int16_t samples[GraphW];
    int writeIdx = 0;                       // 下一个写入位置
    int sampleCount = 0;                    // 已写入样本（<=GraphW）
    int16_t lastRaw = 0;                    // 最新原始码
    uint32_t lastSampleMs = 0;              // 上一次采样时间
    static const uint32_t WindowMs = 3000;  // 3 秒窗口
    static const uint32_t SampleIntervalMs = WindowMs / GraphW;  // 约 23ms

    // 标定/拟合：mN = a*code + b（至少2点生效）
    float target_mN[4];     // 预设档位：0, 3.6, 7.2, 10.8
    float capturedCode[4];  // 各档记录到的原始码
    bool captured[4];       // 是否已记录
    int presetIdx = 0;      // 当前要记录的档位索引
    int capturedCount = 0;  // 已记录的档位数
    bool fitReady = false;  // 已具备>=2点
    float fitA = 0.0f;      // mN = a*code + b
    float fitB = 0.0f;

    // 录制
    static const int RecCap = 16384;
    int16_t recBuf[RecCap];
    int recLen = 0;
    bool recActive = false;
    uint32_t recStartMs = 0;
    uint32_t recEndMs = 0;

    // 上传状态提示
    char statusMsg[64];
    uint32_t statusUntilMs = 0;
    uint16_t statusColor = WHITE;
};

static UI appUI;
static AppState app;

static bool prevLeft = false;
static bool prevRight = false;
static bool prevUp = false;

// 布局参数
static const int SCREEN_W = 128;
static const int SCREEN_H = 160;
static const int ICON_W = 48;   // 图标宽度
static const int ICON_H = 48;   // 图标高度
static const int SPACING = 72;  // 图标中心间距（略大于宽度，保证边缘留白）
static const int CENTER_X = (SCREEN_W - ICON_W) / 2;  // 中心图标左上角 X
static const int BASE_Y = 40;        // 图标顶端 Y（上方留空间给标题）
static const int LABEL_MARGIN = 12;  // 图标与文字竖向间距

static const int CHAR_W = 14;
static const int CHAR_H = 14;

// 采样队列：将高速采样与 UI 渲染解耦
static QueueHandle_t g_sampleQ = nullptr;  // 队列元素：int16_t（CH0 原始码）

// 采样任务：固定频率触发 AD7606 读取，推入队列
static void samplerTask(void* param) {
    // 目标采样率（可按需调高/调低）。
    const TickType_t period = pdMS_TO_TICKS(4);
    TickType_t last = xTaskGetTickCount();
    for (;;) {
        int16_t s = ad7606::readCH0();
        if (g_sampleQ) {
            // 队列满则丢弃最旧样本：先试一次无阻塞发送，失败则腾挪1个旧样本
            if (xQueueSendToBack(g_sampleQ, &s, 0) != pdTRUE) {
                int16_t dummy;
                xQueueReceive(g_sampleQ, &dummy, 0);
                xQueueSendToBack(g_sampleQ, &s, 0);
            }
        }
        vTaskDelayUntil(&last, period);
    }
}

// 工具：推入一条新样本（原码）
static inline void pushSample(AppState& s, int16_t code, uint32_t nowMs) {
    // 控制写入速率，保持一屏=3秒
    if (nowMs - s.lastSampleMs < AppState::SampleIntervalMs)
        return;
    s.lastSampleMs = nowMs;
    s.samples[s.writeIdx] = code;
    s.writeIdx = (s.writeIdx + 1) % AppState::GraphW;
    if (s.sampleCount < AppState::GraphW)
        s.sampleCount++;
    s.lastRaw = code;
}

// 发送录制数据到服务器（JSON）
static void postRecording(AppState& s) {
    if (s.recLen <= 0) {
        s.statusColor = YELLOW;
        snprintf(s.statusMsg, sizeof(s.statusMsg), "无数据可上传");
        s.statusUntilMs = millis() + 2000;
        return;
    }
    if (WiFi.status() != WL_CONNECTED) {
        s.statusColor = RED;
        snprintf(s.statusMsg, sizeof(s.statusMsg), "未连接WiFi");
        s.statusUntilMs = millis() + 2000;
        return;
    }

    String payload;
    // 估算预留空间：每个数最多6字符+逗号
    payload.reserve(32 + s.recLen * 7 + 48);
    payload += "{";
    payload += "\"code\":[";
    for (int i = 0; i < s.recLen; ++i) {
        payload += String((int)s.recBuf[i]);
        if (i + 1 < s.recLen)
            payload += ",";
    }
    payload += "]";
    // 附带录制起止时间（毫秒）
    payload += ",\"recStartMs\":";
    payload += String(s.recStartMs);
    payload += ",\"recEndMs\":";
    payload += String(s.recEndMs);
    if (s.fitReady) {
        payload += ",\"fit\":{\"a\":";
        payload += String(s.fitA, 6);
        payload += ",\"b\":";
        payload += String(s.fitB, 6);
        payload += "}";
    }
    payload += "}";
    printf("上传数据：%s\n", payload.c_str());
    HTTPClient http;
    http.begin(esp32::init::getBackendUrl());
    http.addHeader("Content-Type", "application/json");
    http.setTimeout(10000);
    int code = http.POST(payload);
    String resp = http.getString();
    http.end();

    if (code > 0) {
        // 尝试解析 { success: true, id, timestamp }
        JsonDocument doc;
        DeserializationError err = deserializeJson(doc, resp);
        bool ok = false;
        String idStr = "";
        String tsStr = "";
        if (!err) {
            ok = doc["success"].as<bool>();
            idStr = doc["id"].as<String>();
            tsStr = doc["timestamp"].as<String>();
        } else {
            ok = resp.indexOf("\"success\":true") >= 0;  // 兜底
        }
        if (ok) {
            s.statusColor = GREEN;
            snprintf(s.statusMsg, sizeof(s.statusMsg), "上传成功");
            s.statusUntilMs = millis() + 3000;
        } else {
            s.statusColor = RED;
            snprintf(s.statusMsg, sizeof(s.statusMsg), "上传失败(%d)", code);
            s.statusUntilMs = millis() + 3000;
        }
    } else {
        s.statusColor = RED;
        snprintf(s.statusMsg, sizeof(s.statusMsg), "HTTP错误(%d)", code);
        s.statusUntilMs = millis() + 3000;
    }
}

// 根据状态输出显示列表
static void buildScene(void* ctx, DisplayList& out) {
    auto* st = static_cast<AppState*>(ctx);

    // 背景整屏清除
    out.addFillRect(1, 0, 0, SCREEN_W, SCREEN_H, BLACK);

    // 第一行：显示原始码 并在拟合可用时附加 mN
    char line[64];
    if (st->fitReady) {
        float currM = st->fitA * (float)st->lastRaw + st->fitB;
        snprintf(line, sizeof(line), "%d it  %.3f mN", st->lastRaw, currM);
    } else {
        snprintf(line, sizeof(line), "%d it", st->lastRaw);
    }
    out.addText(2, SCREEN_W / 2, 10, WHITE, line, true);
    // 右上角录制提示：红色圆点（空心两层加粗）+ 录制点数
    if (st->recActive) {
        int cx = SCREEN_W - 30;
        int cy = SCREEN_H - 6;
        int r = 4;
        out.addCircle(5, cx, cy, r, RED);
        out.addCircle(6, cx, cy, r - 1, RED);
        char recCnt[16];
        snprintf(recCnt, sizeof(recCnt), "%d", st->recLen);
        uint16_t cntColor = esp32::utils::rgbTo565(0xFFCCCC);
        out.addText(7, cx + 8, cy - 4, cntColor, recCnt, false);
    }

    // 第二行：格式如 "[0*] 3.6 7.2 10.8"，[]表示当前选择，*表示该档已记录
    char info[64];
    info[0] = '\0';
    for (int i = 0; i < 4; ++i) {
        char valStr[12];
        float tgt = st->target_mN[i];
        if (fabsf(tgt) < 0.05f) {
            snprintf(valStr, sizeof(valStr), "0");
        } else {
            snprintf(valStr, sizeof(valStr), "%.1f", tgt);
        }
        char seg[16];
        if (i == st->presetIdx) {
            if (st->captured[i])
                snprintf(seg, sizeof(seg), "[%s*]", valStr);
            else
                snprintf(seg, sizeof(seg), "[%s]", valStr);
        } else {
            if (st->captured[i])
                snprintf(seg, sizeof(seg), "%s*", valStr);
            else
                snprintf(seg, sizeof(seg), "%s", valStr);
        }
        size_t cur = strlen(info);
        if (cur + strlen(seg) + 2 < sizeof(info)) {
            if (cur > 0)
                strcat(info, " ");
            strcat(info, seg);
        }
    }
    uint16_t infoColor = esp32::utils::rgbTo565(0xFFD060);
    out.addText(3, SCREEN_W / 2, 22, infoColor, info, true);

    // 图形区域：最大化剩余高度，无边框
    const int topY = 40;                     // 标题高度（两行信息）
    const int graphH = SCREEN_H - topY - 4;  // 剩余高度
    const int gw = AppState::GraphW;         // 128
    const int gx = 0;

    if (st->sampleCount == 0)
        return;  // 尚无数据

    // 动态范围（原始码模式：min-200/max+200；拟合后：mN 加适度留白）
    bool useFit = st->fitReady;
    float fMin, fMax, fSpan, fPad;
    if (!useFit) {
        int16_t minV = st->samples[0];
        int16_t maxV = st->samples[0];
        for (int i = 1; i < st->sampleCount; ++i) {
            int16_t v = st->samples[i];
            if (v < minV)
                minV = v;
            if (v > maxV)
                maxV = v;
        }
        int32_t rangeMin = (int32_t)minV - 200;
        int32_t rangeMax = (int32_t)maxV + 200;
        if (rangeMin < -32768)
            rangeMin = -32768;
        if (rangeMax > 32767)
            rangeMax = 32767;
        fMin = (float)rangeMin;
        fMax = (float)rangeMax;
        fSpan = fMax - fMin;
        if (fSpan <= 0.0f)
            fSpan = 1.0f;
    } else {
        // 将样本转换为 mN 后求范围
        float minM = st->fitA * (float)st->samples[0] + st->fitB;
        float maxM = minM;
        for (int i = 1; i < st->sampleCount; ++i) {
            float m = st->fitA * (float)st->samples[i] + st->fitB;
            if (m < minM)
                minM = m;
            if (m > maxM)
                maxM = m;
        }
        // mN 下给一个小的固定留白（0.2mN）
        fMin = minM - 0.2f;
        fMax = maxM + 0.2f;
        if (fMin > fMax) {
            float t = fMin;
            fMin = fMax;
            fMax = t;
        }
        fSpan = fMax - fMin;
        if (fSpan <= 1e-6f)
            fSpan = 1.0f;  // 避免除0
    }

    auto mapY = [&](int16_t code) -> int {
        float val = useFit ? (st->fitA * (float)code + st->fitB) : (float)code;
        float rel = (val - fMin) / fSpan;  // 0..1
        if (rel < 0.0f)
            rel = 0.0f;
        if (rel > 1.0f)
            rel = 1.0f;
        float y = (1.0f - rel) * (graphH - 1);  // 倒置
        int iy = topY + (int)(y + 0.5f);
        if (iy < topY)
            iy = topY;
        if (iy > topY + graphH - 1)
            iy = topY + graphH - 1;
        return iy;
    };

    // 绘制 Y 轴刻度与网格
    uint16_t gridColor = esp32::utils::rgbTo565(0x303030);
    uint16_t tickColor = esp32::utils::rgbTo565(0x606060);
    const int ticks = 4;  // 分成4段，5条刻度线
    for (int i = 0; i <= ticks; ++i) {
        float val = fMin + (fSpan * i) / ticks;
        // 为了绘制刻度线，构造一个虚拟 code 值逆映射并用 mapY 仅为获取 y 像素
        int yPix;
        if (useFit) {
            // 近似：在当前 y 下对应的 val 直接用反推像素到值，再直接求 y 像素
            float rel = (val - fMin) / fSpan;
            if (rel < 0)
                rel = 0;
            if (rel > 1)
                rel = 1;
            float y = (1.0f - rel) * (graphH - 1);
            yPix = topY + (int)(y + 0.5f);
        } else {
            // 同上，直接由范围转 y 像素
            float rel = (val - fMin) / fSpan;
            if (rel < 0)
                rel = 0;
            if (rel > 1)
                rel = 1;
            float y = (1.0f - rel) * (graphH - 1);
            yPix = topY + (int)(y + 0.5f);
        }
        // 网格横线
        out.addLine(20 + i, 0, yPix, SCREEN_W - 1, yPix, gridColor);
        // 左侧短刻度
        out.addLine(40 + i, 0, yPix, 3, yPix, tickColor);

        // 标签
        char lab[20];
        if (useFit) {
            snprintf(lab, sizeof(lab), "%.1f", val);
        } else {
            snprintf(lab, sizeof(lab), "%d", (int)val);
        }
        out.addText(60 + i, 6, yPix - 6, tickColor, lab, false);
    }

    int prevY = mapY(st->samples[(st->writeIdx) % gw]);
    for (int x = 1; x < gw; ++x) {
        int idx = (st->writeIdx + x) % gw;
        int y = mapY(st->samples[idx]);
        out.addLine(100 + x, gx + x - 1, prevY, gx + x, y, GREEN);
        prevY = y;
    }

    // 底部上传状态提示（临时显示）
    uint32_t nowMs = millis();
    if (st->statusUntilMs > nowMs && st->statusMsg[0] != '\0') {
        out.addText(400, SCREEN_W / 2, SCREEN_H - 10, st->statusColor,
                    st->statusMsg, true);
    }
}

void setup() {
    // 按钮:内部下拉
    pinMode(BTN_LEFT, INPUT_PULLDOWN);
    pinMode(BTN_RIGHT, INPUT_PULLDOWN);
    pinMode(BTN_UP, INPUT_PULLDOWN);

    // 1. 把硬件 SPI 绑定到现有的引脚（与 AD7606 共用：MISO=AD7606 DOUTA）
    SPI.begin(TFT_SCLK, ad7606::PIN_AD_MISO, TFT_MOSI, TFT_CS);
    SPI.setFrequency(27000000);  // ST7735 通常 27MHz 左右比较稳

    // 2. 初始化屏幕
    tft.initR(INITR_GREENTAB);
    tft.setRotation(0);
    tft.fillScreen(BLACK);
    tft.setCursor(0, 0);
    // ★ 关键：把 u8g2 挂到 tft 上
    u8g2.begin(tft);
    // 连接到 Adafruit_GFX :contentReference[oaicite:2]{index=2}
    u8g2.setFontMode(1);             // 透明背景（默认也是 1）
    u8g2.setFontDirection(0);        // 正常从左到右
    u8g2.setForegroundColor(WHITE);  // 文字颜色

    // 使用更小的中文字体
    // u8g2_font_wqy12_t_chinese1: 12像素，常用汉字（约2500字）
    // u8g2_font_wqy12_t_chinese2: 12像素，更多汉字（约6000字）
    // u8g2_font_wqy12_t_chinese3: 12像素，完整汉字（约9000字）
    u8g2.setFont(u8g2_font_wqy12_t_chinese3);  // 12像素字体，比 unifont 小

    tft.setTextColor(WHITE);
    tft.setTextSize(1);

    // 初始化系统(读取配置并连接 WiFi)
    init::initSystem(tft);

    // UI 初始化，传入 u8g2 以支持中文
    appUI.begin(&tft, &u8g2);
    // 启用内存帧缓冲，避免闪烁（若内存不足会返回 false）
    appUI.enableBackBuffer(SCREEN_W, SCREEN_H);

    // 绑定场景构建器
    appUI.setScene(buildScene, &app);

    // 初始化 AD7606（与 TFT 共用 SPI）
    ad7606::init();

    // 创建采样队列与任务（将采样放到另一核，降低 UI 阻塞影响）
    g_sampleQ = xQueueCreate(2048, sizeof(int16_t));
    // 在 Core 0 上跑采样任务，优先级略高于默认
    xTaskCreatePinnedToCore(samplerTask, "sampler", 4096, nullptr, 3, nullptr,
                            0);

    // 清空波形缓存并初始化计时
    for (int i = 0; i < AppState::GraphW; ++i)
        app.samples[i] = 0;
    app.sampleCount = 0;
    app.lastSampleMs = millis();

    // 初始化标定目标
    app.target_mN[0] = 0.0f;
    app.target_mN[1] = 3.6f;
    app.target_mN[2] = 7.2f;
    app.target_mN[3] = 10.8f;
    for (int i = 0; i < 4; ++i) {
        app.captured[i] = false;
        app.capturedCode[i] = 0.0f;
    }
    app.presetIdx = 0;
    app.capturedCount = 0;
    app.fitReady = false;
    app.fitA = 0.0f;
    app.fitB = 0.0f;
}

void loop() {
    bool leftPressed = digitalRead(BTN_LEFT) == HIGH;  // 按下=HIGH
    bool rightPressed = digitalRead(BTN_RIGHT) == HIGH;
    bool upPressed = digitalRead(BTN_UP) == HIGH;

    // 从采样队列取尽样本：
    // - 显示：按时间节流到 128 点/3秒（pushSample 内已限速）
    // - 录制：不节流，全部写入 recBuf
    uint32_t now = millis();
    int drained = 0;
    int16_t ch0;
    while (g_sampleQ && xQueueReceive(g_sampleQ, &ch0, 0) == pdTRUE) {
        drained++;
        app.lastRaw = ch0;
        // 显示按时间节流
        pushSample(app, ch0, now);
        now = millis();
        // 录制不节流
        if (app.recActive && app.recLen < AppState::RecCap) {
            app.recBuf[app.recLen++] = ch0;
        }
    }

    // BTN_RIGHT：记录当前电压到当前档位，并循环到下一档
    if (rightPressed && !prevRight) {
        float c = (float)app.lastRaw;
        int idx = app.presetIdx;
        app.capturedCode[idx] = c;
        if (!app.captured[idx]) {
            app.captured[idx] = true;
            app.capturedCount++;
        }

        // 计算线性拟合（使用已记录点的最小二乘）
        if (app.capturedCount >= 2) {
            double sx = 0, sy = 0, sxx = 0, sxy = 0;
            int n = 0;
            for (int i = 0; i < 4; ++i) {
                if (!app.captured[i])
                    continue;
                double x = app.capturedCode[i];
                double y = app.target_mN[i];
                sx += x;
                sy += y;
                sxx += x * x;
                sxy += x * y;
                n++;
            }
            double denom = (n * sxx - sx * sx);
            if (n >= 2 && fabs(denom) > 1e-9) {
                app.fitA = (float)((n * sxy - sx * sy) / denom);
                app.fitB = (float)((sy - app.fitA * sx) / n);
                app.fitReady = true;
            }
        }

        // 下一个档位
        app.presetIdx = (app.presetIdx + 1) % 4;
    }

    // 左键：开始/停止录制，停止后上传
    if (leftPressed && !prevLeft) {
        if (!app.recActive) {
            app.recActive = true;
            app.recLen = 0;
            app.recStartMs = millis();
            app.recEndMs = 0;
        } else {
            app.recActive = false;
            app.recEndMs = millis();
            postRecording(app);
        }
    }
    // printf("l:%s, r:%s, u:%s\n", leftPressed ? "Y" : "N", rightPressed ? "Y"
    // : "N", upPressed ? "Y" : "N"); 驱动 UI 帧刷新

    appUI.tick(millis());

    prevLeft = leftPressed;
    prevRight = rightPressed;
    prevUp = upPressed;
    // delay(16);  // 简单节流 ~60FPS
}
