#include <Arduino.h>
  #include "Settings.h"
  #include "pumppwm.h"

    extern void DbgMsg(const String& Msg, bool ln);
  /**
  * @brief Прочитать конфигурацию из памяти.
  */
  void read_config();

  /**
  * @brief Получить значение из строки по разделителю.
  * @param data Строка
  * @param separator Разделитель
  * @param index Индекс значения
  * @return Значение (строка)
  */
  String getValue(const String& data, char separator, int index);

  /**
  * @brief Запустить сервисные задачи (например, шаговый двигатель).
  */
  void startService(void);

  /**
  * @brief Остановить сервисные задачи.
  */
  void stopService(void);

  /**
  * @brief Установить режим питания.
  * @param Mode Режим (строка)
  */
  void set_power_mode(String Mode);

  /**
  * @brief Проверить ошибки питания и обработать их.
  */
  void check_power_error();

  /**
  * @brief Установить текущую мощность.
  * @param Volt Мощность (Вольт)
  */
  void set_current_power(float Volt);

  /**
  * @brief Сохранить текущий профиль настроек.
  */
  void save_profile();

  /**
  * @brief Завершить программу варки, выключить нагрев, насос и клапаны.
  */
  void cheese_finish();

  /**
  * @brief Управлять состоянием нагревателя по ПИД-регулятору.
  * @param setpoint Целевая температура
  * @param temp Текущая температура
  */
  void set_heater_state(float setpoint, float temp);

  /**
  * @brief Установить ШИМ для нагревателя.
  * @param dutyCycle Скважность (0.0 - 1.0)
  */
  void set_heater(double dutyCycle);

  /**
  * @brief Включить или выключить нагреватель.
  * @param state true — включить, false — выключить
  */
  void setHeaterPosition(bool state);

  /**
  * @brief Перейти к строке программы с номером num, инициализировать этап.
  * @param num Номер строки программы
  */
  void run_cheese_program(uint8_t num);

  /**
  * @brief Запустить автотюнинг ПИД-регулятора.
  */
  void StartAutoTune();

  /**
  * @brief Завершить автотюнинг ПИД-регулятора и применить параметры.
  */
  void FinishAutoTune();

  /**
  * @brief Включить или выключить питание.
  * @param On true — включить, false — выключить
  */
  void set_power(bool On);

  /**
  * @brief Создать файл с данными текущей сессии.
  */
  void create_data();

  /**
  * @brief Открыть или закрыть клапан.
  * @param Val true — открыть, false — закрыть
  * @param msg true — отправить сообщение
  */
  void open_valve(bool Val, bool msg);

  /**
  * @brief Отправить сообщение пользователю.
  * @param m Текст сообщения
  * @param msg_type Тип сообщения
  */
  void SendMsg(const String& m, MESSAGE_TYPE msg_type);

  /**
  * @brief Получить строковое описание программы варки.
  * @return Строка с описанием программы
  */
  String get_cheese_program();

  /**
  * @brief Проверить и обработать состояние мешалки и насоса.
  */
  void check_mixer_state();

  /**
  * @brief Установить целевое состояние насоса через I2C.
  * @param on 1 — включить, 0 — выключить
  * @return true если успешно
  */
  bool set_mixer_pump_target(uint8_t on);

  /**
  * @brief Управлять шаговым двигателем по времени.
  * @param spd Скорость
  * @param direction Направление
  * @param time Время работы (мс)
  * @return true если успешно
  */
  bool set_stepper_by_time(uint16_t spd, uint8_t direction, uint16_t time);

  /**
  * @brief Совершить шаг шаговым двигателем для засыпи хмеля.
  */
  void HopStepperStep(uint16_t speed, uint16_t steps);

  /**
  * @brief Включить или выключить буззер.
  * @param fl true — включить, false — выключить
  */
  void set_buzzer(bool fl);

  /**
  * @brief Установить ШИМ для насоса.
  * @param duty Значение ШИМ
  */
  void set_pump_pwm(float duty);

  /**
  * @brief Сбросить счетчик датчиков и состояния процесса.
  */
  void reset_sensor_counter(void);
  void stop_process(String reason);
  bool set_stepper2_by_time(uint16_t spd, uint8_t direction, uint16_t time);
  bool isBoilingStarted(float currentTemp);
  void ReadAcid();

  #define TEMP_HISTORY_SIZE 10  // Размер буфера истории температур (точек)
  #define BOILING_DETECT_THRESHOLD 0.08  // Порог по стандартному отклонению, °C
  #define MIN_BOILING_TEMP 98.0  // Минимальная температура кипения (с учетом погрешности)
  #define STABLE_WINDOWS_REQUIRED 5 // Кол-во стабильных окон подряд для фиксации кипения
  #define MAX_TREND_ABS_PER_SEC 0.02 // Макс. модуль тренда в °C/с для стабильности

//
float getCheeseCurrentTemp() {// Получить температуру с выбранного датчика для режима пива
  if (SamovarStatusInt != 5000) return 0.0;
  
  switch (program[ProgramNum].TempSensor) {
    case 0:
      return TankSensor.avgTemp;
    case 1:
      return WaterSensor.avgTemp;
    case 2:
      return PipeSensor.avgTemp;
    case 3:
      return SteamSensor.avgTemp;
    case 4:
      return ACPSensor.avgTemp;
    default:
      return TankSensor.avgTemp;
  }
}

/**
 * @brief Основной цикл запуска процесса варки. Инициализация и старт программы.
 */
void cheese_proc() {
  if (SamovarStatusInt != 5000) return;

  if (startval == 5000 && !PowerOn) {
    // Сброс детектора кипения при запуске процесса
    resetBoilingDetector();
  if (SamSetup.UseMQTT) {
      SessionDescription.replace(",", ";");
      MqttSendMsg(String(chipId) + "," + SamSetup.TimeZone + "," + SAMOVAR_VERSION + "," + get_cheese_program() + "," + SessionDescription, "st");
  }
    create_data();  //создаем файл с данными
    PowerOn = true;
    set_power(true);
    run_cheese_program(0);
  }
  vTaskDelay(10 / portTICK_PERIOD_MS);
}

/**
 * @brief Переход к этапу программы с номером num, обработка сообщений и сброс переменных этапа.
 * @param num Номер этапа программы
 */
void run_cheese_program(uint8_t num) {
  if (Samovar_Mode != SAMOVAR_CHEESE_MODE || !PowerOn) return;
  if (startval == 5000) startval = 5001;
  ProgramNum = num;
  begintime = 0;
  msgfl = true;

  if (program[ProgramNum].WType == "A") {
    StartAutoTune();
  }

  if (ProgramNum > ProgramLen - 1) {
    num = MAX_PRG;
    SetScriptOff = 1;
  }

  //Если предыдущая программа была программой Lua и скрипт запущен - останавливаем скрипт
  if (ProgramNum > 0 && program[ProgramNum-1].WType == "L" && loop_lua_fl) {
    SetScriptOff = 1;
  }
  //Если предыдущая строка была слив после посола - закрываем клапан слива 
  if (ProgramNum > 0 && program[ProgramNum-1].WType == "S") {
      digitalWrite(RELE_CHANNEL4, !SamSetup.rele4); //Прекращаем слив
      SendMsg("Закрыт клапан слива.", NOTIFY_MSG);
  }

  String msg = "Переход к строке программы №" + String((num + 1));
  if (num == MAX_PRG) {
    //если num = MAX_PRG значит мы достигли финала (или процесс сброшен принудительно), завершаем работу
    cheese_finish();
    return;
  } else {
    if (program[num].WType == "M") {
      msg += "; Нагрев до температуры: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "P") {
      msg += "; Температурная пауза: " + String(program[num].Temp) + "°, время: " + String(program[num].Time) + " мин";
    } else if (program[num].WType == "C") {
      msg += "; Охлаждение до температуры: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "W") {
      msg += "; Режим ожидания";
    } else if (program[num].WType == "Z") {
      msg += "; Внесение защитной культуры.";
    } else if (program[num].WType == "f") {
      msg += "; Внесение фермента.";
    } else if (program[num].WType == "z") {
      msg += "; Внесение закваски.";
    } else if (program[num].WType == "d") {
      msg += "; Внесение дополнительного инградиента.";
    } else if (program[num].WType == "s") {
      msg += "; Стуфактура. ";
    } else if (program[num].WType == "p") {
      msg += "; Пастеризация при температуре: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "v") {
      msg += "; Вымешивание при температуре: " + String(program[num].Temp) + "°";
    } else if (program[num].WType == "r") {
      msg += "; Резка Калье.";
    } else if (program[num].WType == "n") {
      msg += "; Набор кислотности.";
    } else if (program[num].WType == "S") {
      msg += "; Слив после посола.";
    } else if (program[num].WType == "R") {
      msg += "; Переворот сыра.";
    } 
  }

  if (SamSetup.ChangeProgramBuzzer) {
    set_buzzer(true);
    SendMsg(msg, ALARM_MSG);
  } else {
    SendMsg(msg, NOTIFY_MSG);
  }

  //сбрасываем переменные для мешалки и насоса
  alarm_c_low_min = 0;  //мешалка вкл
  alarm_c_min = 0;  //мешалка пауза
  currentstepcnt = 0; //счетчик циклов мешалки
}

/**
 * @brief Завершает процесс варки: выключает насос, нагрев, клапаны, сбрасывает состояния.
 */
void cheese_finish() {
  if (valve_status) {
    open_valve(false, true);
  }
  // Сброс детектора кипения при завершении процесса
  resetBoilingDetector();
  set_mixer_state(OFF);
  if (SamSetup.UseWP) {
  set_pump_pwm(0);
  pump_started = false;
  }
  setHeaterPosition(false);
  PowerOn = false;
  heater_state = false;
  startval = 0;
  stop_process("Программа сыроварения завершена");
}

/**
 * @brief Проверяет и управляет состоянием процесса варки, включая нагрев, охлаждение, паузы и кипячение.
 */
void check_alarm_cheese() {

  if (startval <= 5000) return;

  // Если процесс запущен, обеспечиваем корректное состояние флага PowerOn
  if (!PowerOn && startval > 5000) {
    PowerOn = true;
  }

  float temp = 0;
  switch (program[ProgramNum].TempSensor) {
    case 0:
      temp = TankSensor.avgTemp;
      break;
    case 1:
      temp = WaterSensor.avgTemp;
      break;
    case 2:
      temp = PipeSensor.avgTemp;
      break;
    case 3:
      temp = SteamSensor.avgTemp;
      break;
    case 4:
      temp = ACPSensor.avgTemp;
      break;
  }

  //Обрабатываем программу

  //Проверяем, что клапан воды охлаждения не открыт, когда не нужно
  if (program[ProgramNum].WType != "C" && valve_status && PowerOn && program[ProgramNum].WType != "L"
   && program[ProgramNum].WType != "Z" && program[ProgramNum].WType != "f" && program[ProgramNum].WType != "z" && program[ProgramNum].WType != "d"
    && program[ProgramNum].WType != "s" && program[ProgramNum].WType != "p" && program[ProgramNum].WType != "v" && program[ProgramNum].WType != "r"
     && program[ProgramNum].WType != "n" && program[ProgramNum].WType != "S" && program[ProgramNum].WType != "R") {
    //Закрываем клапан воды
    open_valve(false, false);
  }

  //Если программа - Lua - ждем, ничего не делаем
  if (program[ProgramNum].WType == "L") {
    return;
  }

  //Если программа - ожидание - ждем, ничего не делаем
  if (program[ProgramNum].WType == "W") {
    if (begintime == 0) {
      begintime = millis();
      setHeaterPosition(false);
      open_valve(false, false);
    }
    check_mixer_state(); // Управление мешалкой и насосом по параметрам программы
    return;
  }
  //Если программа - переворот сыра - только ворочаем сыр
  if (program[ProgramNum].WType == "R") {
    if (begintime == 0) {
      begintime = millis();
      setHeaterPosition(false);
    }
    check_mixer_state(); // Управление мешалкой и насосом по параметрам программы
    if (((millis() - begintime) / 1000 / 60 >= program[ProgramNum].Time)) run_cheese_program(ProgramNum + 1); //Запускаем следующую программу
    return;
  }

  //Если режим Автотюнинг
  if (program[ProgramNum].WType == "A") {
    if (tuning) {
      set_heater_state(program[ProgramNum].Temp, temp);
    } else {
      cheese_finish();
    }
  }

  //Если режим Прогрев или Пауза
  if (program[ProgramNum].WType == "M" || program[ProgramNum].WType == "P" || program[ProgramNum].WType == "Z" || program[ProgramNum].WType == "f" || program[ProgramNum].WType == "z"
   || program[ProgramNum].WType == "d" || program[ProgramNum].WType == "s" || program[ProgramNum].WType == "p" || program[ProgramNum].WType == "v" || program[ProgramNum].WType == "r"
    || program[ProgramNum].WType == "n") {
    set_heater_state(program[ProgramNum].Temp, temp);
  }

  if (program[ProgramNum].WType == "M" && temp >= program[ProgramNum].Temp - TankSensor.SetTemp) {
    //Достигли заданной температуры. Пишем об этом. Продолжаем поддерживать температуру. Переход с этой строки программы на следующую автоматический
    if (startval == 5001) {
      set_buzzer(true);
      SendMsg(("Достигнута заданная температура!"), NOTIFY_MSG);
    }
    startval = 5002;
    run_cheese_program(ProgramNum + 1);//Запускаем следующую программу
  }

  if ((program[ProgramNum].WType == "P" || program[ProgramNum].WType == "Z" || program[ProgramNum].WType == "f" || program[ProgramNum].WType == "z"
   || program[ProgramNum].WType == "d" || program[ProgramNum].WType == "s" || program[ProgramNum].WType == "p" || program[ProgramNum].WType == "v" || program[ProgramNum].WType == "r"
    || program[ProgramNum].WType == "n") && temp >= program[ProgramNum].Temp - TankSensor.SetTemp) {
    if (begintime == 0) {
      //Засекаем время для отсчета, сколько держать паузу
      begintime = millis();
      if (program[ProgramNum].WType == "Z" || program[ProgramNum].WType == "f" || program[ProgramNum].WType == "z" || program[ProgramNum].WType == "d") {
        HopStepperStep(SamSetup.D_Speed, SamSetup.D_Steps);
        SendMsg("Внесено. Достигнута температурная пауза " + String(program[ProgramNum].Temp) + "°. Ждем " + String(program[ProgramNum].Time) + " минут.", NOTIFY_MSG);
      } else SendMsg("Достигнута температурная пауза " + String(program[ProgramNum].Temp) + "°. Ждем " + String(program[ProgramNum].Time) + " минут.", NOTIFY_MSG);
    }
  }

  //Если программа - охлаждение - ждем, когда температура в кубе упадет ниже заданной, и управляем водой для охлаждения
  if (program[ProgramNum].WType == "C") {
    if (begintime == 0) {
      begintime = millis();
      setHeaterPosition(false);
      //Открываем клапан воды
      open_valve(true, false);
      if (SamSetup.UseWP) {
          set_pump_pwm(1023);
          pump_started = true;
      }
    }
    if (temp <= program[ProgramNum].Temp) {
      //Если температура упала
      //Закрываем клапан воды
      open_valve(false, false);
      if (SamSetup.UseWP) {
          set_pump_pwm(0);
          pump_started = false;
      }
      //запускаем следующую программу
      run_cheese_program(ProgramNum + 1);
    }
  }

  //Проверяем, что еще нужно держать паузу
  if (begintime > 0 && (program[ProgramNum].WType == "P" || program[ProgramNum].WType == "Z" || program[ProgramNum].WType == "f" || program[ProgramNum].WType == "z"
   || program[ProgramNum].WType == "d" || program[ProgramNum].WType == "s" || program[ProgramNum].WType == "p" || program[ProgramNum].WType == "v" || program[ProgramNum].WType == "r"
    || program[ProgramNum].WType == "n") && ((millis() - begintime) / 1000 / 60 >= program[ProgramNum].Time)) {
    //Запускаем следующую программу
    run_cheese_program(ProgramNum + 1);
  }

    //Слив рассола
  if (program[ProgramNum].WType == "S")   {
    if (begintime == 0) {// В начале слива открываем клапан и запоминаем время
      setHeaterPosition(false);//отключаем нагрев
      begintime = millis();
      digitalWrite(RELE_CHANNEL4, SamSetup.rele4);
      SendMsg("Открыт клапан слива.", NOTIFY_MSG);
    }
    if (begintime > 0 && ((millis() - begintime) / 1000 / 60 >= program[ProgramNum].Time)) { //Время вышло
      run_cheese_program(ProgramNum + 1); //Запускаем следующую программу, там закроется клапан
    }    
  }


  //Если достигли кислотности при Наборе кислотности
  if (SamSetup.Ch_Use_Acid_Sensor && program[ProgramNum].WType == "n") {    
    if (MAcid<=SamSetup.Acid) {
      SendMsg("Кислотность " + format_float(SamSetup.Acid, 2) +" достигнута, переход к след. строке.", NOTIFY_MSG);
      run_cheese_program(ProgramNum + 1);
    }
  }


  //Обрабатываем мешалку и насос
  check_mixer_state();

  vTaskDelay(10 / portTICK_PERIOD_MS);
}

/**
 * @brief Возвращает строковое описание текущей программы варки.
 * @return Строка с описанием программы
 */
String get_cheese_program() {
  String Str = "";
  Str.reserve(1024);
  for (uint8_t i = 0; i < MAX_PRG; i++) {
    if (program[i].WType.length() == 0) {
      return Str;
    } else {
      Str += program[i].WType + ";";
      Str += (String)program[i].Temp + ";";
      Str += (String)(int)program[i].Time + ";";
      Str += (String)program[i].capacity_num + "^" + program[i].Speed + "^" + program[i].Volume + "^" + program[i].Power + ";";
      Str += (String)program[i].TempSensor + "\n";
    }
  }
  return Str;
}

/**
 * @brief Устанавливает программу варки из строки.
 * @param WProgram Строка с описанием программы
 */
void set_cheese_program(String WProgram) {
  //M - malt application temp, P - pause, B - boil, C - cool
  char c[1024] = {0};
  WProgram.toCharArray(c, 1024);
  char *pair = strtok(c, ";");
  //String MeshTemplate;
  int i = 0;
  while (pair != NULL && i < MAX_PRG) {
    program[i].WType = pair;
    pair = strtok(NULL, ";");
    program[i].Temp = atof(pair);
    pair = strtok(NULL, ";");
    program[i].Time = atof(pair);
    pair = strtok(NULL, ";");
    //разберем шаблон для насоса/мешалки по частям
    program[i].capacity_num = getValue(pair, '^', 0).toInt();  //Тип устройства - 1 - мешалка, 2 - насос, 3 - мешалка и насос одновременно
    program[i].Speed = getValue(pair, '^', 1).toInt();         //Направление вращения, если задано отрицательное значение - мешалка после паузы меняет направление вращения
    program[i].Volume = getValue(pair, '^', 2).toInt();        //Время включения в секундах
    program[i].Power = getValue(pair, '^', 3).toInt();         //Время выключения в секундах
    pair = strtok(NULL, "\n");
    program[i].TempSensor = atoi(pair);

    i++;
    ProgramLen = i;
    pair = strtok(NULL, ";");
    if ((!pair || pair == NULL || pair[0] == 13) && i < MAX_PRG) {
      program[i].WType = "";
      break;
    }
  }
}

