#include <stdint.h>
#include <board/ioexp/ioexp_tca6424.h>
#include <board/eeprom.h>
#include <drivers/i2c.h>
#include "ti_drivers_config.h"
#include "ti_board_config.h"
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include <kernel/dpl/DebugP.h>

/* ========================================================================== */
/*                         IO Expander pin index 정의                         */
/* ========================================================================== */
/* PORT 0, PIN 4  -> ioIndex : 0*8 + 4 = 4 (FSI_MUX_SEL) */
#define IO_MUX_MCAN_SEL                         (4U)
/* PORT 2, PIN 1  -> ioIndex : 2*8 + 1 = 17 (MCAN1_STB) */
#define IO_MUX_MCAN_STB                         (17U)
/* PORT 1, PIN 2  -> ioIndex : 1*8 + 2 = 10 (SPI1_MUX_SEL) */
#define IO_MUX_SPI1_SEL                         (10U)

/* FSI_MUX_SEL : 0 → MCAN 경로 선택 */
#define TCA6424_IO_MUX_MCAN_SEL_PORT_LINE_STATE (TCA6424_OUT_STATE_LOW)     //TCA6424_OUT_STATE_LOW --> TCA6424_OUT_STATE_HIGH 로 변경
/* MCAN1_STB   : 1 → 트랜시버 enable */
#define TCA6424_IO_MUX_MCAN_STB_PORT_LINE_STATE (TCA6424_OUT_STATE_HIGH)
/* SPI1_MUX_SEL: 0 → SPI1 → PMIC */
#define TCA6424_IO_MUX_SPI1_SEL_PORT_LINE_STATE (TCA6424_OUT_STATE_LOW)

/* ========================================================================== */
/*                       TCA6424 레지스터 주소 (datasheet)                    */
/* ========================================================================== */
#define TCA6424_REG_INPUT_PORT0      (0x00U)  /* Input  Port0~2 : 0x00~0x02 */
#define TCA6424_REG_OUTPUT_PORT0     (0x04U)  /* Output Port0~2 : 0x04~0x06 */
#define TCA6424_REG_POLARITY_PORT0   (0x08U)  /* Polarity       : 0x08~0x0A */
#define TCA6424_REG_CONFIG_PORT0     (0x0CU)  /* Config (dir)   : 0x0C~0x0E */

#define EEPROM_OFFSET_READ_PCB_REV   (0x0022U)
#define EEPROM_READ_PCB_REV_DATA_LEN (0x2U)
#define BOARD_VERSION_E2             ('2')

/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */

static TCA6424_Config  gTCA6424_Config;

/* ========================================================================== */
/*                      Static 헬퍼 함수 프로토타입                           */
/* ========================================================================== */

static int32_t TCA6424_readReg(uint8_t regAddr, uint8_t *value);
static int32_t TCA6424_readDirAndLevel(uint32_t ioIndex,
                                       uint8_t *dirBit,
                                       uint8_t *outBit,
                                       uint8_t *inBit,
                                       uint8_t *cfgReg,
                                       uint8_t *outReg,
                                       uint8_t *inReg);
static void TCA6424_dumpIoStatus(const char *name, uint32_t ioIndex);

/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

int32_t TCA6424_Mcan_Transceiver(void)
{
    int32_t        status = SystemP_SUCCESS;
    TCA6424_Params tca6424Params;

    TCA6424_Params_init(&tca6424Params);

    /* SysConfig에서 설정한 i2cInstance, i2cAddress가
     * gTCA6424_Config.params에 이미 들어있으므로
     * 여기서는 그대로 사용 (tca6424Params는 기본값으로 호출) */
    status = TCA6424_open(&gTCA6424_Config, &tca6424Params);
    DebugP_log("[IOEXP] TCA6424_open status=%d (i2cInst=%u, addr=0x%02X)\r\n",
               status,
               (unsigned int)gTCA6424_Config.params.i2cInstance,
               (unsigned int)gTCA6424_Config.params.i2cAddress);
    DebugP_assert(SystemP_SUCCESS == status);

    /* 1) FSI_MUX_SEL → LOW, OUTPUT (MCAN 경로 선택) */
    status = TCA6424_setOutput(
                 &gTCA6424_Config,
                 IO_MUX_MCAN_SEL,
                 TCA6424_IO_MUX_MCAN_SEL_PORT_LINE_STATE);
    DebugP_assert(SystemP_SUCCESS == status);

    status += TCA6424_config(
                 &gTCA6424_Config,
                 IO_MUX_MCAN_SEL,
                 TCA6424_MODE_OUTPUT);
    DebugP_assert(SystemP_SUCCESS == status);

    TCA6424_dumpIoStatus("FSI_MUX_SEL", IO_MUX_MCAN_SEL);

    /* 2) MCAN1_STB → HIGH, OUTPUT (트랜시버 스탠바이 해제) */
    status += TCA6424_setOutput(
                 &gTCA6424_Config,
                 IO_MUX_MCAN_STB,
                 TCA6424_IO_MUX_MCAN_STB_PORT_LINE_STATE);
    DebugP_assert(SystemP_SUCCESS == status);

    status += TCA6424_config(
                 &gTCA6424_Config,
                 IO_MUX_MCAN_STB,
                 TCA6424_MODE_OUTPUT);
    DebugP_assert(SystemP_SUCCESS == status);

    TCA6424_dumpIoStatus("MCAN1_STB", IO_MUX_MCAN_STB);

    /* 3) SPI1_MUX_SEL → LOW, OUTPUT (SPI1 → PMIC) */
    status += TCA6424_setOutput(
                 &gTCA6424_Config,
                 IO_MUX_SPI1_SEL,
                 TCA6424_IO_MUX_SPI1_SEL_PORT_LINE_STATE);
    DebugP_assert(SystemP_SUCCESS == status);

    status += TCA6424_config(
                 &gTCA6424_Config,
                 IO_MUX_SPI1_SEL,
                 TCA6424_MODE_OUTPUT);
    DebugP_assert(SystemP_SUCCESS == status);

    TCA6424_dumpIoStatus("SPI1_MUX_SEL", IO_MUX_SPI1_SEL);

    if (status != SystemP_SUCCESS)
    {
        DebugP_log("Transceiver Setup Failure !!\r\n");
        TCA6424_close(&gTCA6424_Config);
        return status;
    }

    TCA6424_close(&gTCA6424_Config);
    return status;
}

void mcanEnableTransceiver(void)
{
    int32_t status = SystemP_SUCCESS;
    uint8_t boardVer[2] = "";

    Board_eepromOpen();

    status = EEPROM_read(gEepromHandle[CONFIG_EEPROM0],
                         EEPROM_OFFSET_READ_PCB_REV,
                         boardVer,
                         EEPROM_READ_PCB_REV_DATA_LEN);

    DebugP_log("mcanEnableTransceiver: EEPROM_read status=%d, boardVer=%c 0x%02X\r\n",
               status, (char)boardVer[0], boardVer[1]);

    if (status == SystemP_SUCCESS)
    {
        if ((boardVer[0] == 'A' && boardVer[1] == '\0'))   /* Rev A */
        {
            DebugP_log("mcanEnableTransceiver: Rev A detected\r\n");
            status = TCA6424_Mcan_Transceiver();
        }
        else if (boardVer[0] == 'E' && boardVer[1] == '2') /* Rev E2 */
        {
            DebugP_log("mcanEnableTransceiver: Rev E2 detected\r\n");
            status = TCA6424_Mcan_Transceiver();
        }
        else if (boardVer[0] == 'E' && boardVer[1] == '1') /* Rev E1 */
        {
            DebugP_log("mcanEnableTransceiver: Rev E1 detected, IOExp config not required\r\n");
            /* MCAN Transceiver is enabled by default in E1 */
        }
        else if (boardVer[0] == 'B' && boardVer[1] == 0x00)/* ★ Rev B */
        {
            DebugP_log("mcanEnableTransceiver: Rev B detected, enabling transceiver via IO Expander\r\n");
            status = TCA6424_Mcan_Transceiver();
        }
        else
        {
            DebugP_log("mcanEnableTransceiver: Unknown board rev %c 0x%02X, skipping IOExp config\r\n",
                       (char)boardVer[0], boardVer[1]);
        }
    }

    DebugP_assert(status == SystemP_SUCCESS);
    Board_eepromClose();
}

/* ========================================================================== */
/*                           Static helper 구현                               */
/* ========================================================================== */

/* TCA6424의 임의 레지스터 1바이트 읽기 */
static int32_t TCA6424_readReg(uint8_t regAddr, uint8_t *value)
{
    int32_t         status;
    I2C_Transaction i2cTransaction;
    uint8_t         txBuf[1];
    uint8_t         rxBuf[1];

    txBuf[0] = regAddr;

    /* 트랜잭션 초기화 */
    I2C_Transaction_init(&i2cTransaction);

    /* 슬레이브 주소 설정 (SysConfig에서 지정된 0x22 사용) */
    i2cTransaction.targetAddress = gTCA6424_Config.params.i2cAddress;

    /* 쓰기 1바이트(레지스터 주소), 읽기 1바이트(데이터) */
    i2cTransaction.writeBuf   = txBuf;
    i2cTransaction.writeCount = 1U;
    i2cTransaction.readBuf    = rxBuf;
    i2cTransaction.readCount  = 1U;

    /* 실제 전송 */
    status = I2C_transfer(gTCA6424_Config.i2cHandle, &i2cTransaction);
    if (status == SystemP_SUCCESS)
    {
        *value = rxBuf[0];
    }
    else
    {
        DebugP_log("[IOEXP] I2C_transfer failed (reg=0x%02X, status=%d, addr=0x%02X)\r\n",
                   regAddr,
                   status,
                   (unsigned int)gTCA6424_Config.params.i2cAddress);
    }

    return status;
}

/* 특정 ioIndex에 대한 방향(config) 비트, 출력(out) 비트, 입력(in) 비트를 읽어온다 */
static int32_t TCA6424_readDirAndLevel(uint32_t ioIndex,
                                       uint8_t *dirBit,
                                       uint8_t *outBit,
                                       uint8_t *inBit,
                                       uint8_t *cfgReg,
                                       uint8_t *outReg,
                                       uint8_t *inReg)
{
    int32_t status;
    uint8_t port;
    uint8_t bitMask;

    port    = (uint8_t)(ioIndex / 8U);
    bitMask = (uint8_t)(1U << (ioIndex % 8U));

    /* 방향 레지스터 */
    status  = TCA6424_readReg((uint8_t)(TCA6424_REG_CONFIG_PORT0 + port), cfgReg);
    if (status != SystemP_SUCCESS)
        return status;

    /* 출력 레지스터 (우리가 쓴 값) */
    status  = TCA6424_readReg((uint8_t)(TCA6424_REG_OUTPUT_PORT0 + port), outReg);
    if (status != SystemP_SUCCESS)
        return status;

    /* 입력 레지스터 (실제 핀 전압 샘플링) */
    status  = TCA6424_readReg((uint8_t)(TCA6424_REG_INPUT_PORT0 + port), inReg);
    if (status != SystemP_SUCCESS)
        return status;

    *dirBit = (uint8_t)((*cfgReg & bitMask) ? 1U : 0U);  /* 1: input, 0: output */
    *outBit = (uint8_t)((*outReg & bitMask) ? 1U : 0U);  /* 우리가 쓴 값 (논리)   */
    *inBit  = (uint8_t)((*inReg  & bitMask) ? 1U : 0U);  /* 실제 핀에 보이는 값   */

    return SystemP_SUCCESS;
}

/* 디버그 로그 : 이름/방향/OUT/IN/레지스터 값 출력 */
static void TCA6424_dumpIoStatus(const char *name, uint32_t ioIndex)
{
    uint8_t dirBit, outBit, inBit;
    uint8_t cfgReg, outReg, inReg;
    int32_t status;

    status = TCA6424_readDirAndLevel(ioIndex,
                                     &dirBit,
                                     &outBit,
                                     &inBit,
                                     &cfgReg,
                                     &outReg,
                                     &inReg);
    if (status != SystemP_SUCCESS)
    {
        DebugP_log("[IOEXP] %s : readDirAndLevel failed (status=%d)\r\n",
                   name, status);
        return;
    }

    DebugP_log("[IOEXP] %-12s : dir=%s, OUT=%s, IN=%s  (CFG=0x%02X, OUT=0x%02X, IN=0x%02X)\r\n",
               name,
               (dirBit == 0U) ? "OUTPUT" : "INPUT ",
               (outBit == 0U) ? "LOW "   : "HIGH",
               (inBit  == 0U) ? "LOW "   : "HIGH",
               cfgReg,
               outReg,
               inReg);
}
