
// NTP headers
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

#include "AutoStartStop.h"
#include "Logger.h"

template <typename SessionT>
class SessionManager
{
public:
    using MeasurementType = typename SessionT::MeasurementType;

    SessionManager();

    void begin(int scaleDoutPin, int scaleSckPin, uint8_t tareAvgCount, int valueRightShift, MeasurementType autoStartMinTh, MeasurementType autoStartMaxTh, uint32_t autoStartMeasuresBetweenPeaks,
               MeasurementType autoStopTh, uint32_t autoStopNumMeasures);

    void tare();
    long tareOffset() const { return scale_.offset(); };
    int valueRightShift() const { return scale_.valueRightShift(); }

    void startMeasurements();
    void stopMeasurements();
    bool isMeasuring() const { return measuring_; }
    SessionT &session() { return session_; }

    void iteration();

    void enableAutoStart(bool enable) { autoStart_.enable(enable); }
    void enableAutoStop(bool enable) { autoStop_.enable(enable); }
    bool autoStartEnabled() const { return autoStart_.enabled(); };
    bool autoStopEnabled() const { return autoStop_.enabled(); }
    
    unsigned long getNtpTime() { return timeClient_.getEpochTime(); } 
private:
    void onMeasurementTaken(MeasurementType measurement);

    WiFiUDP ntpUDP_;
    NTPClient timeClient_;

    Scale scale_;
    //MockScale scale;
    SessionT session_;
    bool measuring_;
    long lastCallTime_;

    int scaleDoutPin_;
    int scaleSckPin_;
    uint8_t tareAvgCount_;
    int valueRightShift_;

    AutoStart<MeasurementT> autoStart_;
    AutoStop<MeasurementT> autoStop_;
};

// ------------------------------------------------------------------------------------------------

template <typename SessionT>
SessionManager<SessionT>::SessionManager()
    : timeClient_(ntpUDP_, "pool.ntp.org"),
      measuring_(false),
      lastCallTime_(0)
{
}

template <typename SessionT>
void SessionManager<SessionT>::tare()
{
    if (measuring_)
        stopMeasurements();
    LOG_INFO("Beginning tare");
    scale_.begin(scaleDoutPin_, scaleSckPin_, valueRightShift_);
    scale_.tare(CONFIG_TARE_AVG_COUNT);
    LOG_INFO("Finished tare");
}

template <typename SessionT>
void SessionManager<SessionT>::begin(int scaleDoutPin, int scaleSckPin, uint8_t tareAvgCount, int valueRightShift,
                                     MeasurementType autoStartMinTh, MeasurementType autoStartMaxTh, uint32_t autoStartMeasuresBetweenPeaks,
                                     MeasurementType autoStopTh, uint32_t autoStopNumMeasures)
{
    if(measuring_)
        stopMeasurements();
    
    scaleDoutPin_ = scaleDoutPin;
    scaleSckPin_ = scaleSckPin;
    tareAvgCount_ = tareAvgCount;
    valueRightShift_ = valueRightShift;
    
    timeClient_.begin();
    timeClient_.update();
    session_.init(timeClient_.getEpochTime());

    autoStart_.begin(autoStartMinTh, autoStartMaxTh, autoStartMeasuresBetweenPeaks);
    autoStop_.begin(autoStopTh, autoStopNumMeasures);
}

template <typename SessionT>
void SessionManager<SessionT>::startMeasurements()
{
    if (measuring_ == true)
        return;
    measuring_ = true;
    lastCallTime_ = 0;
    session_.init(timeClient_.getEpochTime());
}

template <typename SessionT>
void SessionManager<SessionT>::stopMeasurements()
{
    if (measuring_ == false)
        return;
    session_.finalize();
    measuring_ = false;
}

template <typename SessionT>
void SessionManager<SessionT>::iteration()
{
    MeasurementType measurement = -1;
    bool measurementDone = false;
    while (!measurementDone)
        measurementDone = scale_.measure(measurement);

    onMeasurementTaken(measurement);

    if (lastCallTime_ != 0)
    {
        const long cycleDuration = millis() - lastCallTime_;
        if (cycleDuration <= CONFIG_MEASURE_DELAY)
        {
            delay(CONFIG_MEASURE_DELAY - cycleDuration);
        }
        else
        {
            const long skipped = (cycleDuration / CONFIG_MEASURE_DELAY);
            LOG_WARNING("Measurements skipped: %ld, cycleDuration %ld", skipped, cycleDuration);

            for (int i = 0; i < skipped; ++i)
                onMeasurementTaken(measurement);

            delay(CONFIG_MEASURE_DELAY * (skipped + 1) - cycleDuration);
        }
    }
    lastCallTime_ = millis();
}

template <typename SessionT>
void SessionManager<SessionT>::onMeasurementTaken(MeasurementType measurement)
{
    if (measuring_)
    {
        bool autoStop = autoStop_.autoStop(measurement);
        if (autoStop)
        {
            LOG_INFO("Auto stop");
            stopMeasurements();
            return;
        }

        bool addPointSuccessful = session_.addPoint(measurement);
        if (!addPointSuccessful)
        {
            LOG_INFO("Maximum time of session reached - stopping");
            stopMeasurements();
            return;
        }
    }
    else
    {
        if (autoStart_.autoStart(measurement))
        {
            LOG_INFO("Auto start");
            startMeasurements();
            return;
        }
    }
}
