/**
******************************************************************************
* @file zero_cross.c
* @brief Модуль фиксирующий переход через ноль
******************************************************************************
* @details
******************************************************************************/

#include "zero_cross.h"
#include <string.h>

/** 
  * @brief    Инициализация детектора нуля с индивидуальными настройками.
  * @param    zc               Указатель на хендл детектора нуля
  * @param    num_channels     Количество каналов для этого хендла
  * @param    hysteresis       Гистерезис для избежания дребезга
  * @param    debounce_samples Количество samples для антидребезга
  * @return   HAL Status.
  */
HAL_StatusTypeDef ZC_Init(ZeroCross_Handle_t *zc, uint8_t num_channels, 
                         float hysteresis, uint16_t debounce_samples)
{
  if (zc == NULL || num_channels == 0 || num_channels > ZC_MAX_CHANNELS) {
    return HAL_ERROR;
  }
  
  // Инициализация структуры
  memset(zc, 0, sizeof(ZeroCross_Handle_t));
  
  // Установка параметров хендла
  zc->Config.NumChannels = num_channels;
  zc->Config.Hysteresis = hysteresis;
  zc->Config.DebounceSamples = debounce_samples;
  
  // Инициализация каналов
  for (int i = 0; i < num_channels; i++) {
    zc->Channel[i].CrossCount = 0;
    zc->Channel[i].LastValue = 0.0f;
    zc->Channel[i].CurrentValue = 0.0f;
    zc->Channel[i].DebounceCounter = 0;
    zc->Channel[i].LastCrossTime = 0;
    zc->Channel[i].Period = 0;
    zc->Channel[i].Frequency = 0.0f;
    zc->Channel[i].EdgeType = ZC_BOTH_EDGES;
  }
  
  zc->f.Initialized = 1;
  zc->f.Monitoring = 1;
    
  return HAL_OK;
}

/** 
  * @brief    Настройка канала детектора.
  * @param    zc        Указатель на хендл детектора нуля
  * @param    channel   Номер канала для конфигурации
  * @param    edgeType  Тип детектируемого перехода
  * @return   HAL Status.
  */
HAL_StatusTypeDef ZC_ConfigChannel(ZeroCross_Handle_t *zc, uint8_t channel, 
                                  ZC_EdgeType_t edgeType)
{
  if (assert_upp(zc)){
    return HAL_ERROR;
  }
  if (channel >= zc->Config.NumChannels) {
    return HAL_ERROR;
  }
  
  zc->Channel[channel].EdgeType = edgeType;
  
  // Сброс состояния канала при реконфигурации
  zc->Channel[channel].LastValue = 0.0f;
  zc->Channel[channel].DebounceCounter = 0;
  zc->Channel[channel].LastCrossTime = 0;
  zc->Channel[channel].Period = 0;
  zc->Channel[channel].Frequency = 0.0f;
  zc->Channel[channel].CrossCount = 0;
  
  return HAL_OK;
}
/** 
  * @brief    Основная функция обработки значения канала.
  * @param    zc         Указатель на хендл детектора нуля
  * @param    channel    Номер канала
  * @param    value      Текущее значение сигнала
  * @param    timestamp  Временная метка измерения в микросекундах
  */
void ZC_ProcessChannel(ZeroCross_Handle_t *zc, uint8_t channel, float value, uint32_t timestamp)
{
  if (assert_upp(zc)){
    return;
  }  
  if (!zc->f.Monitoring) {
    return;
  }  
  if (channel >= zc->Config.NumChannels) {
    return;
  }
  int zc_detected = 0;
  
  ZC_Channel_t *zc_ch = &zc->Channel[channel];
  zc_ch->CurrentValue = value;
  
  
  // Фильтрация дребезга
  if(zc_ch->DebounceCounter > 0)
  {
    zc_ch->DebounceCounter--;
    return;
  }
  
  // Детектирование rising edge (отрицательное -> положительное)
  if ((zc_ch->LastValue <= -zc->Config.Hysteresis) && 
      (value >= zc->Config.Hysteresis))
  {
    
    if (zc_ch->EdgeType == ZC_RISING_EDGE || zc_ch->EdgeType == ZC_BOTH_EDGES) {
      if(zc->Config.DebounceSamples)
      {
        if(zc_ch->DebounceCounter == 0)
        {
          zc_ch->DebounceCounter = zc->Config.DebounceSamples;
        }
      }
      
      zc_detected = 1;
    }
  }
  // Детектирование falling edge (положительное -> отрицательное)
  else if ((zc_ch->LastValue >= zc->Config.Hysteresis) && 
           (value <= -zc->Config.Hysteresis))
  {
    
    if (zc_ch->EdgeType == ZC_FALLING_EDGE || zc_ch->EdgeType == ZC_BOTH_EDGES) {
      if(zc->Config.DebounceSamples)
      {
        if(zc_ch->DebounceCounter == 0)
        {
          zc_ch->DebounceCounter = zc->Config.DebounceSamples;
        }
      }
      
      zc_detected = -1;
    }
  }
  
  if(zc_detected)
  {
    zc_ch->HalfWave = zc_detected == 1 ? UPP_WAVE_POSITIVE : UPP_WAVE_NEGATIVE;
    zc_ch->Occurred = 1;
    uint32_t RealTimeShift = 0;//zc->Config.DebounceSamples*(timestamp - zc->LastTick); // коло-во тиков * период вызова функции
    
    // Переход подтвержден сразу
    uint32_t RealTimestamp = timestamp-RealTimeShift;
    if (zc_ch->LastCrossTime != 0) {
      // Расчет периода и частоты
      float curr_half_period = RealTimestamp - zc_ch->LastCrossTime;
      if (curr_half_period > 0) {
        if(zc_ch->HalfWave == UPP_WAVE_POSITIVE)
        {
          zc_ch->PeriodStartTime = RealTimestamp;
          zc_ch->Period = zc_ch->halfPeriod + curr_half_period;
          zc_ch->Frequency = 1000000.0f / zc_ch->Period;
          zc_ch->FrequencyOffset = (float)zc_ch->halfPeriod / curr_half_period;
        }
      }
      zc_ch->halfPeriod = curr_half_period;
    }
    
    zc_ch->LastCrossTime = RealTimestamp;
    zc_ch->CrossCount++;
  }
  
  // Сохраняем текущее значение для следующей итерации в случае если оно не в мертвой зоне
  if((value > zc->Config.Hysteresis) || (value < -zc->Config.Hysteresis))
  {
    zc_ch->LastValue = value;
  }
}

/** 
  * @brief    Пакетная обработка всех каналов.
  * @param    zc         Указатель на хендл детектора нуля
  * @param    values     Массив значений сигналов
  * @param    timestamp  Временная метка измерения
  */
void ZC_ProcessAllChannels(ZeroCross_Handle_t *zc, float *values, uint32_t timestamp)
{
  if (assert_upp(zc)){
    return;
  }
  
  if (!zc->f.Monitoring || values == NULL) {
    return;
  }
  
  for (int ch = 0; ch < zc->Config.NumChannels; ch++) {
    ZC_ProcessChannel(zc, ch, values[ch], timestamp);
  }
  
  zc->LastTick = timestamp;
}


/** 
  * @brief    Полученить флаг - переход произошел.
  * @param    zc       Указатель на хендл детектора нуля
  * @param    channel  Номер канала
  * @return   1 - переход произошел, 0 - перехода не было.
  */
int ZC_isOccurred(ZeroCross_Handle_t *zc, uint8_t channel)
{
  if (assert_upp(zc)){
    return 0;
  } 
  int occurred = zc->Channel[channel].Occurred;
  zc->Channel[channel].Occurred = 0;
  return occurred;
}

/** 
  * @brief    Получение полуволны (после последнего zero-cross).
  * @param    zc       Указатель на хендл детектора нуля
  * @param    channel  Номер канала
  * @return   Текущая полуволна @ref UPP_HalfWave_t.
  */
UPP_HalfWave_t ZC_GetHalfWave(ZeroCross_Handle_t *zc, uint8_t channel)
{
  if (assert_upp(zc)){
    return UPP_WAVE_UNKNOWED;
  }
  
  return zc->Channel[channel].HalfWave;
}

/** 
  * @brief    Получение частоты сигнала.
  * @param    zc       Указатель на хендл детектора нуля
  * @param    channel  Номер канала
  * @return   Частота в Гц.
  */
float ZC_GetFrequency(ZeroCross_Handle_t *zc, uint8_t channel)
{
  if (assert_upp(zc)){
    return 0.0f;
  }  
  if (channel >= zc->Config.NumChannels) {
    return 0.0f;
  }
  
  return zc->Channel[channel].Frequency;
}

/** 
  * @brief    Получение смещение частот полупериода сигнала.
  * @param    zc       Указатель на хендл детектора нуля
  * @param    channel  Номер канала
  * @return   Отношение длительности первой полуволны к второй полуволне.
  */
float ZC_GetOffset(ZeroCross_Handle_t *zc, uint8_t channel)
{
  if (assert_upp(zc)){
    return 0.0f;
  }  
  if (channel >= zc->Config.NumChannels) {
    return 0.0f;
  }
  
  return zc->Channel[channel].FrequencyOffset;
}


/** 
  * @brief    Получить сдвиг между двумя фазами.
  * @param    zc        Указатель на хендл детектора нуля
  * @param    channel   Номер канала от которого считать сдвиг
  * @param    channe2   Номер канала для которого расчитать сдвиг
* @return   Фазовый сдвиг в процентах от периода от 1 до 2 канала
  */
float ZC_GetPhaseShift(ZeroCross_Handle_t *zc, uint8_t channel1, uint8_t channel2)
{
  if (assert_upp(zc)){
    return 0.0f;
  }
  
  if ((channel1 >= zc->Config.NumChannels) || (channel2 >= zc->Config.NumChannels)){
    return 0.0f;
  }
  
  float delta_time = 0;
  if(zc->Channel[channel1].PeriodStartTime >= zc->Channel[channel2].PeriodStartTime)
    delta_time = (zc->Channel[channel1].PeriodStartTime - zc->Channel[channel2].PeriodStartTime);
  else
    delta_time = (zc->Channel[channel2].PeriodStartTime - zc->Channel[channel1].PeriodStartTime);
  
  
  float phase_ratio = delta_time/zc->Channel[channel1].Period;
  return phase_ratio;
}


/** 
  * @brief    Включение/выключение мониторинга.
  * @param    zc        Указатель на хендл детектора нуля
  * @param    enable    Флаг включения (1 - вкл, 0 - выкл)
  */
void ZC_EnableMonitoring(ZeroCross_Handle_t *zc, uint8_t enable)
{
  if (assert_upp(zc)){
    return;
  }
  if (zc == NULL || !zc->f.Initialized) {
    return;
  }
  
  zc->f.Monitoring = enable ? 1 : 0;
}

/** 
  * @brief    Сброс статистики канала.
  * @param    zc       Указатель на хендл детектора нуля
  * @param    channel  Номер канала
  */
void ZC_Reset(ZeroCross_Handle_t *zc, uint8_t channel)
{
  if (assert_upp(zc)){
    return;
  }
  
  if (channel < zc->Config.NumChannels) {
    zc->Channel[channel].LastValue = 0.0f;
    zc->Channel[channel].CurrentValue = 0.0f;
    zc->Channel[channel].DebounceCounter = 0;
    zc->Channel[channel].LastCrossTime = 0;
    zc->Channel[channel].Period = 0;
    zc->Channel[channel].Frequency = 0.0f;
    zc->Channel[channel].CrossCount = 0;
  }
  else {
    // Сброс всех каналов
    for (int i = 0; i < zc->Config.NumChannels; i++) {
      zc->Channel[i].LastValue = 0.0f;
      zc->Channel[i].CurrentValue = 0.0f;
      zc->Channel[i].DebounceCounter = 0;
      zc->Channel[i].LastCrossTime = 0;
      zc->Channel[i].Period = 0;
      zc->Channel[i].Frequency = 0.0f;
      zc->Channel[i].CrossCount = 0;
    }
  }
}