/**
  ******************************************************************************
  * @file    gpio.c
  * @author  Разваляев Алексей
  * @brief   Драйвер GPIO на основе PLIB035.
  *          Этот файл содержит:
  *           + Инициализацию портов GPIOA и GPIOB
  *           + Функции для работы со светодиодами:
  *               - Инициализацию структуры светодиода
  *               - Включение, выключение, переключение светодиода
  *               - Динамические режимы: моргание и плавное затухание
  *           + Функции для работы с кнопками:
  *               - Инициализацию структуры кнопки
  *               - Чтение состояния кнопки с защитой от дребезга
  *           + Утилитарные функции для получения конфигурации пинов
  *
  ******************************************************************************
  * @attention
  * 
  * Использование этого драйвера предполагает наличие корректных настроек:
  *  - Определены массивы конфигурации gpioa_config и gpiob_config в periph_config.h
  *
  ******************************************************************************
  * @verbatim
  ==============================================================================
                        ##### Как использовать этот драйвер #####
  ==============================================================================
  
  1. Настройка периферии (periph_config.h):
      (+) Определить массивы GPIO_Init_TypeDef для портов:
            gpioa_config[32], gpiob_config[32]
      (+) Настроить режим пинов: Input, Output, AltFunc или не используется (аналог режим)

  2. Инициализация GPIO:
      (+) gpio_init() — инициализация портов GPIOA и GPIOB

  3. Работа со светодиодами:
      (+) GPIO_LED_Init(&led, GPIOA, GPIO_PIN_5, SET) — инициализация светодиода
      (+) GPIO_LED_On(&led) — включение светодиода
      (+) GPIO_LED_Off(&led) — выключение светодиода
      (+) GPIO_LED_Toggle(&led) — переключение светодиода
      (+) GPIO_LED_Blink_Start(&led, 500) — запуск моргания (период 500 мс)
      (+) GPIO_LED_Fading_Start(&led, 1000) — запуск плавного затухания (период 1 секунда)
      (+) GPIO_LED_Dynamic_Handle(&led) — обработка динамических режимов в основном цикле

  4. Работа с кнопками:
      (+) GPIO_Switch_Init(&sw, GPIOB, GPIO_PIN_0, SET) — инициализация кнопки
      (+) GPIO_Read_Switch(&sw) — чтение состояния кнопки с фильтрацией

  5. Получение конфигурации пинов:
      (+) gpio_get_init(порт, пин) - для периферии

  ==============================================================================
                          ##### Особенности работы #####
  ==============================================================================

  - Динамические режимы светодиодов:
      - Моргание: простой on/off с заданным периодом
      - Плавное затухание: квадратичное изменение яркости через ШИМ
      - Требуют вызова GPIO_LED_Dynamic_Handle() в основном цикле

  - Защита от дребезга:
      - Фильтрация по времени (Sw_FilterDelay в миллисекундах)
      - Неблокирующий алгоритм с отслеживанием состояния
      - При изменении уровня запускается таймер, состояние обновляется после задержки

  - ШИМ для плавного затухания:
      - Программная генерация с 15 уровнями яркости (LED_PWM_TICKS)
      - Полный цикл затухания-разтухания: 2 × LED_PWM_TICKS шагов

  - Конфигурация пинов:
      - Периферия может перезаписывать конфигурацию пинов (например, UART на AltFunc)
      - gpio_get_init() позволяет периферии получить исходную конфигурацию
      - После использования периферии можно восстановить исходные настройки

  - Табличный подход:
      - Каждый пин имеет свою строку в массиве конфигурации
      - Позволяет централизованно управлять всеми пинами
      - Упрощает отладку и модификацию конфигурации
      
  @endverbatim
  ******************************************************************************
  */

//-- Includes ------------------------------------------------------------------
#include "periph_config.h"

//-- Defines -------------------------------------------------------------------

//-- GPIO init functions -------------------------------------------------------
void gpio_init(void)
{
  RCU_AHBClkCmd(RCU_AHBClk_GPIOA, ENABLE);
  RCU_AHBRstCmd(RCU_AHBRst_GPIOA, ENABLE);
  RCU_AHBClkCmd(RCU_AHBClk_GPIOB, ENABLE);
  RCU_AHBRstCmd(RCU_AHBRst_GPIOB, ENABLE);
  
  /* Сброс пинов */
  GPIO_DeInit(GPIOA);
  GPIO_DeInit(GPIOB);
  
  // Инициализация порта A
  for (int i = 0; i < sizeof(gpioa_config) / sizeof(gpioa_config[0]); i++)
  {
      GPIO_Init(GPIOA, &gpioa_config[i]);
  }
  
  // Инициализация порта B
  for (int i = 0; i < sizeof(gpiob_config) / sizeof(gpiob_config[0]); i++)
  {
      GPIO_Init(GPIOB, &gpiob_config[i]);
  }
}


GPIO_Init_TypeDef *gpio_get_init(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)
{
  uint8_t pin_index = (31 - __CLZ(GPIO_Pin & -GPIO_Pin));
  
  if (GPIOx == GPIOA)
  {
    // Проверяем границы массива
    if (pin_index < sizeof(gpioa_config) / sizeof(gpioa_config[0]))
    {
      return &gpioa_config[pin_index];
    }
  }
  else if (GPIOx == GPIOB)
  {
    // У тебя была опечатка: возвращал gpioa_config вместо gpiob_config
    if (pin_index < sizeof(gpiob_config) / sizeof(gpiob_config[0]))
    {
      return &gpiob_config[pin_index];
    }
  }
  
  return NULL;
}



//-- GPIO LED functions --------------------------------------------------------

/**
  * @brief  Инициализировать светодиод (структуру светодиода)
  * @param led              Указатель на структуру светодиода
  * @param GPIOx            Указатель на структуру порта для светодиода
  * @param GPIO_PIN_X       Пин для светодиода
  * @param LED_ActiveLevel  Состояния пина, при котором светодиод будет включен
  */
OperationStatus GPIO_LED_Init(GPIO_LEDTypeDef *led, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, BitState LED_ActiveLevel)
{
  if(!led || !GPIOx ||!GPIO_PIN_X)
    return ERROR;
  
  led->LED_Port       = GPIOx;
  led->LED_Pin        = GPIO_PIN_X;
  led->LED_ActiveLvl  = LED_ActiveLevel;
  
  GPIO_LED_Off(led);
  return OK;
}

/**
  * @brief  Включить светодиод 
  * @param  led Указатель на структуру светодиода
  * @return Operation Status
  */
OperationStatus GPIO_LED_On(GPIO_LEDTypeDef *led)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  led->state = LED_IS_ON;
  GPIO_WriteBit(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl);

  
  return OK;
}
/**
  * @brief  Выключить светодиод 
  * @param  led Указатель на структуру светодиода
  * @return Operation Status
  */
OperationStatus GPIO_LED_Off(GPIO_LEDTypeDef *led)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  led->state = LED_IS_OFF;
  BitState offstate = (led->LED_ActiveLvl == SET)? CLEAR: SET;
  GPIO_WriteBit(led->LED_Port, led->LED_Pin, offstate);

  
  
  return OK;
}

/**
  * @brief  Переключить светодиод
  * @param  led        Указатель на структуру светодиода
  * @return Operation Status
  */
OperationStatus GPIO_LED_Toggle(GPIO_LEDTypeDef *led)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  
  if(led->state == LED_IS_ON || led->state == LED_IS_OFF)
  {
    if(led->state == LED_IS_OFF)
      led->state = LED_IS_ON;
    else
      led->state = LED_IS_OFF;
    
    GPIO_ToggleBits(led->LED_Port, led->LED_Pin); 
    return OK;
  }
  else
    return ERROR;
}

/**
  * @brief  Выставить светодиод по переменной
  * @param  led        Указатель на структуру светодиода
  * @param  led_state  Состояние светодиода
  * @return Operation Status
  */
OperationStatus GPIO_LED_Set(GPIO_LEDTypeDef *led, uint8_t led_state)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  if(led_state)
  {
    return GPIO_LED_On(led);
  }
  else
  {
    return GPIO_LED_Off(led);
  }
}
/**
  * @brief  Активировать моргание светодиодом
  * @param  led Указатель на структуру светодиода
  * @param  period Период плавного моргания светодиода
  * @return Operation Status
  * @details  Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle
  */
OperationStatus GPIO_LED_Blink_Start(GPIO_LEDTypeDef *led, uint32_t period)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  led->state = LED_IS_BLINKING;
  led->LED_Period = period;
    
  return OK;
}
  
/**
  * @brief  Активировать моргание светодиодом
  * @param  led  Указатель на структуру светодиода
  * @param  period Период плавного моргания светодиода
  * @return Operation Status
  * @details  Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle
  */
OperationStatus GPIO_LED_Fading_Start(GPIO_LEDTypeDef *led, uint32_t period)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return ERROR;  
  
  led->state = LED_IS_FADING;
  led->LED_Period = period;
  
    
  return OK;
}

//uint8_t LED_PWM_FADING_DUTYS[LED_PWM_TICKS] = {0 1 2 3 4 5 6 7 8 9 10 11 12 }
/**
  * @brief  Управление динамическими режимами свечения светодиода
  * @param  Указатель на структуру светодиода
  * @details  Функция моргает/плавно моргает светодиодом в неблокирующем режиме
  * Т.е. функцию надо вызывать постоянно, чтобы она мониторила тики 
  * и в нужный момент переключала светодиод
  */
void GPIO_LED_Dynamic_Handle(GPIO_LEDTypeDef *led)
{
  if(!led || !led->LED_Port || !led->LED_Pin)
    return;
  
  /* Режим моргания светодиода */
  if(led->state == LED_IS_BLINKING)
  {
    uint32_t tickcurrent = millis();
    /* Ожидание истечения периода моргания */
    if((tickcurrent - led->tickprev) > led->LED_Period)
    {
      /* Моргание */
      GPIO_ToggleBits(led->LED_Port, led->LED_Pin);
      
      led->tickprev = tickcurrent;
    }
  }
  /* Режим плавного моргания светодиода */
  else if(led->state == LED_IS_FADING)
  {
    static unsigned direction = 0;
    static int duty = 0;
    uint32_t tickcurrent = millis();
    /* Ожидание момента изменения яркости */
    /* Период ШИМ 20 мс, поэтому менять яроксть надо 40 раз за период (туда обратно) */
    if((tickcurrent - led->tickprev) > led->LED_Period/(LED_PWM_TICKS*2))
    {
      /* Формирование разтухания */
      if(direction == 0)
      {
        if(++duty >= LED_PWM_TICKS)
        {
          direction = 1;
          duty = LED_PWM_TICKS;
        }
      }
      /* Формирование затухания */
      else
      {
        if(--duty  <= 0)
        {
          direction = 0;
          duty = 0;
        }
      }
      led->tickprev = tickcurrent;
    }
    /* Формирование ШИМ для изменения яркости */
    int duty_crt = (duty*duty/LED_PWM_TICKS);
    if(tickcurrent%LED_PWM_TICKS < duty_crt)
    {
      GPIO_WriteBit(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl);
    }
    else
    {
      BitState offstate = (led->LED_ActiveLvl == SET)? CLEAR: SET;
      GPIO_WriteBit(led->LED_Port, led->LED_Pin, offstate);
    }
  }
}

//-- GPIO Switch functions -----------------------------------------------------

/**
* @brief  Инициализировать кнопку (структуру кнопки)
  * @param  sw             Указатель на структуру кнопки
  * @param  GPIOx          Указатель на структуру порта для кнопки
  * @param  GPIO_PIN_X     Пин для кнопки
  * @param  SW_ActiveLevel Состояния пина, когда кнопка нажата
  * @return Operation Status
  */
OperationStatus GPIO_Switch_Init(GPIO_SwitchTypeDef *sw, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, BitState SW_ActiveLevel)
{
  if(!sw || !GPIOx || !GPIO_PIN_X)
    return ERROR;
  
  sw->Sw_Port       = GPIOx;
  sw->Sw_Pin        = GPIO_PIN_X;
  sw->Sw_ActiveLvl  = SW_ActiveLevel;
  return OK;
}

/**
  * @brief  Считать состоянии кнопки 
  * @param  sw  Указатель на структуру кнопки
  * @return 1 - если кнопка нажата, 
  *         0 - если отжата, 
  *         -1 - если ошибка
  * @details  Функция включает в себя неблокирующую проверку на дребезг
  * Т.е. функцию надо вызывать постоянно, чтобы она мониторила состояние кнопки
  */
int GPIO_Read_Switch(GPIO_SwitchTypeDef *sw)
{
  if(!sw || !sw->Sw_Port || !sw->Sw_Pin)
    return -1;
  
  int current_level = (GPIO_ReadBit(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl);
    
  if(sw->Sw_FilterDelay) // если включена защита от дребезга
  {
    // Если таймер не запущен и состояние изменилось - запускаем таймер
    if(sw->tickprev == 0 && current_level != sw->Sw_CurrentState)
    {
      sw->tickprev = millis();
    }
    
    // Если таймер запущен
    if(sw->tickprev != 0)
    {
      // Проверяем, прошел ли достаточный интервал для фильтрации
      if((millis() - sw->tickprev) >= sw->Sw_FilterDelay)
      {
        // Обновляем состояние только если оно все еще отличается
        if(current_level != sw->Sw_CurrentState)
        {
          sw->Sw_CurrentState = current_level;
        }
        // Останавливаем таймер в любом случае
        sw->tickprev = 0;
      }
    }
  }
  else // если нет защиты от дребезга
  {
    sw->Sw_CurrentState = current_level;
  }
  return sw->Sw_CurrentState;
}
