#pragma once

#include <sndfile.h>
#include <hack/logger/logger.hpp>

#include "utils/var.hpp"                                  // IWYU pragma: keep
#include "utils/using.hpp"
#include "utils/workers/setup.hpp"
#include "utils/workers/result.hpp"
#include "adapter/adapter.hpp"

#include "plugins/raw_data/raw_data.hpp"                  // IWYU pragma: keep
#include "plugins/magnitude/magnitude.hpp"                // IWYU pragma: keep

namespace hr
{
  /**
   * @brief Запуск на чтение аудиофайла и обработка его через плагин
   * @tparam Plugin Тип плагина для обработки аудиоданных
   * @param setup Настройки обработки (путь к файлу, параметры и т.д.)
   * @return Результат обработки аудио
   */
  template<typename Plugin>
  inline result run(setup& setup)
  {
    // Инициализация структуры для libsndfile и открытие файла
    SF_INFO sf_info;
    SNDFILE* file = sf_open(setup.m_file.c_str(), SFM_READ, &sf_info);
    if (!file) 
    {
      // Обработка ошибки открытия файла
      hack::exception ex;
      hack::log().on_file();
      hack::log().on_func();
      hack::log().on_row();
      ex.title("Error of open file");
      ex.description(sf_strerror(file));
      ex.set("file", setup.m_file);
      hack::error()(ex);
      throw ex;
    }

    // Сохранение информации о файле в настройки
    setup.m_sample_rate = sf_info.samplerate;
    setup.m_frames = sf_info.frames; 
    setup.m_channels = sf_info.channels;

    if (setup.m_channels == 0) throw std::runtime_error("Нет каналов в аудио файле");

    // Инициализация переменных для чтения
    std::size_t read = 0;            // Количество обработанных кадров
    fvec_t read_data(setup.m_channels * setup.m_step_size, .0); // Буфер для чтения (интерливированные данные)
    fvec_t in(setup.m_step_size, .0);                           // Буфер для моно-данных

    // Создание плагина и адаптера для обработки
    Plugin pl { setup };
    adapter ad { pl };

    do 
    {
      // Определение длины читаемого блока (защита от выхода за границы)
      auto length = hack::math::min(setup.m_step_size, in.size());
      auto read_samples = sf_read_float(file, read_data.data(), read_data.size());

      uint_t read_length = read_samples / setup.m_channels;  // Перевод в кадры
      read_length = hack::math::min(length, read_length);    // Ограничение длиной буфера

      // Де-интерливирование и down-mixing (преобразование многоканального в моно)
      for (std::size_t i = 0; i < read_length; ++i) 
      {
        in[i] = 0.0;
        // Суммирование всех каналов
        for (int c = 0; c < setup.m_channels; ++c) 
          in[i] += read_data[setup.m_channels * i + c];
        // Усреднение для получения моно-сигнала
        in[i] /= static_cast<base_t>(setup.m_channels);
      }

      // Подготовка к следующей итерации
      read = hack::math::min(length, static_cast<uint_t>(floorf(read_length + .5)));
      
      // Дополнение буфера нулями если считано неполный блок (конец файла)
      if (in.size() > read) std::fill(in.begin() + read, in.end(), 0.0);

      // Вычисление временной метки и обработка данных через адаптер
      real_time timestamp = real_time::frame2rt(read, sf_info.samplerate);
      ad.process(in, timestamp);
    }
    while (read == setup.m_step_size);  // Продолжать пока читаются полные блоки

    // Закрытие файла и возврат результата
    sf_close(file);
    return ad.get_result();
  }
}
