#include <QTimer>
#include <QFile>
#include <sstream>
#include <cassert>

#include "heatControl.h"

HeatControl * HeatControl::C[SHNodeIdSize * SHSensorIdSize] = {nullptr};
QFile HeatControl::m_file(CSVfileName);
//    QDir::setCurrent("/tmp");


/*
 *
 */
HeatControl::HeatControl(SHsensor *temp, SHactuator *swtch, float maxTempDay, float maxTempNight, QTime startTimeDay, QTime startTimeNight, QObject *parent) :
    QObject(parent),
    m_temp(temp), m_switch(swtch),
    m_maxTempDay(maxTempDay), m_maxTempNight(maxTempNight),
    m_startTimeDay(startTimeDay), m_startTimeNight(startTimeNight)
{
    m_mode = Mode::Auto; // default is Auto

    // Hinzufügen:
    m_pos = m_switch->pos();

    // qDebug() << m_name << pos;

    if (C[m_pos] != nullptr)
        throw std::invalid_argument("HeatControl::HeatControl: double usage");

    C[m_pos] = this;

    m_suspended = Suspension::NONE;
}



/*
 *
 */
HeatControl::~HeatControl()
{
    C[m_pos] = nullptr;
}



/*
 *
 */
HeatControl::Mode HeatControl::mode() const
{
    return m_mode;
}




/*
 *
 */
QString HeatControl::value() const
{
    return modeName(m_mode);
}




/*
 *
 */
SHactuator::State HeatControl::state() const
{
    return m_switch->switchState();

}



/*
 *
 */
int HeatControl::demand() const
{
    return m_switch->powerDemand();
}


/*
 *
 */
float HeatControl::temperature() const
{
    if (m_temp == nullptr)
        return -9.0; // kleiner Fantasie-Wert ohne jede Bedeutung, d.h. Gerät soll einfach an bleiben

    return m_temp->value().toFloat();
}



/*
 *
 */
float HeatControl::minTemp() const
{
    QDateTime now = SHsensor::currentTime();

    if (m_startTimeDay <= now.time() and now.time() < m_startTimeNight) // Tag
        return m_maxTempDay - autoTempMargin;
    else
        return m_maxTempNight;

}


/*
 *
 */
QString HeatControl::name() const
{
    return m_switch->name();
}


/*
 *
 */
void HeatControl::grant(bool ON)
{
    m_granted = ON;

    if (ON)
        m_switch->ON();
    else
        m_switch->OFF();
}




/*
 *
 */
void HeatControl::suspend(bool maxTemp)
{
    Suspension suspendedOld = m_suspended;

    if (maxTemp)
        m_suspended = Suspension::MaxTemp;
    else
        m_suspended = Suspension::MinTemp;

    // Zeit wird nur neu gesetzt, wenn sich der Zustand ändert:
    if (suspendedOld != m_suspended) {
        QDateTime now = SHsensor::currentTime();
        QDateTime newExpireTime = now.addSecs(60*suspensionTime());

        m_expireTime = newExpireTime;
    }


}





/*
 *
 */
void HeatControl::checkSusExpiration()
{
    QDateTime now = SHsensor::currentTime();

//    qDebug() << "HeatControl::checkSusExpiration before =>" << (int) m_suspended;
//    qDebug() << "check" << name() << m_expireTime << now;

//    if (m_expireTime.hour() == 0 and now.hour() == 23)
//        return; // wait until after midnight

    if (m_expireTime <= now)
        m_suspended = Suspension::NONE;

//    qDebug() << "HeatControl::checkSusExpiration after =>" << (int) m_suspended;
}




/*
 * check reset im Zustand MinTemp (weil die Rahmenbedingungen sich geändert haben)
 */
void HeatControl::checkResetSus(bool supplyPossible)
{
    if (m_suspended == Suspension::MinTemp) {
        // mehr Solar verfügbar:
        bool couldBeMoore = (supplyPossible and not maxReached());

        QTime expireSetTime = SHsensor::currentTime().time().addSecs(-60*suspensionTime()); // Zeitpunkt der Suspendierung

        // Tagesanbruch mit neuer minimal Temperatur:
        bool newDayTemp = (expireSetTime < m_startTimeDay and m_startTimeDay < m_expireTime.time() and not minReached());

        if (couldBeMoore or newDayTemp)
            m_suspended = Suspension::NONE;
    }
}




/*
 *
 */
bool HeatControl::maxReached() const
{
    return (temperature() >= m_maxTempDay);
}




/*
 *
 */
bool HeatControl::minReached() const
{
    return (not m_temp->isError() and temperature() >= minTemp());
}


/*
 *
 */
int HeatControl::suspensionTime() const
{
    return 15; // min
}





/*
 *
 */
bool HeatControl::grantPossible(bool supplyPossible) const
{
    bool smartMode = (mode() == HeatControl::Mode::Smart);

    return (not suspended() and ((smartMode and not minReached()) or (supplyPossible and not maxReached())));

}




/*
 *
 */
void HeatControl::checkMaxTempSus(bool supplyPossible)
{
    if (maxReached() and supplyPossible)
        suspend(true); // auf MaxTemp Suspension setzen
}




/*
 *
 */
void HeatControl::checkMinTempSus(bool supplyPossible)
{
    bool smartMode = (mode() == HeatControl::Mode::Smart);

    if (not suspended() and smartMode and minReached() and not supplyPossible)
        suspend(false);  // auf MinTemp Suspension setzen
}



/*
 *
 */
QString HeatControl::output() const
{
    QString s_output;

    s_output += " " + name() + "(" + QString::number(demand()) + ",";


    s_output += QString::number(temperature()) + "/";

    if (mode() == HeatControl::Mode::Smart)
        s_output += QString::number(minTemp());
    else if (mode() == HeatControl::Mode::Auto)
        s_output += QString::number(m_maxTempDay);
    else
        s_output += "-";

    s_output += "):";

    return s_output;
}


/*
 *
 */
bool HeatControl::granted() const
{
    return m_granted;
}



/*
 *
 */
bool HeatControl::suspended() const
{
    return (m_suspended != Suspension::NONE);
}




/*
 *
 */
QDateTime HeatControl::expireTime() const
{
    return m_expireTime;
}


/*
 *
 */
QString HeatControl::susName() const
{
    switch (m_suspended) {
    case Suspension::NONE:
        return "OFF"; // no suspension -> else-part in SolarControl::process()
    case Suspension::MaxTemp:
        return "MXT";
    case Suspension::MinTemp:
        return "MIT";
    }

    return "ERR";
}



/*
 *
 */
void HeatControl::remoteOn(int pos /* , bool setOn */)
{
//    if (C[pos]->m_switch->nodeID() == NodeIdSolarheat )
//        qDebug() << "remoteMsg" << C[pos]->m_switch->nodeID() <<  C[pos]->m_switch->sensorID() ;


    C[pos]->m_previousMode = C[pos]->m_mode;
    C[pos]->m_previousState = C[pos]->m_switch->switchState();
    C[pos]->m_mode = Mode::RemoteOn;

    C[pos]->m_switch->ON(); // d.h. nicht auf process() warten
}




/*
 *
 */
void HeatControl::resumeMode(int pos)
{
    C[pos]->m_mode = C[pos]->m_previousMode;

    C[pos]->m_switch->set(C[pos]->m_previousState); // d.h. nicht auf process() warten
}



/*
 *
 */
void HeatControl::processTimed()
{
    QDateTime now = SHsensor::currentTime();

    // qDebug() << "HeatControl::processTimed" << temperature() << m_maxTempNight << m_maxTempDay;

    if (m_startTimeDay <= now.time() and now.time() < m_startTimeNight) { // Tag
        if (m_suspended == Suspension::NONE and temperature() < m_maxTempDay)
            m_switch->ON();
        else {
            m_switch->OFF();

            if (temperature() >= m_maxTempDay)
                suspend(true);
        }
    } else { // Nacht
        if (m_suspended == Suspension::NONE and temperature() < m_maxTempNight)
            m_switch->ON();
        else {
            m_switch->OFF();

            if (temperature() >= m_maxTempNight)
                suspend(true);
        }
    }

    if (suspended())
        checkSusExpiration();

    // Debug:
/*    if (m_switch->switchState() == SHactuator::State::ON)
        qDebug() << "ON";
    else if (m_switch->switchState() == SHactuator::State::UNKOWN)
        qDebug() << "err";
    else if (suspended())
        qDebug() << "SUS";
    else
        qDebug() << "OFF"; */
}






/*
 *
 */
void HeatControl::process()
{
    // qDebug() << "HeatControl::process" << m_switch->name();

    switch (m_mode) {
    case Mode::Off:
        m_switch->OFF();
        break;
    case Mode::Timed:
        processTimed();
        break;
    case Mode::On:
        m_switch->ON();
        break;
    case Mode::Auto: // nichts zu tun -> wird von SolarControl gehandhabt
    case Mode::Smart:
    case Mode::RemoteOn: // nur der Vollständigkeit halber
    case Mode::size:
        break;
    }

}





/*
 * static
 */
void HeatControl::processALL()
{
    for (auto c : C)
        if (c != nullptr)
            c->process();
}

static QString getToken(std::istringstream & ss, char delim = sep) {
    std::string token;

    std::getline(ss, token, delim);

    // qDebug() << "token <" << token.c_str() << ">";

    return token.c_str();
}

/*
 *
 */
void HeatControl::readCSV()
{
    QDir dir;

    if (m_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug() << "open" << dir.absolutePath() << CSVfileName;

        while (not m_file.atEnd()) {
            QString line = m_file.readLine();

            qDebug() << line;

            std::string input = line.toStdString();
            std::istringstream ss(input);

            QString name = getToken(ss);

            int nid = getToken(ss).toInt();
            int sid = getToken(ss).toInt();

            int pos = SHsensor::hash(nid, sid);

//           int pos = getToken(ss).toInt(); -- Alte Version

//            if (pos < 0 or pos > SHNodeIdSize * SHSensorIdSize) {
//                qDebug() << "pos out of range" << pos;
//                continue;
//            }

            if (C[pos] == nullptr) {
                qDebug() << "C[pos] == nullptr" << pos;
                continue;
            }

            if (C[pos]->m_switch->name() != name) {
                qDebug() << "C[pos]->m_switch->name() != name" << pos;
                continue;
            }

            C[pos]->m_mode = HeatControl::toMode(getToken(ss));
            C[pos]->m_maxTempDay = getToken(ss).toFloat();
            C[pos]->m_maxTempNight = getToken(ss).toFloat();
            C[pos]->m_startTimeDay = QTime::fromString(getToken(ss));
            C[pos]->m_startTimeNight = QTime::fromString(getToken(ss, '\n'));

            qDebug() << name << nid << sid << modeName(C[pos]->m_mode)
                     << C[pos]->m_maxTempDay << C[pos]->m_maxTempNight
                     << C[pos]->m_startTimeDay << C[pos]->m_startTimeNight;
        }


        m_file.close();
    } else { // erstmalig
        qDebug() << "writing defaults" << dir.absolutePath() << CSVfileName;

        writeCSV();
    }
}

/*
 *
 */
void HeatControl::writeCSV()
{

    if (not m_file.open(QIODevice::WriteOnly | QIODevice::Text))
        return;

    QTextStream out(&m_file);


    for (auto c : C)
        if (c != nullptr) {
            out << c->m_switch->name() << sep;
            out << c->m_switch->nodeID() << sep;
            out << c->m_switch->sensorID() << sep;
//            out << c->m_pos << sep; - alte Version
            out << modeName(c->mode()) << sep;
            out << c->m_maxTempDay << sep;
            out << c->m_maxTempNight << sep;
            out << c->m_startTimeDay.toString("hh:mm") << sep;
            out << c->m_startTimeNight.toString("hh:mm") << "\n";
        }

    m_file.close();
}



/*
 *
 */
void HeatControl::setMode(int pos, const Mode &mode)
{
    assert(pos >= 0 and pos < SHNodeIdSize * SHSensorIdSize);

    C[pos]->m_mode = mode;
    C[pos]->m_switch->setIgnoreF3error();

    writeCSV();
}



/*
 *
 */
HeatControl::Mode HeatControl::getMode(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return Mode::size;
    else
        return C[pos]->m_mode;
}



/*
 *
 */
float HeatControl::getMaxTempDay(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return -42;
    else
        return C[pos]->m_maxTempDay;
}


/*
 *
 */
float HeatControl::getMaxTempNight(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return -42;
    else
        return C[pos]->m_maxTempNight;
}


/*
 *
 */
QTime HeatControl::getStartTimeDay(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return QTime(11,11);
    else
        return C[pos]->m_startTimeDay;
}


/*
 *
 */
QTime HeatControl::getStartTimeNight(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return QTime(11,11);
    else
        return C[pos]->m_startTimeNight;
}


/*
 *
 */
QString HeatControl::getName(int nid, int sid)
{
    int pos = SHsensor::hash(nid, sid);

    if (pos == -1 or C[pos] == nullptr)
        return "ERRname";
    else
        return C[pos]->name();
}

/*
 *
 */
void HeatControl::setStartTimeNight(int pos, const QTime &startTimeNight)
{
    assert(pos >= 0 and pos < SHNodeIdSize * SHSensorIdSize);

    C[pos]->m_startTimeNight = startTimeNight;

    writeCSV();
}


/*
 *
 */
void HeatControl::setStartTimeDay(int pos, const QTime &startTimeDay)
{
    assert(pos >= 0 and pos < SHNodeIdSize * SHSensorIdSize);

    C[pos]->m_startTimeDay = startTimeDay;

    writeCSV();
}


/*
 *
 */
void HeatControl::setMaxTempNight(int pos, float maxTempNight)
{
    assert(pos >= 0 and pos < SHNodeIdSize * SHSensorIdSize);

    C[pos]->m_maxTempNight = maxTempNight;

    writeCSV();
}


/*
 *
 */
void HeatControl::setMaxTempDay(int pos, float maxTempDay)
{
    assert(pos >= 0 and pos < SHNodeIdSize * SHSensorIdSize);

    C[pos]->m_maxTempDay = maxTempDay;

    writeCSV();
}




static QVector<QString> N = {"Off", "Auto", "Smart", "Timed", "On", "Manuell", "ERRnoMode"};

/*
 *
 */
QString HeatControl::modeName(HeatControl::Mode m)
{
    return N[(int) m];
}


/*
 *
 */
HeatControl::Mode HeatControl::toMode(const QString &m)
{
    for (int i=0; i<N.size(); i++)
        if (N[i] == m)
            return (Mode) i;

    return HeatControl::Mode::Off; // default
}