#include "stm32l1xx_hal.h"
#include "stm32l1xx_hal_pwr.h"
#include "stm32l1xx_hal_pwr_ex.h"
#include "stm32l1xx_hal_pcd.h"
#include "core/gpio.h"
#include "core/main.h"
#include "core/sleep_and_exti.h"
#include "core/csect.h"
#include <intrinsics.h>
#include "core/config.h"
#include "core/config_pins.h"

// USB_VBUS_WKPIN
// Specifies the special wake up pin (WKUP1..WKUP3) for waking up from USB_VBUS signal
// Possible values: PWR_WAKEUP_PIN1, PWR_WAKEUP_PIN2, PWR_WAKEUP_PIN3, or undefined
#define USB_VBUS_WKPIN        (CONFIG_PIN__USB__VBUSWKUP)


// USB_VBUS_LINE
// Specifies the GPIO line number for USB_VBUS detecting using EXTI GPIO 
// Possible values: GPIO_PIN_0 .. GPIO_PIN_15
#define USB_VBUS_LINE         (CONFIG_PIN__USB__VBUSDETECT)

// USB_VBUS_PORT
// Specifies the GPIO port for USB_VBUS detecting using EXTI GPIO 
// Possible values: GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOH
#define USB_VBUS_PORT         (CONFIG_PORT__USB__VBUSDETECT)

// USB_VBUS_PORT_CLKEN
// Enables the corresponding port clocking (see USB_VBUS_PORT)
#define USB_VBUS_PORT_CLKEN() __HAL_RCC_GPIOC_CLK_ENABLE()
#define USB_VBUS_PORT_CLKDS() __HAL_RCC_GPIOC_CLK_DISABLE()

// USB_VBUS_EXTI_IRQ
// Specifies the IRQ-number for USB_VBUS detecting using EXTI GPIO
// Possible values: EXTI15_10_IRQn, EXTI9_5_IRQn, EXTI4_IRQn, EXTI3_IRQn, EXTI2_IRQn, EXTI1_IRQn, EXTI0_IRQn
#define USB_VBUS_EXTI_IRQ     (EXTI15_10_IRQn) 

// USB_INTERNAL_WKUP_SUPPORT
// Enables internal USB wakeup-event support
#define USB_INTERNAL_WKUP_SUPPORT   CONFIG_USB_INTERNAL_WKUP_SUPPORT

// USB_DEBUG_SUPRESS_VBUS_WKUP
// Debug: do not wakeup on VBUS line changing
#define USB_DEBUG_SUPRESS_VBUS_WKUP CONFIG_DEBUG_USB_SUPRESS_VBUS_WKUP

#if CONFIG_SLEEPMANAGER
// This module uses WakeUp Pin #2 as a wakeup source
// This WakeUp Pin (PC13, Pin #2) is connected to USB_VBUS
// Rising edge means the USB cable plugging in
// Falling edge means the USB cable unplugging
static bool bInitialized = false;
static bool bUSBActive = false;  // shows if USB in suspend mode or not
static bool bUSBPlugged = false; // shows if USB cable connected (USB VBUS Pin monitoring)
static eSleepManager_WakeupReason_t xWakeupReason = eSleepManager_GenericWakeup;

static bool SleepManager_Init();
static void SleepManager_DeInit();
static bool SleepManager_SetUSBWakeUp( bool );
static bool SleepManager_EnterSleepTimeout( uint32_t );
static bool SleepManager_EnterSleep();
static bool SleepManager_GetUSBPlugged();
static bool SleepManager_GetUSBActive();
static eSleepManager_WakeupReason_t SleepManager_Sleep();
static eSleepManager_WakeupReason_t SleepManager_SleepTimeout( uint32_t );
static bool SleepManager_USBPlugStatusUpdate();

const sSleepManager_Handle_t SleepManagerHandle = {

     .Init = SleepManager_Init,
     .SetUSBWakeup = SleepManager_SetUSBWakeUp,
     .Sleep = SleepManager_Sleep,
     .SleepTimeout = SleepManager_SleepTimeout,
     .GetUSBPlugged = SleepManager_GetUSBPlugged,     
     .GetUSBActive = SleepManager_GetUSBActive,     
     .DeInit = SleepManager_DeInit,
};

static void SleepManager_USBSuspendEvent();
static void SleepManager_USBResumeEvent();

const sSleepManager_Notify_t SleepManagerNotify = {

     .USBSuspendEvent = SleepManager_USBSuspendEvent,
     .USBResumeEvent = SleepManager_USBResumeEvent, 
};

static bool SleepManager_Init()
{    
    __DI__
      
      HAL_PWR_DisableSleepOnExit();

      HAL_PWREx_EnableFastWakeUp();

      HAL_PWREx_DisableLowPowerRunMode();
      
      bInitialized = true;

      SleepManager_SetUSBWakeUp( false );

      SleepManager_USBPlugStatusUpdate();

    __EI__

    return true;
}

static void SleepManager_DeInit()
{        
    __DI__

      HAL_PWREx_DisableFastWakeUp();

      HAL_PWR_DisableSleepOnExit();

      HAL_PWREx_DisableLowPowerRunMode();
            
      SleepManager_SetUSBWakeUp( false );

      bInitialized = false;
    
   __EI__
}

static bool SleepManager_USBPlugStatusUpdate()
{
    bool bValue = (GPIO_PIN_SET == HAL_GPIO_ReadPin( USB_VBUS_PORT, USB_VBUS_LINE ));

    __DI__ 
      bUSBPlugged = bValue;
    __EI__

    return bValue;
}

static bool SleepManager_SetUSBWakeUp( bool state )
{
    bool ret = false;

    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __DI__
      if( bInitialized )
      {
          if( state )
          {
              // Configure GPIO pins for VBUS

              #if USB_DEBUG_SUPRESS_VBUS_WKUP == 0
              // Enable port clocking
              USB_VBUS_PORT_CLKEN();

              // Configure EXTI line
              GPIO_InitStruct.Pin = USB_VBUS_LINE;
              GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; // use rising and falling edges
              GPIO_InitStruct.Pull = GPIO_PULLDOWN;   
              GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;    
              HAL_GPIO_Init( USB_VBUS_PORT, &GPIO_InitStruct );

              // Enable EXTI Interrupt
              HAL_NVIC_EnableIRQ( USB_VBUS_EXTI_IRQ );

              #ifdef USB_VBUS_WKPIN
              HAL_PWR_EnableWakeUpPin( USB_VBUS_WKPIN );
              #endif
              #endif

              #if USB_INTERNAL_WKUP_SUPPORT
              __HAL_USB_WAKEUP_EXTI_ENABLE_IT();  // Enable corresponding internal EXTI line (USB Wakeup)            
              HAL_NVIC_EnableIRQ( USB_FS_WKUP_IRQn );
              #endif
          }
          else
          {
              #if USB_INTERNAL_WKUP_SUPPORT
              HAL_NVIC_DisableIRQ( USB_FS_WKUP_IRQn );
              __HAL_USB_WAKEUP_EXTI_DISABLE_IT();  // Disable corresponding internal EXTI line (USB Wakeup)
              #endif

              #if USB_DEBUG_SUPRESS_VBUS_WKUP == 0
              #ifdef USB_VBUS_WKPIN
              HAL_PWR_DisableWakeUpPin( PWR_WAKEUP_PIN2 );
              #endif

              // DeConfigure EXTI line, configure as Input
              GPIO_InitStruct.Pin = USB_VBUS_LINE;
              GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // use port as input to poll signal
              GPIO_InitStruct.Pull = GPIO_NOPULL;   
              GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;    
              HAL_GPIO_Init( USB_VBUS_PORT, &GPIO_InitStruct );

              // Disable EXTI Interrupt
              HAL_NVIC_DisableIRQ( USB_VBUS_EXTI_IRQ );

              // DO NOT DISABLE PORT CLOCKING DUE TO IT CAN BE IN USE!!!
              /* __ USB_VBUS_PORT_CLKDS(); __ */
              #else
              // DeConfigure EXTI line, configure as Input
              GPIO_InitStruct.Pin = USB_VBUS_LINE;
              GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // use port as input to poll signal
              GPIO_InitStruct.Pull = GPIO_NOPULL;   
              GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;    
              HAL_GPIO_Init( USB_VBUS_PORT, &GPIO_InitStruct );
              #endif
          }

          ret = true;
      }
    __EI__
    
    return ret;
}

// SleepManager_USBSuspendEvent callback
// Is called by Low-Level PCD Module (USB HAL) to inform the high level
// about the fact that USB enters in SUSPEND mode
static void SleepManager_USBSuspendEvent()
{
    __DI__ 
      if( bInitialized )
      {
          bUSBActive = false;
      }
    __EI__
}
// SleepManager_USBResumeEvent callback
// Is called by Low-Level PCD Module (USB HAL) to inform the high level
// about the fact that USB enters in RUN mode
static void SleepManager_USBResumeEvent()
{
    __DI__
      if( bInitialized )
      {
          bUSBActive = true;
      }
    __EI__
}
                        
static bool SleepManager_GetUSBPlugged()
{   
    // Check if initialized:    
    DI(); 
      if( !bInitialized ) 
      {
          EI();
          return false;
      }
    EI();

    return SleepManager_USBPlugStatusUpdate();
}

static bool SleepManager_GetUSBActive()
{
    bool ret = false;

    __DI__ ret = bInitialized && bUSBActive; __EI__
    
    return ret;
}


static eSleepManager_WakeupReason_t SleepManager_GetWakeupReason()
{
   eSleepManager_WakeupReason_t xValue;

   __DI__ xValue = xWakeupReason; __EI__

   return xValue;
}

static bool SleepManager_EnterSleepTimeout( uint32_t timeout )
{
    xWakeupReason = eSleepManager_Error;

    // HAL_PWR_EnterSLEEPMode( PWR_LOWPOWERREGULATOR_ON,
    //                           PWR_SLEEPENTRY_WFE );
    // 

    return false;
}

static bool SleepManager_EnterSleep( )
{
    xWakeupReason = eSleepManager_GenericWakeup;

    // disable some clocks for the power consumption reducing?
    //RCC_AHBENR
    //RCC_AHB1ENR
    //RCC_AHB2ENR
    // HAL_PWR_EnableSleepOnExit();

    #if CONFIG_SLEEPMANAGER_KEEPDEBUGCLK
    HAL_DBGMCU_EnableDBGSleepMode();
    HAL_DBGMCU_EnableDBGStopMode();
    #endif

    #if CONFIG_SLEEPMANAGER_DEBUGPIN
    CONFIG_SLEEPMANAGER_DEBUGPIN_SETPIN
    #endif

    #if CONFIG_SLEEPMANAGER_DEBUG_NOSLEEP
    __NOP();
    #else
    HAL_PWR_EnterSLEEPMode( PWR_MAINREGULATOR_ON,      // OK
                            PWR_SLEEPENTRY_WFI );    // OK

    //  HAL_PWR_EnterSLEEPMode( PWR_LOWPOWERREGULATOR_ON,   //??
    //                            PWR_SLEEPENTRY_WFI );    //??
    //HAL_PWR_EnterSLEEPMode( PWR_MAINREGULATOR_ON,
    //                          PWR_SLEEPENTRY_WFE );
    //HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, //PWR_MAINREGULATOR_ON,
    //                          PWR_STOPENTRY_WFI );
    //HAL_PWR_EnterSTOPMode( PWR_LOWPOWERREGULATOR_ON, //PWR_MAINREGULATOR_ON,
    //                          PWR_STOPENTRY_WFE );

    #endif
    #if CONFIG_SLEEPMANAGER_DEBUGPIN
    CONFIG_SLEEPMANAGER_DEBUGPIN_CLRPIN
    #endif

    return true;
}

static eSleepManager_WakeupReason_t SleepManager_Sleep()
{
    if( SleepManager_EnterSleep() )
      return SleepManager_GetWakeupReason();

    return eSleepManager_Error;
}

static eSleepManager_WakeupReason_t SleepManager_SleepTimeout( uint32_t timeout )
{
    if( SleepManager_EnterSleepTimeout( timeout ) )
      return SleepManager_GetWakeupReason();

    return eSleepManager_Error;
}
#endif

#if USB_INTERNAL_WKUP_SUPPORT
//
// USB-Wakeup Interrupt Event Routing
//
void USB_FS_WKUP_IRQHandler()  // startup_stm32l151xb.s
{
    xWakeupReason = eSleepManager_USBPlugWakeup;

    HAL_PWR_DisableSleepOnExit();    
}
#endif

// EXTI line detection callbacks.
// Note: It is required to route the interrupt routine to the
//  HAL-function HAL_GPIO_EXTI_IRQHandler() 
//  e.g. void EXTI4_IRQHandler { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); }
//
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)  // stm32l1xx_hal_gpio.c
{
    // USB VBUS is connected to PC13.
    // This callback is called if any GPIO pin is triggered and
    // the corresponding EXTI-line is configured.
    // Check if the pin number is corresponds to PC13:
    if( USB_VBUS_LINE == GPIO_Pin )
    {
        #if CONFIG_SLEEPMANAGER
        SleepManager_USBPlugStatusUpdate();
        xWakeupReason = eSleepManager_USBPlugWakeup;
        #endif

        HAL_PWR_DisableSleepOnExit();
    }
}

