//
// Created by Luca on 10/8/2025.
//

#ifndef SHMINGO_HAL_SHAL_ADC_REG_H753ZI_H
#define SHMINGO_HAL_SHAL_ADC_REG_H753ZI_H

#include "SHAL_CORE.h"
#include "SHAL_ADC_TYPES.h"
#include "stm32h753xx.h"

#define SHAL_ADC1 SHAL_ADC(1)
#define SHAL_ADC2 SHAL_ADC(2)
#define SHAL_ADC3 SHAL_ADC(3)

#define NUM_ADCS 3
#define NUM_ADC_CHANNELS 16

enum class SHAL_ADC_Channel : uint32_t {
    CH0 = 0,
    CH1,
    CH2,
    CH3,
    CH4,
    CH5,
    CH6,
    CH7,
    CH8,
    CH9,
    CH10,
    CH11,
    CH12,
    CH13,
    CH14,
    CH15,
    CH16,
    CH17,
    CH18,
    CHTemp,
    CHRef,
    CHBat,
    NO_ADC_MAPPING
};

enum class ADC_Key : uint8_t{
    S_ADC1 = 0,
    S_ADC2 = 1,
    S_ADC3 = 2,
    NUM_ADC = 3,
    INVALID = 255
};

enum class SHAL_ADC_Resolution : uint8_t { //TODO figure out what to do with this difference
    B16         = 0x00,
    B14         = 0x01,
    B12         = 0x02,
    B10         = 0x03,
    B8          = 0x04,
    B6          = 0x05,
};

enum class SHAL_ADC_Clock_Mode {
    ASYNC = 0x00,
    SYNC_BY_1 = 0x01,
    SYNC_BY_2 = 0x02,
    SYNC_BY_4 = 0x03,
};


static volatile ADC_TypeDef* ADC_TABLE[3] = { //Lookup table for ADCs
    ADC1,
    ADC2,
    ADC3,
};

static volatile ADC_Common_TypeDef* ADC_CCR_TABLE[3] = { //Common registers
    ADC12_COMMON, //1 and 2 share a common register
    ADC12_COMMON,
    ADC3_COMMON,
};

enum class ADC_Clock_Source : uint32_t {
    SHAL_NO_CLOCK       = 0x00,
    SHAL_PLLSAI1        = 0x01,
    SHAL_PLLSYS         = 0x02,
    SHAL_SYSCLK         = 0x03,
};

static SHAL_ADC_Common_Control_Reg getADCCommonControl(ADC_Key key) {

    SHAL_ADC_Common_Control_Reg res = {
        .reg = nullptr,
        .VoltageRefEnable = ADC_CCR_VREFEN,
        .TempSensorEnable = ADC_CCR_TSEN,
        .VBatteryEnable = ADC_CCR_VBATEN,
        .clock_mode_position = ADC_CCR_CKMODE_Pos
    };

    res.reg = &ADC_CCR_TABLE[static_cast<uint8_t>(key)]->CCR;
    return res;
}

static inline SHAL_ADC_RCC_Enable_Reg getADCRCCEnableRegister(const ADC_Key key){
    switch (key) {
        case ADC_Key::S_ADC1:
        case ADC_Key::S_ADC2:
            return {&RCC->AHB1ENR, RCC_AHB1ENR_ADC12EN};
        case ADC_Key::S_ADC3:
            return {&RCC->AHB4ENR, RCC_AHB4ENR_ADC3EN};
        default:
            __builtin_unreachable();
    }
}

static inline SHAL_ADC_Control_Reg getADCControlReg(ADC_Key key) {

    SHAL_ADC_Control_Reg res = {nullptr, ADC_CR_ADEN,
                                ADC_CR_ADSTP,
                                ADC_CR_ADDIS,
                                ADC_CR_ADCAL,
                                ADC_CR_ADSTART,
                                ADC_CR_DEEPPWD,
                                 ADC_CR_ADVREGEN,
                                 ADC_CR_ADCALDIF};

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->CR);
    return res;
}

static inline SHAL_ADC_Config_Reg getADCConfigReg(ADC_Key key) {

    //NOTE offset of 33 means the function is deprecated
    SHAL_ADC_Config_Reg res = {nullptr,
        ADC_CFGR_CONT,
        ADC_CFGR_RES_Pos,
        0,  //TODO 0 is broken, shouldnt have alignment
        ADC_CFGR_CONT_Msk
    };

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->CFGR);
    return res;
}

static inline SHAL_ADC_ISR_Reg getADCISRReg(ADC_Key key){
    SHAL_ADC_ISR_Reg res = {nullptr,
        ADC_ISR_EOC,
        ADC_ISR_EOS,
        ADC_ISR_ADRDY,
        ADC_ISR_OVR,
    ADC_ISR_LDORDY};

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->ISR);
    return res;
}

static inline SHAL_ADC_Data_Reg getADCDataReg(ADC_Key key){
    SHAL_ADC_Data_Reg res = {nullptr, 0xFFFF};

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->DR);
    return res;
}

static inline SHAL_ADC_Channel_Sampling_Time_Reg getADCChannelSamplingTimeRegister(ADC_Key key, SHAL_ADC_Channel channel){
    volatile ADC_TypeDef* ADCReg = ADC_TABLE[static_cast<uint8_t>(key)];

    volatile uint32_t* SMPReg = nullptr;
    uint32_t pos;

    auto channelNum = static_cast<uint8_t>(channel);

    if (channelNum <= 9) {
        SMPReg = &ADCReg->SMPR1;
        pos = (channelNum * 3);
    } else {
        SMPReg = &ADCReg->SMPR2;
        pos = ((channelNum - 10) * 3);
    }

    return {SMPReg, pos};
}

static inline SHAL_ADC_Sequence_Amount_Reg getADCSequenceAmountRegister(ADC_Key key){
    SHAL_ADC_Sequence_Amount_Reg res = {nullptr, ADC_SQR1_L_Pos};

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->SQR1);
    return res;
}

static SHAL_ADC_Sequence_Reg getADCSequenceRegister(ADC_Key key, uint32_t conversionNum){

    volatile ADC_TypeDef* adc_reg = ADC_TABLE[static_cast<uint8_t>(key)];

    volatile uint32_t* sqr[4] = {&adc_reg->SQR1,
                                 &adc_reg->SQR2,
                                 &adc_reg->SQR3,
                                 &adc_reg->SQR4,
    };

    const uint8_t sqrIndex = conversionNum / 5; //CONVERSION NUM STARTS AT 1! AS PER DATASHEET REFERENCING IT AS SQ1
    const uint32_t offset = (((conversionNum) % 5) * 6);

    return {sqr[sqrIndex], offset};
}

static SHAL_ADC_Preselect_Reg getADCPreselectRegister(ADC_Key key, SHAL_ADC_Channel channel) {
    SHAL_ADC_Preselect_Reg res = {nullptr, static_cast<uint32_t>(channel)};

    res.reg = &(ADC_TABLE[static_cast<uint8_t>(key)]->PCSEL);

    return res;
}

#endif //SHMINGO_HAL_SHAL_ADC_REG_H753ZI_H