#include "ch32v30x.h"
#include "string.h"
#include "eth_driver.h"
#include "dvp.h"
#include "qdx_port.h"
#include "qdx_preprocess.h"
#include "qdx_tcp_logic.h"

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

#define MAX_TCP_PAYLOAD_SIZE 10240
uint8_t g_TxNetBuffer_A_Mem[MAX_TCP_PAYLOAD_SIZE];
uint8_t g_TxNetBuffer_B_Mem[MAX_TCP_PAYLOAD_SIZE];

TcpTxBuffer_t g_TxNetBuffer_A = {
    .pBuffer = g_TxNetBuffer_A_Mem,
    .TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
    .HeadOffset = 64,
    .ValidPayloadLen = 0
};
TcpTxBuffer_t g_TxNetBuffer_B = {
    .pBuffer = g_TxNetBuffer_B_Mem,
    .TotalCapacity = MAX_TCP_PAYLOAD_SIZE,
    .HeadOffset = 64,
    .ValidPayloadLen = 0
};



void OnConfigUpdate(const ConfigCommon_t *common, const Config2D_t *cfg2d, const Config1D_t *cfg1d)
{
    Preprocess_Settings_Change(cfg2d, cfg1d, common);
}

/* NG output GPIO: PA8 push-pull, active-high when NG detected */
#define NG_GPIO_PORT   GPIOA
#define NG_GPIO_PIN    GPIO_Pin_8
#define NG_GPIO_CLK    RCC_APB2Periph_GPIOA
#define NG_PULSE_MS    200  /* default NG pulse width */

static volatile uint32_t g_ng_off_time = 0;
extern volatile uint32_t sys_tick_ms;

static void NG_GPIO_Init(void)
{
    GPIO_InitTypeDef gpio = {0};
    RCC_APB2PeriphClockCmd(NG_GPIO_CLK, ENABLE);
    gpio.GPIO_Pin   = NG_GPIO_PIN;
    gpio.GPIO_Mode  = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(NG_GPIO_PORT, &gpio);
    GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
}

void OnDetectionResult(uint32_t frameNumber, uint8_t resultStatus)
{
    (void)frameNumber;
    /* resultStatus: 0 = NG, 1 = OK */
    if (resultStatus == 0) {
        GPIO_SetBits(NG_GPIO_PORT, NG_GPIO_PIN);
        g_ng_off_time = sys_tick_ms + NG_PULSE_MS;
    }
}

#define KEEPALIVE_ENABLE    1

/* ============================================================
 * FLASH / SRAM 分配配置 (Option Bytes RAM_CODE_MOD[2:0])
 * 修改后需复位才生效
 * ============================================================ */
typedef enum {
    FLASH_192_SRAM_128 = 0,   /* 00x  默认 */
    FLASH_224_SRAM_96,        /* 01x */
    FLASH_256_SRAM_64,        /* 10x */
    FLASH_128_SRAM_192,       /* 110 */
    FLASH_288_SRAM_32         /* 111 */
} FLASH_SRAM_DEFIN;

static void Config_Flash_SRAM(FLASH_SRAM_DEFIN mode)
{
    uint8_t UserByte = FLASH_GetUserOptionByte() & 0xFF;
    uint8_t newByte  = UserByte & ~0xE0;   /* clear bits [7:5] */

    switch (mode) {
    case FLASH_192_SRAM_128:                          break; /* 000 */
    case FLASH_224_SRAM_96:  newByte |= 0x40;         break; /* 010 */
    case FLASH_256_SRAM_64:  newByte |= 0x80;         break; /* 100 */
    case FLASH_128_SRAM_192: newByte |= 0xC0;         break; /* 110 */
    case FLASH_288_SRAM_32:  newByte |= 0xE0;         break; /* 111 */
    default: return;
    }

    if (newByte == UserByte) return;   /* already configured */

    FLASH_Unlock();
    FLASH_ProgramOptionByteData(0x1FFFF802, newByte);
    FLASH_Lock();
    printf("Flash/SRAM config changed to %d, resetting...\r\n", mode);
    NVIC_SystemReset();
}

u8  MACAddr[6];
u8  IPAddr[4]   = {192, 168, 1, 10};
u8  GWIPAddr[4] = {192, 168, 1,  1};
u8  IPMask[4]   = {255, 255, 255, 0};
u8  DESIP[4]    = {192, 168, 1, 50};
u16 desport     = 5512;
u16 srcport     = 5511;

u8 SocketId;
u8 SocketRecvBuf[WCHNET_MAX_SOCKET_NUM][RECE_BUF_LEN];

void mStopIfError(u8 iError)
{
    if (iError == WCHNET_ERR_SUCCESS) return;
    printf("Error: %02X\r\n", (u16)iError);
}

void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = {0};
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    TIM_TimeBaseStructure.TIM_Period        = WCHNETTIMERPERIOD * 1000 - 1;
    TIM_TimeBaseStructure.TIM_Prescaler     = SystemCoreClock / 1000000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    NVIC_EnableIRQ(TIM2_IRQn);
}





/* qdx_port.c notification hooks */
extern void qdx_port_sock_recv_notify(uint8_t sockid);
extern void qdx_port_sock_connect_notify(uint8_t sockid);
extern void qdx_port_sock_disconnect_notify(uint8_t sockid);
extern void qdx_port_init(void);


void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
    if (intstat & SINT_STAT_RECV)
    {
        qdx_port_sock_recv_notify(socketid);
    }
    if (intstat & SINT_STAT_CONNECT)
    {
        WCHNET_ModifyRecvBuf(socketid, (u32)SocketRecvBuf[socketid], RECE_BUF_LEN);
        qdx_port_sock_connect_notify(socketid);
        printf("TCP Connected, socket %d\r\n", socketid);
    }
    if (intstat & SINT_STAT_DISCONNECT)
    {
        qdx_port_sock_disconnect_notify(socketid);
        printf("TCP Disconnected, socket %d\r\n", socketid);
    }
    if (intstat & SINT_STAT_TIM_OUT)
    {
        qdx_port_sock_disconnect_notify(socketid);
        printf("TCP Timeout, socket %d\r\n", socketid);
    }
}

void WCHNET_HandleGlobalInt(void)
{
    u8  intstat;
    u16 i;
    u8  socketint;
    intstat = WCHNET_GetGlobalInt();
    if (intstat & GINT_STAT_UNREACH)    printf("GINT_STAT_UNREACH\r\n");
    if (intstat & GINT_STAT_IP_CONFLI)  printf("GINT_STAT_IP_CONFLI\r\n");
    if (intstat & GINT_STAT_PHY_CHANGE) {
        if (WCHNET_GetPHYStatus() & PHY_Linked_Status)
            printf("PHY Link Success\r\n");
    }
    if (intstat & GINT_STAT_SOCKET) {
        for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++) {
            socketint = WCHNET_GetSocketInt(i);
            if (socketint) WCHNET_HandleSockInt(i, socketint);
        }
    }
}

/* ============================================================
 * RTOS Task: WCHNET protocol stack driver (highest priority)
 * ============================================================ */
static void task_wchnet_entry(void *pvParameters)
{
    (void)pvParameters;
    while (1)
    {
        WCHNET_MainTask();
        if (WCHNET_QueryGlobalInt())
            WCHNET_HandleGlobalInt();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

/* ============================================================
 * RTOS Task: Business logic (DVP + preprocess + send)
 * ============================================================ */
static void task_business_entry(void *pvParameters)
{
    (void)pvParameters;
    static uint8_t use_buffer_A = 1;

    while (1)
    {
        /* NG pulse off check */
        if (g_ng_off_time && sys_tick_ms >= g_ng_off_time) {
            GPIO_ResetBits(NG_GPIO_PORT, NG_GPIO_PIN);
            g_ng_off_time = 0;
        }

        DVP_Task();

        if (Frame_Ready_Flag)
        {
            Frame_Ready_Flag = 0;

            RawImageBuffer_t raw_img;
            raw_img.pData = (uint16_t *)FrameBuffer;
            raw_img.Width = SENSOR_WIDTH;
            raw_img.Height = SENSOR_HEIGHT;
            raw_img.FrameNumber = Ready_Frame_Count;

            if (Preprocess_CheckInternalTrigger2D(&raw_img) == 1)
            {
                PreprocessResult_t meta;
                TcpTxBuffer_t *tx_buf = use_buffer_A ? &g_TxNetBuffer_A : &g_TxNetBuffer_B;
                use_buffer_A = !use_buffer_A;

                if (Preprocess_Execute(&raw_img, tx_buf, &meta) == 0)
                {
                    TcpLogic_BuildAndSendTemperatureFrame(tx_buf, &meta, 1, 1);
                }
            }
        }
        else
        {
            vTaskDelay(pdMS_TO_TICKS(2));
        }
    }
}

int main(void)
{
    u8 i;
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("TCPClient Test\r\nSystemClk:%d\r\n", SystemCoreClock);
    printf("UserByte: %02x\r\n", FLASH_GetUserOptionByte() & 0xFF);
    Config_Flash_SRAM(FLASH_128_SRAM_192);
    printf("net version:%x\n", WCHNET_GetVer());
    if (WCHNET_LIB_VER != WCHNET_GetVer()) printf("version error.\n");

    WCHNET_GetMacAddr(MACAddr);
    printf("mac addr:");
    for (i = 0; i < 6; i++) printf("%x ", MACAddr[i]);
    printf("\n");

    DVP_Init();
    TIM2_Init();
    NG_GPIO_Init();

    i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr);
    mStopIfError(i);
    if (i == WCHNET_ERR_SUCCESS) printf("WCHNET_LibInit Success\r\n");

#if KEEPALIVE_ENABLE
    {
        struct _KEEP_CFG cfg = {20000, 15000, 9};
        WCHNET_ConfigKeepLive(&cfg);
    }
#endif

    qdx_port_init();

    Preprocess_Init(SENSOR_WIDTH, SENSOR_HEIGHT);
    TcpLogic_Init(MACAddr, NULL);
    TcpLogic_RegisterConfigCallback(OnConfigUpdate);
    TcpLogic_RegisterDetectionCallback(OnDetectionResult);
    TcpLogic_Start();

    xTaskCreate(task_wchnet_entry, "wchnet", 1024, NULL, 6, NULL);
    xTaskCreate(task_business_entry, "business", 1024, NULL, 5, NULL);
    vTaskStartScheduler();

    /* Should never reach here */
    while (1) {}
}
