#include "drivers/i2c/i2c.h"
#include "app/thermo/tsensor_ll.h"
#include "stm32l1xx_hal.h"
#include "stm32l1xx_hal_i2c.h"
#include "stm32l1xx_hal_gpio.h"
#include "core/main.h"
#include "core/csect.h"
#include "core/config_pins.h"

#define pI2CHandle (&I2C_Handle)

#define GENERAL_TIMEOUT        100              // Due to USB interrupt there is a latency
#define WRITE_ADDRESS_TIMEOUT  GENERAL_TIMEOUT
#define WRITE_CONFIG_TIMEOUT   GENERAL_TIMEOUT
#define READ_CONFIG_TIMEOUT    GENERAL_TIMEOUT
#define READ_TEMP_TIMEOUT      GENERAL_TIMEOUT

static bool TSensor_Init();
static void TSensor_DeInit();
static bool TSensor_GetMomentaryTemp( tTSensorTemp * temp );

// Typical startup time is 4us for AD74xx, let it be 10us     
#define AD74XX_POWERUP_TIME_us    (10)   // 4us typical
 
// Typical conversion time for AD74xx, let it be 40us  
#define AD74XX_CONVERSION_TIME_us (40)   // 25us typical


#define AD74XX_REG_TEMPERATURE    0x0
#define AD74XX_REG_CONFIGURATION  0x1
#define AD7414_REG_THIGH          0x2
#define AD7414_REG_TLOW           0x3

// AD74xx sensor peroforms the single conversions
//  during 4ms startup time plus 29us conversion time
#define AD74XX_ONESHOT_DELAY_us   ((AD74XX_POWERUP_TIME_us)+(AD74XX_CONVERSION_TIME_us))    

#pragma pack(push,1)
typedef union
{
   #if THERMO_SENSOR_MODEL == AD7414
   struct ad7414
   {
       uint8_t test_mode:      2;
       uint8_t one_shot:       1;
       uint8_t alert_reset:    1;
       uint8_t alert_polarity: 1;
       uint8_t alert_disable:  1;
       uint8_t i2c_filter:     1;
       uint8_t power_down:     1;
   };
   #endif

   #if THERMO_SENSOR_MODEL == AD7415
   struct ad7415
   {
       uint8_t test_mode:      2;
       uint8_t one_shot:       1;
       uint8_t test_mode_2:    3;       
       uint8_t i2c_filter:     1;
       uint8_t power_down:     1;
   };
   #endif
   
   uint8_t rawbyte;
}
sTSensorConfig;
#pragma pack(pop)

static bool AD74XX_ReadConfiguration( sTSensorConfig * pcfg );
static bool AD74XX_WriteConfiguration( sTSensorConfig cfg );
static bool AD74XX_ReadTemperature( uint8_t * pData, size_t nBytes );

#if THERMO_SENSOR_POWERCONTROL_HW
#if THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED
#if THERMO_SENSOR_POWERCONTROL_HW_LAZYCONFIG
static bool Sensor_Configured = false;
#endif
#endif
#endif

const sI2CTempSensorHandle_t I2CTempSensor = {
   .Init = TSensor_Init,
   .DeInit = TSensor_DeInit,
   .GetTemp = TSensor_GetMomentaryTemp
};

static bool TSensor_Init()
{       
    #if THERMO_SENSOR_POWERCONTROL_HW
    // Deinitialize I2C with the lines pulled down
    pI2CHandle->DeInit( eI2CPinMode_KeepPullDown );

    // Enable sensor VCC power
    // Deliver the sensor power directly from the MCU Pin PB5
    {   // Initialize POWER PIN
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.Pin = CONFIG_PIN__TSENS_PWR;  
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;        
        HAL_GPIO_Init(CONFIG_PORT__TSENS_PWR, &GPIO_InitStruct);
        HAL_GPIO_WritePin( CONFIG_PORT__TSENS_PWR, CONFIG_PIN__TSENS_PWR, GPIO_PIN_SET );

        // It is required to wait for some time while sensor starting up
        HAL_Wait_us( AD74XX_POWERUP_TIME_us );        
    }
    #endif    

    #if (THERMO_SENSOR_POWERCONTROL_HW != 0) && (THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED != 0)
    bool ret = pI2CHandle->Init( eI2CPinMode_KeepPullUp );
    #else
    bool ret = pI2CHandle->Init( eI2CPinMode_Default );
    #endif    

    if( ret )
    {
        ret = false;

        // It is required to wait for some time while sensor starting up
        // Typical startup time is 4us for AD7414        
        HAL_Wait_us( AD74XX_POWERUP_TIME_us );
        
        #if THERMO_SENSOR_POWERCONTROL_HW
        #if THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED
        #if THERMO_SENSOR_POWERCONTROL_HW_LAZYCONFIG
        if( ! Sensor_Configured )
        #endif
        #endif
        #endif
        {
            sTSensorConfig sensor_cfg;

            // Read configuration register
            if( AD74XX_ReadConfiguration( &sensor_cfg ) )
            {
                sensor_cfg.one_shot = 0;    // One-Shot conversion - disabled
                sensor_cfg.power_down = 1;  // Enable POWER-DOWN mode (without periodical conversion)
                sensor_cfg.i2c_filter = 1;  // Enable I2C-pins filtering
                #if THERMO_SENSOR_MODEL == AD7414
                sensor_cfg.alert_reset = 0;      // Disable ALERT function
                sensor_cfg.alert_polarity = 0;   // Disable ALERT function
                sensor_cfg.alert_disable = 1;    // Disable ALERT function
                #endif

                // Write configuration register:
                if( AD74XX_WriteConfiguration( sensor_cfg ) )
                {
                    // Now the sensor is power-down mode

                    #if THERMO_SENSOR_POWERCONTROL_HW
                    #if THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED
                    #if THERMO_SENSOR_POWERCONTROL_HW_LAZYCONFIG
                    Sensor_Configured = true;
                    #endif
                    #endif
                    #endif
                    
                    ret = true;                    
                }
            }
        }
        #if THERMO_SENSOR_POWERCONTROL_HW
        #if THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED
        #if THERMO_SENSOR_POWERCONTROL_HW_LAZYCONFIG
        else
          ret = true; 
        #endif
        #endif
        #endif
    }

    if( !ret )
    {
        TSensor_DeInit();  
    }

    return ret;
}

static void TSensor_DeInit()
{    
    #if THERMO_SENSOR_POWERCONTROL_HW
    #if THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED == 0
    // Disable sensor VCC power
    // The sensor power is delivered directly from the MCU Pin PB5
    {
        // Disable this pin
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.Pin = CONFIG_PIN__TSENS_PWR;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(CONFIG_PORT__TSENS_PWR, &GPIO_InitStruct);
        // #if THERMO_SENSOR_POWERCONTROL_HW_LAZYCONFIG
        // Sensor_Configured = false;
        // #endif
    }
    #endif
    #endif

    #if (THERMO_SENSOR_POWERCONTROL_HW != 0) && (THERMO_SENSOR_POWERCONTROL_HW_KEEPPOWERED != 0)
    pI2CHandle->DeInit(eI2CPinMode_KeepPullUp);
    #else
    pI2CHandle->DeInit(eI2CPinMode_Default);
    #endif
}

static bool AD74XX_WriteConfiguration( sTSensorConfig cfg )
{
   #if THERMO_SENSOR_MODEL == AD7414
   cfg.test_mode = 0;
   #endif
   #if THERMO_SENSOR_MODEL == AD7415
   cfg.test_mode = 0;
   cfg.test_mode_2 = 0;
   #endif

   uint8_t write_transaction[2] = { [0] = AD74XX_REG_CONFIGURATION, [1] = cfg.rawbyte };

   if( HAL_OK == pI2CHandle->Transmit( 
                            THERMO_SENSOR_8bit_ADDRESS,
                            write_transaction, sizeof(write_transaction),
                            WRITE_CONFIG_TIMEOUT ) )
   {
       return true;
   }

   return false;
}

static bool AD74XX_ReadConfiguration( sTSensorConfig * pcfg )
{
   uint8_t register_address = AD74XX_REG_CONFIGURATION;

   if( NULL != pcfg )
   {
       if( HAL_OK == pI2CHandle->Transmit( 
                                THERMO_SENSOR_8bit_ADDRESS,
                                &register_address, 1,
                                WRITE_ADDRESS_TIMEOUT ) )
       {

           if( HAL_OK == pI2CHandle->Receive( 
                                    THERMO_SENSOR_8bit_ADDRESS,
                                    &pcfg->rawbyte, 1,
                                    READ_CONFIG_TIMEOUT ) )
           {
               return true;
           }
       }
   }

   return false;
}

static bool AD74XX_ReadTemperature( uint8_t * pData, size_t nBytes )
{
   uint8_t register_address = AD74XX_REG_TEMPERATURE;

   if( NULL != pData && (nBytes>0) )
   {
       if( HAL_OK == pI2CHandle->Transmit( 
                                THERMO_SENSOR_8bit_ADDRESS,
                                &register_address, 1,
                                WRITE_ADDRESS_TIMEOUT ) )
       {
           if( nBytes > 2 ) nBytes = 2;   // maximum 2 bytes

           if( HAL_OK == pI2CHandle->Receive( 
                                    THERMO_SENSOR_8bit_ADDRESS,
                                    pData, nBytes,
                                    READ_TEMP_TIMEOUT ) )
           {
               return true;
           }
       }
   }
   return false;
}

static bool TSensor_GetMomentaryTemp( tTSensorTemp * temp )
{
   sTSensorConfig sensor_cfg;

   // Read configuration register
   if( AD74XX_ReadConfiguration( &sensor_cfg ) )
   {
       sensor_cfg.power_down = 1; // set the POWER-DOWN mode (do not measure by itself)
       sensor_cfg.one_shot = 1;  // set ONE-SHOT measuing mode

       if( AD74XX_WriteConfiguration( sensor_cfg ) )
       {
           HAL_Wait_us( AD74XX_ONESHOT_DELAY_us );

           uint8_t raw_temp[2];

           if( AD74XX_ReadTemperature( raw_temp, sizeof(raw_temp) ) )
           {
               *((uint16_t*)temp) = ((uint16_t)raw_temp[0] << 8) | (uint16_t)raw_temp[1];

               return true;
           }
       }
   }

   return false;   
}


