#include <ch32v00x.h>
#include "clib/u8x8.h"
#include <debug.h>

// TSOP20
// OPP - PA2
// OPN - PA1
// OPO - PD4

void NMI_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void HardFault_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

// u8x8 functions

uint8_t u8x8_gpio_and_delay_ch32v(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    switch(msg) {
        case U8X8_MSG_GPIO_AND_DELAY_INIT:
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
            GPIO_InitTypeDef init = {0};
            init.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
            init.GPIO_Mode = GPIO_Mode_Out_OD;
            init.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOC, &init);
            break;
        case U8X8_MSG_DELAY_MILLI:
            Delay_Ms(arg_int);
            break;
        case U8X8_MSG_DELAY_10MICRO:
            for (volatile uint32_t i = 0; i < (arg_int * 8); i++) { __NOP(); }
            break;
        case U8X8_MSG_DELAY_100NANO:
            __NOP();
            break;

        case U8X8_MSG_GPIO_I2C_CLOCK:
            GPIO_WriteBit(GPIOC, GPIO_Pin_2, (BitAction)(arg_int ? Bit_SET : Bit_RESET));
            break;

        case U8X8_MSG_GPIO_I2C_DATA:
            GPIO_WriteBit(GPIOC, GPIO_Pin_1, (BitAction)(arg_int ? Bit_SET : Bit_RESET));
            break;
    }
    return 1;
}

uint8_t u8x8_byte_hw_i2c_ch32v(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    uint8_t *data;
    switch(msg) {
        case U8X8_MSG_BYTE_INIT:
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

            // We will use PC1 (SDA) and PC2 (SCL)
            GPIO_InitTypeDef GPIO_InitStructure = {0};
            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // Alternate Function Open-Drain
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOC, &GPIO_InitStructure);

            I2C_InitTypeDef I2C_InitTSturcture = {0};
            I2C_InitTSturcture.I2C_ClockSpeed = 1600000; // 400 kHz
            I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
            I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_2;
            I2C_InitTSturcture.I2C_OwnAddress1 = 0x00;
            I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
            I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
            
            I2C_Init(I2C1, &I2C_InitTSturcture);
            I2C_Cmd(I2C1, ENABLE);
            break;

        case U8X8_MSG_BYTE_START_TRANSFER:
            while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET);

            I2C_GenerateSTART(I2C1, ENABLE);
            while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // Wait for EV5

            I2C_Send7bitAddress(I2C1, u8x8_GetI2CAddress(u8x8), I2C_Direction_Transmitter);
            while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // Wait for EV6
            break;

        case U8X8_MSG_BYTE_SEND:
            data = (uint8_t *)arg_ptr;
            while (arg_int > 0) {
                I2C_SendData(I2C1, *data++);
                while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
                arg_int--;
            }
            break;

        case U8X8_MSG_BYTE_END_TRANSFER:
            I2C_GenerateSTOP(I2C1, ENABLE);
            break;
        default:
            break;
    }
    return 1;
}

// OPA and ADC initialization
void Thermocouple_Init(void) {
    // Enable clock for ports A, D, ADC, and alternate functions (AFIO)
    RCC_APB2PeriphClockCmd(
        RCC_APB2Periph_GPIOA |
        RCC_APB2Periph_GPIOD |
        RCC_APB2Periph_ADC1 | 
        RCC_APB2Periph_AFIO, ENABLE
    );

    // Pin configuration
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    
    // Configure PA1 (OPA- input / OPN0) and PA2 (OPA+ input / OPP0) as analog inputs
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // Configure PD3 (NTC) and PD4 (OPA OUT) as analog inputs
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    // The EXTEN_CTR register controls the built-in OPA
    EXTEN->EXTEN_CTR |= EXTEN_OPA_EN;

    // OPA initialization via library
    OPA_InitTypeDef OPA_InitStructure = {0};
    OPA_InitStructure.PSEL = CHP0; // OPA+ is connected to the physical PA2 pin
    OPA_InitStructure.NSEL = CHN0; // OPA- is connected to the physical PA1 pin
    OPA_Init(&OPA_InitStructure);
    OPA_Cmd(ENABLE);

    // 4. ADC configuration
    ADC_InitTypeDef ADC_InitStructure = {0};
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);
    
    // Reset ADC calibration
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

// ADC read function
uint16_t Read_ADC_Channel(uint8_t channel) {
    ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_241Cycles);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    return ADC_GetConversionValue(ADC1);
}

// Returns the temperature in hundredths of a degree (e.g., 2150 = 21.50 °C)
uint16_t Calculate_Temperature(uint16_t adc_value, uint16_t ambient_temp_c100) {
    // 1. Basic settings (based on your measurements)
    const uint16_t adc_offset = 160;         // Average ADC value at 21 °C

    // 2. Underflow protection (if ADC drops below the offset due to noise)
    if (adc_value <= adc_offset) {
        return ambient_temp_c100;
    }

    // Use a 32-bit variable to avoid overflow during multiplication
    uint32_t active_adc = adc_value - adc_offset;

    // 3. Temperature delta calculation in one step
    
    // OPTION A: Theoretically accurate (Recommended)
    // Combined formula: (3300mV * 1000 * 100) / (1024 * 101 * 41) = 77.81
    // Meaning 1 ADC step = 0.778 °C
    uint32_t temp_delta_c100 = (active_adc * 7900) / 100;

    // OPTION B: Empirical (Tuned to your soldering iron)
    // Delta 131 °C = ADC delta 134. Multiplier: 13100 / 134 = 97.76
    // If you uncomment this line, the soldering iron will show exactly 152 °C:
    // uint32_t temp_delta_c100 = (active_adc * 9776) / 100;

    // 4. Add room temperature
    return (uint16_t)(temp_delta_c100 + ambient_temp_c100);
}

// ADC value table for NTC 10k (B=3950) with a 10k divider (at 3.3V)
// From 0 °C to 50 °C with a 5 °C step
const uint16_t ntc_adc_table[11] = {
    784, // 0 °C
    735, // 5 °C
    681, // 10 °C
    625, // 15 °C
    568, // 20 °C
    512, // 25 °C (NTC resistance = 10k, half of 1024)
    456, // 30 °C
    404, // 35 °C
    355, // 40 °C
    311, // 45 °C
    271  // 50 °C
};

// Calculate NTC temperature in hundredths (2150 = 21.50 °C)
uint16_t Get_NTC_Temperature_C100(void) {
    uint16_t adc_value = Read_ADC_Channel(ADC_Channel_4);
    
    if (adc_value >= ntc_adc_table[0]) return 0;      // 0.00 °C
    if (adc_value <= ntc_adc_table[10]) return 5000;  // 50.00 °C
    
    for (uint8_t i = 0; i < 10; i++) {
        if (adc_value <= ntc_adc_table[i] && adc_value > ntc_adc_table[i+1]) {
            
            // Found the interval: between (i * 5) and ((i+1) * 5) degrees
            uint16_t adc_diff = ntc_adc_table[i] - ntc_adc_table[i+1];
            uint16_t adc_offset = ntc_adc_table[i] - adc_value;
            
            uint16_t temp_base = i * 500; 
            
            uint16_t temp_offset = (adc_offset * 500) / adc_diff;
            
            return temp_base + temp_offset;
        }
    }
    
    return 2500; // fallback 25°C
}

int main(void) {
    SystemCoreClockUpdate();
    Delay_Init();
    Delay_Ms(2000);
    // Disable_Reset_Pin();
    USART_Printf_Init(9600);

    Thermocouple_Init();
    // u8x8 initialization block
    u8x8_t u8x8;
    u8x8_Setup(&u8x8, 
        u8x8_d_ssd1306_128x32_univision, 
        u8x8_cad_ssd13xx_i2c, 
        u8x8_byte_hw_i2c_ch32v, 
        u8x8_gpio_and_delay_ch32v
    );

    u8x8_InitDisplay(&u8x8);
    u8x8_SetPowerSave(&u8x8, 0);
    u8x8_ClearDisplay(&u8x8);
    u8x8_SetFont(&u8x8, u8x8_font_chroma48medium8_r);
    u8x8_DrawString(&u8x8, 0, 0, "Status:");

    char temp_str[24];
    while (1) {
        Delay_Ms(1000);
        uint16_t a4_adc = Read_ADC_Channel(ADC_Channel_4);
        uint16_t a7_adc = Read_ADC_Channel(ADC_Channel_7);
        printf("ADC Channel 4 Value: %u\n", a4_adc);
        printf("ADC Channel 7 Value: %u\n", a7_adc);
        uint16_t ntc_temperature = Get_NTC_Temperature_C100();
        printf("NTC Temperature: %u°C\n", ntc_temperature);
        uint16_t temperature = Calculate_Temperature(a7_adc, ntc_temperature);
        printf("Calculated Temperature: %u°C\n", temperature);
        // snprintf(temp_str, sizeof(temp_str), "A4:%u  A7:%u ", a4_adc, a7_adc);
        snprintf(temp_str, sizeof(temp_str), "T:%u.%02u (%u)", temperature/100, temperature % 100, a7_adc);
        u8x8_DrawString(&u8x8, 0, 1, temp_str);
        snprintf(temp_str, sizeof(temp_str), "NTC:%u.%02u (%u)", ntc_temperature/100, ntc_temperature % 100, a4_adc);
        u8x8_DrawString(&u8x8, 0, 2, temp_str);
    }
    return 0;
}

void NMI_Handler(void) {}
void HardFault_Handler(void)
{
    while (1)
    {
    }
}
}