#ifdef FIRMWARE_VARIANT_HEATCONTROL

void heatcontrol_switchPump(bool _state) {

    if (heatcontrol_out_pump && !_state) {
        // pump is running and is now switched off
        // -> set heatcontrol_pump_lastRunMillis
        heatcontrol_pump_lastRunMillis = millis();
        heatcontrol_pump_inForceRunMode = false;
    }

    heatcontrol_out_pump = _state;
    pcf8574.digitalWrite(P6, !_state); // output active low!

    char _statName[5];
    if (_state) sprintf(_statName, PGMStr_ON);
    else sprintf(_statName, PGMStr_OFF);

    sprintf_P(logBuf, PGMStr_heatc_pumpSwitchedTo, PGMStr_heatc_logName, _statName);
    sendLog(logBuf, LOGLEVEL_INFO);

    heatcontrol_publish_out_pump();
}



void heatcontrol_switchHeating(bool _state) {
    heatcontrol_out_heat = _state;
    pcf8574.digitalWrite(P7, !_state); // output active low!

    char _statName[5];
    if (_state) sprintf(_statName, PGMStr_ON);
    else sprintf(_statName, PGMStr_OFF);

    sprintf_P(logBuf, PGMStr_heatc_heatingSwitchedTo, PGMStr_heatc_logName, _statName);
    sendLog(logBuf, LOGLEVEL_INFO);

    heatcontrol_publish_out_heat();
}




// Pump Backlash

void heatcontrol_pumpBacklash_begin() {
    heatcontrol_pumpBacklash_startMillis = millis();
    heatcontrol_state_pumpBacklash_active = true;

    sprintf_P(logBuf, PGMStr_heatc_pumpBacklashStarted, PGMStr_heatc_logName);
    sendLog(logBuf, LOGLEVEL_INFO);

    heatcontrol_publish_pumpBacklash();
}

void heatcontrol_pumpBacklash_cancel() {
    heatcontrol_pumpBacklash_startMillis = 0;
    heatcontrol_state_pumpBacklash_active = false;

    heatcontrol_publish_pumpBacklash();

    sprintf_P(logBuf, PGMStr_heatc_pumpBacklashCanceled, PGMStr_heatc_logName);
    sendLog(logBuf, LOGLEVEL_INFO);
}

void heatcontrol_pumpBacklash_handleTimeout() {
    // switch off pump after backlash time has exceeded
    if (heatcontrol_pumpBacklash_startMillis > 0) {
        unsigned long _pumpBacklashMillis;
        
        if (heatcontrol_pump_inForceRunMode) {
            _pumpBacklashMillis = confHeatc.pumpTime_forceRun * 60000;
        }
        else {
            _pumpBacklashMillis = confHeatc.pumpBacklash * 60000;
        }

        if ((millis() - heatcontrol_pumpBacklash_startMillis) > _pumpBacklashMillis) {

            sprintf_P(logBuf, PGMStr_heatc_pumpBacklashFinished, PGMStr_heatc_logName);
            sendLog(logBuf, LOGLEVEL_INFO);

            heatcontrol_switchPump(false); // pump OFF
        
            heatcontrol_pumpBacklash_startMillis = 0;
            heatcontrol_state_pumpBacklash_active = false;

            heatcontrol_publish_pumpBacklash();

            display_updateImmediately = true;
        }
    }
}

uint8_t heatcontrol_pumpBacklash_getRemainingMins() {
    if(heatcontrol_pumpBacklash_startMillis > 0) {
        unsigned long _pumpBacklashMillis = confHeatc.pumpBacklash * 60000;
        unsigned long _elapsedMillis = (millis() - heatcontrol_pumpBacklash_startMillis);
        unsigned long _remainingMinutes = (_pumpBacklashMillis - _elapsedMillis) / 60000;
        return _remainingMinutes + 1;
    }
    else return 0;
}



void heatcontrol_lockTime_begin() {
    if (heatcontrol_skipLocktimeOnce) {
        // if last turned on by test mode -> no lock time after that
        heatcontrol_skipLocktimeOnce = false;
    }
    else {
        uint8_t _lockTimeMinutes;
        
        pcf8574.digitalWrite(P1, LOW); // output active low!

        // get current lock time depending on outside temp
        int _tOut = (int)owTemp_out;
        if (_tOut <= -20) _lockTimeMinutes = confHeatc.heatLocktime[0];
        else if (_tOut <= -15) _lockTimeMinutes = confHeatc.heatLocktime[1];
        else if (_tOut <= -10) _lockTimeMinutes = confHeatc.heatLocktime[2];
        else if (_tOut <= -5) _lockTimeMinutes = confHeatc.heatLocktime[3];
        else if (_tOut <= 0) _lockTimeMinutes = confHeatc.heatLocktime[4];
        else if (_tOut <= 5) _lockTimeMinutes = confHeatc.heatLocktime[5];
        else if (_tOut <= 10) _lockTimeMinutes = confHeatc.heatLocktime[6];
        else if (_tOut <= 15) _lockTimeMinutes = confHeatc.heatLocktime[7];
        else if (_tOut <= 20) _lockTimeMinutes = confHeatc.heatLocktime[8];
        else if (_tOut > 20) _lockTimeMinutes = confHeatc.heatLocktime[8]; // also use value for <=20 for >20
        else _lockTimeMinutes = 0;
        
        #ifdef DEBUGMODE
            // i dont like to wait during development
            if (_lockTimeMinutes == 0) _lockTimeMinutes = 1;
        #endif

        heatcontrol_lockTime_startMillis = millis();
        heatcontrol_lockTime_totalMillis = _lockTimeMinutes * 60000;

        heatcontrol_lastState_lockActive = true;

        sprintf_P(logBuf, PGMStr_heatc_locktimeStart, PGMStr_heatc_logName, _lockTimeMinutes);
        sendLog(logBuf, LOGLEVEL_INFO);

        heatcontrol_publish_lockTime();
        heatcontrol_publish_lockActive();

        display_updateImmediately = true;
    }
}

void heatcontrol_lockTime_handleTimeout() {
    // handle lock time
    if (heatcontrol_lockTime_startMillis > 0) {
        if ((millis() - heatcontrol_lockTime_startMillis) > heatcontrol_lockTime_totalMillis) {
            // locktime is over
            heatcontrol_lockTime_startMillis = 0;
            heatcontrol_lockTime_totalMillis = 0;

            heatcontrol_lock_release();

            sprintf_P(logBuf, PGMStr_heatc_locktimeStop, PGMStr_heatc_logName);
            sendLog(logBuf, LOGLEVEL_INFO);

            heatcontrol_publish_lockTime();
            heatcontrol_publish_lockActive();
            
            display_updateImmediately = true;
        }
    }
    else {
        heatcontrol_lock_release();
    }
}

uint8_t heatcontrol_lockTime_getRemainingMins() {
    if(heatcontrol_lockTime_startMillis > 0) {
        unsigned long _elapsedMillis = (millis() - heatcontrol_lockTime_startMillis);
        unsigned long _remainingMinutes = (heatcontrol_lockTime_totalMillis - _elapsedMillis) / 60000;
        return _remainingMinutes + 1;
    }
    else return 0;
}


void heatcontrol_lock_release() {
    bool _currStat = heatcontrol_getLockActive();
    if(!_currStat) {
        pcf8574.digitalWrite(P1, HIGH); // output active low!
        if (_currStat != heatcontrol_lastState_lockActive) {
            heatcontrol_publish_lockActive();
            heatcontrol_lastState_lockActive = _currStat;
        }
    }
}

bool heatcontrol_getLockActive() {
    if (heatcontrol_lockTime_startMillis == 0 && !heatcontrol_lockTemp_active()) return false;
    else return true;
}



bool heatcontrol_lockTemp_active() {
    int _tFeed = (int)owTemp_feed;
    if (!confHeatc.useHeatCurve) return false;
    else if (_tFeed <= (heatcontrol_currentHeatCurveValue - confHeatc.hystereseOn)) {
        return false;
    }
    else {
        pcf8574.digitalWrite(P1, LOW); // output active low!
        // achtung published zu oft
        //////if (!heatcontrol_lastState_lockActive) {
        //////    heatcontrol_publish_lockActive();
        //////    heatcontrol_lastState_lockActive = true;
        //////}
        return true;
    }
}



void heatcontrol_mainFunction() {

    // stop testmode after timeout
    if (heatcontrol_testmode_startMillis > 0) {
        unsigned long _testmodeMillis = HEATCONTROL_TESTMODE_MINUTES * 60000;
        if ((millis() - heatcontrol_testmode_startMillis) > _testmodeMillis) {
            heatcontrol_testMode_off();
            //heatcontrol_testmode_startMillis = 0; // is already done in heatcontrol_testMode_off()
        }
    }

    // really start testmode if it was set
    // MUST be separate if and not included above, or it will instantly switch back on after timeout!!
    if (heatcontrol_testmode_startMillis > 0) {
        if ((millis() - heatcontrol_testmode_startMillis) > 2800 && !heatcontrol_state_testmode_active) {
            heatcontrol_testMode_reallyStart();
            //heatcontrol_testmode_startMillis = 0;  // is already done in heatcontrol_testMode_reallyStart()
        }
    }


    heatcontrol_lockTime_handleTimeout();


    int _tOut = (int)owTemp_out;
    int _tFeed = (int)owTemp_feed;
    //int _tReturn = (int)owTemp_return;

    // heat curve - get current value if enabled
    if (confHeatc.useHeatCurve) {
        if (_tOut <= -20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[0];
        else if (_tOut <= -15) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[1];
        else if (_tOut <= -10) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[2];
        else if (_tOut <= -5) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[3];
        else if (_tOut <= 0) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[4];
        else if (_tOut <= 5) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[5];
        else if (_tOut <= 10) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[6];
        else if (_tOut <= 15) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[7];
        else if (_tOut <= 20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[8];
        else if (_tOut > 20) heatcontrol_currentHeatCurveValue = confHeatc.heatCurve[8]; // also use value for <=20 for >20
        else heatcontrol_currentHeatCurveValue = 0;
    }
    else heatcontrol_currentHeatCurveValue = 0;

    #ifdef DEBUGMODE
        // during development i prefer working in a heated room 
        // so i need this to be able to do some tests ;-) 
        if (confHeatc.useHeatCurve && heatcontrol_currentHeatCurveValue == 0) heatcontrol_currentHeatCurveValue = 35;
    #endif


    


    // switch heating and pump if requested
    // only if currently heating is inactive
    if (!heatcontrol_out_heat) {
        // check pre conditions
        bool _switchOnPreCondition = false;

        // no locktime is currently active (and usage of lock time is not disabled)
        if (heatcontrol_lockTime_startMillis == 0) _switchOnPreCondition = true;

        // overrule precondition to false if outside temp is above limit
        if ( _tOut >= confHeatc.disableAboveTOut)  _switchOnPreCondition = false;
        
        // overrule precondition if testmode is requested
        if (heatcontrol_state_testmode_active) _switchOnPreCondition = true;


        if (_switchOnPreCondition) {
            // check if any switch-on condition is met
            bool _switchOnCondition = false;

            // test mode has been activated
            if (heatcontrol_state_testmode_active) {
                sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatTestmode);
                sendLog(logBuf);
                _switchOnCondition = true;
            }

            // heat request, heat curve enabled AND temp_feed has fallen below treshold
            else if (heatcontrol_in_heat_request && confHeatc.useHeatCurve && !heatcontrol_lockTemp_active() ) {
                sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatRequestHeatcurve);
                sendLog(logBuf);
                _switchOnCondition = true;
            }

            // heat request, heat curve disabled
            else if (heatcontrol_in_heat_request && !confHeatc.useHeatCurve) {
                sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatRequest);
                sendLog(logBuf);
                _switchOnCondition = true;
            }

            // heat curve forced / heat request ignored 
            // and current feed-temperature <= (currentHeatCurveValue - hystereseOn)
            else if ( confHeatc.useHeatCurve && confHeatc.forceHeatCurve && confHeatc.forceHeatCurveAlsoForSwitchOn && confHeatc.hystereseOn >= 3 && _tFeed <= (heatcontrol_currentHeatCurveValue - confHeatc.hystereseOn) ) {
                sprintf_P(logBuf, PGMStr_heatc_switchOnCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOnCondition_heatCurveForced);
                sendLog(logBuf);
                _switchOnCondition = true;
            }


            if(_switchOnCondition) {
                if (!heatcontrol_out_pump && !heatcontrol_in_sw_disableControl_heating) heatcontrol_switchPump(true); // pump ON
                if (!heatcontrol_out_heat && !heatcontrol_in_sw_disableControl_pump) heatcontrol_switchHeating(true); // heating ON
                if (!heatcontrol_in_sw_disableControl_pump) heatcontrol_pumpBacklash_cancel(); // if pump is in backlash - avoid it going off during heating is running
                display_updateImmediately = true;
            }
        }
        
    }

    // heating is currently switched on 
    else if ( heatcontrol_out_heat ) {
        // check if any switch-off condition is met
        bool _switchOffCondition = false;

        // heat request and/or testmode has stopped and
        // useHeatCurve and/or at least forceHeatCurve is disabled
        // note - if confHeatc.forceHeatCurve == true switching off is ONLY handled when heat curve target is reached!
        if ( (!confHeatc.useHeatCurve || (confHeatc.useHeatCurve && !confHeatc.forceHeatCurve) ) && !heatcontrol_in_heat_request && !heatcontrol_state_testmode_active) {
            sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_heatRequest);
            sendLog(logBuf);
            _switchOffCondition = true;
        }
        
        // test mode is NOT active, 
        // heatcurve is enabled and
        // feed temperature exceeds heat curve value ( + confHeatc.hystereseOff)
        else if (!heatcontrol_state_testmode_active && confHeatc.useHeatCurve && _tFeed >= (heatcontrol_currentHeatCurveValue + confHeatc.hystereseOff) ) {
            sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_heatCurve);
            sendLog(logBuf);
            _switchOffCondition = true;
        }

        // feed temperature exceeds safety limit
        else if ((int)owTemp_feed >= confHeatc.tempFeedLimit) {
            sprintf_P(logBuf, PGMStr_heatc_switchOffCondition, PGMStr_heatc_logName, PGMStr_heatc_switchOffCondition_tempFeedLimit);
            sendLog(logBuf);
            _switchOffCondition = true;
        }


        if (_switchOffCondition) {
            //   --> switch heating off
            //   --> switch pump in backlash
            heatcontrol_switchHeating(false); // heating OFF
            heatcontrol_pumpBacklash_begin(); // pump - enable backlash
            heatcontrol_lockTime_begin();
            display_updateImmediately = true;
        }
    }
    else {
        // just for safety --> switch heating off in all other cases
        if(heatcontrol_out_heat) heatcontrol_switchHeating(false); // heating OFF
    }

    // pump off if in backlash mode after timeout
    if (heatcontrol_state_pumpBacklash_active) {
        heatcontrol_pumpBacklash_handleTimeout();
    }

}






// TEST MODE

void heatcontrol_testMode_toggle() {
    if(!heatcontrol_state_testmode_started) {
        heatcontrol_testMode_on();
    } 
    else {
        heatcontrol_testMode_off();
    }
}


void heatcontrol_testMode_on() {
    heatcontrol_state_testmode_started = true;
    heatcontrol_state_testmode_active = false; // will be set by heatcontrol_testMode_reallyStart()
    heatcontrol_testmode_startMillis = millis();

    // Status-LED TESTMODE -> ON
    heatcontrol_out_stat_testmode = LOW; // LOW = LED ON
    pcf8574.digitalWrite(P0, heatcontrol_out_stat_testmode);

    heatcontrol_publish_testMode();

    char _dispMsg[17];
    snprintf_P(_dispMsg, 17, PGMStr_heatc_display_testModeON);
    display_showRow2OverlayMessage((char*)_dispMsg);
}


void heatcontrol_testMode_off() {
    heatcontrol_state_testmode_started = false;
    heatcontrol_state_testmode_active = false;
    heatcontrol_testmode_startMillis = 0;
    heatcontrol_skipLocktimeOnce = true;

    // Status-LED TESTMODE -> OFF
    heatcontrol_out_stat_testmode = HIGH; // HIGH = LED OFF
    pcf8574.digitalWrite(P0, heatcontrol_out_stat_testmode);

    heatcontrol_publish_testMode();

    //heatcontrol_switchPump(false);
    //heatcontrol_switchHeating(false);

    char _dispMsg[17];
    snprintf_P(_dispMsg, 17, PGMStr_heatc_display_testModeOFF);
    display_showRow2OverlayMessage((char*)_dispMsg);

    sprintf_P(logBuf, PGMStr_heatc_testmodeStop, PGMStr_heatc_logName);
    sendLog(logBuf);
}


void heatcontrol_testMode_reallyStart() {
    heatcontrol_state_testmode_started = true;
    heatcontrol_state_testmode_active = true;
    heatcontrol_testmode_startMillis = millis();
    display_updateImmediately = true;

    sprintf_P(logBuf, PGMStr_heatc_testmodeStart, PGMStr_heatc_logName);
    sendLog(logBuf);
}


uint8_t heatcontrol_testMode_getRemainingMins() {
    if(heatcontrol_testmode_startMillis > 0) {
        unsigned long _testmodeMillis = HEATCONTROL_TESTMODE_MINUTES * 60000;
        unsigned long _elapsedMillis = (millis() - heatcontrol_testmode_startMillis);
        unsigned long _remainingMinutes = (_testmodeMillis - _elapsedMillis) / 60000;
        return _remainingMinutes + 1;
    }
    else return 0;
}




void heatcontrol_pump_forceRunAfterTimeout() {
    if (!heatcontrol_pump_inForceRunMode) {
        bool _runCondition = false;

        #ifdef DEBUGMODE // use minutes rather than hours for testing
            // pump has not run since system start and forceRunInterval is exceeded
            if (heatcontrol_pump_lastRunMillis == 0 && millis() >= (confHeatc.pump_forceRunInterval * 60000) ) {
                _runCondition = true;
            }

            // pump last run longer ago than forceRunInterval
            if (heatcontrol_pump_lastRunMillis > 0 && (millis() - heatcontrol_pump_lastRunMillis) >= (confHeatc.pump_forceRunInterval * 60000) ) {
                _runCondition = true;
            }
        #else // normal operation - interval is set in hours
            // pump has not run since system start and forceRunInterval is exceeded
            if (heatcontrol_pump_lastRunMillis == 0 && millis() >= (confHeatc.pump_forceRunInterval * 3600000) ) {
                _runCondition = true;
            }

            // pump last run longer ago than forceRunInterval
            if (heatcontrol_pump_lastRunMillis > 0 && (millis() - heatcontrol_pump_lastRunMillis) >= (confHeatc.pump_forceRunInterval * 3600000) ) {
                _runCondition = true;
            }
        #endif

        bool _runCondition_inTimeWindow = false;

        if (_runCondition) {
            sprintf_P(logBuf, PGMStr_heatc_pumpForceRun, PGMStr_heatc_logName);
            sendLog(logBuf);

            #ifdef ENABLE_FEATURE_NTP_TIME
                if (confTime.ntpEnable && confHeatc.pump_forceRunTimeBegin != 0 && confHeatc.pump_forceRunTimeBegin != 0)
                {
                    updateTime();
                    if (lt.tm_year > 70) { // check if time has been synced
                        // check if we are in the configured time window
                        if (lt.tm_hour >= confHeatc.pump_forceRunTimeBegin && lt.tm_hour < confHeatc.pump_forceRunTimeBegin) {
                            _runCondition_inTimeWindow = true;
                        }
                    }
                    else {
                        // NTP time is not synced so always assume we are in the time window
                        _runCondition_inTimeWindow = true;    
                    }
                }
                else {
                    // NTP is disabled in so always assume we are in the time window
                    _runCondition_inTimeWindow = true;
                }
            #else
                // NTP is not compiled in so always assume we are in the time window
                _runCondition_inTimeWindow = true;
            #endif

            if (_runCondition_inTimeWindow) {
                heatcontrol_pump_inForceRunMode = true;
                heatcontrol_switchPump(true);
                heatcontrol_pumpBacklash_begin();
            }
        }
    }
}


#endif // FIRMWARE_VARIANT_HEATCONTROL