#include "oven.h"

#include <QtDebug>
#include <QTime>
#include <cmath>

#include "soundplayer.h"
#include "dirtylevel.h"

Oven *Oven::instance = 0;

Oven::Oven(QObject *parent) : QObject(parent)
{
    interface = NULL;

    currentHumidity_ = 0;
    currentTemp_ = 0;
    currentInterTemp_ = 0;

    cookingTimer.setSingleShot(true);
    connect(&cookingTimer, SIGNAL(timeout()), SLOT(stopCooking()));

    damperTimer.setSingleShot(true);
    connect(&damperTimer, SIGNAL(timeout()), SLOT(closeDamper()));

    humidificationTimer.setSingleShot(true);
    connect(&humidificationTimer, SIGNAL(timeout()), SLOT(stopHumidification()));

    damperRepeatRuntimeTimer.setSingleShot(true);
    connect(&damperRepeatRuntimeTimer, SIGNAL(timeout()), SLOT(idleRepeatDamper()));

    damperRepeatDelayTimer.setSingleShot(true);
    connect(&damperRepeatDelayTimer, SIGNAL(timeout()), SLOT(runRepeatDamper()));

    humidificationRepeatRuntimeTimer.setSingleShot(true);
    connect(&humidificationRepeatRuntimeTimer, SIGNAL(timeout()), SLOT(idleRepeatHumidification()));

    humidificationRepeatDelayTimer.setSingleShot(true);
    connect(&humidificationRepeatDelayTimer, SIGNAL(timeout()), SLOT(runRepeatHumidification()));

    isInterTempValid_ = false;

    humidificationRepeat = false;
    damperRepeat = false;
}

void Oven::setInterface(OvenInterface *interface)
{
    if (this->interface)
        this->interface->disconnect();

    this->interface = interface;
    connect(interface, SIGNAL(doorOpened()), this, SLOT(onDoorOpened()));
    connect(interface, SIGNAL(doorClosed()), this, SLOT(onDoorClosed()));
}

void Oven::setMode(Define::Mode mode)
{
    if (setMode_(mode))
        emit changed(this);
}

bool Oven::setMode_(Define::Mode mode)
{
    switch (mode)
    {
    case Define::DryMode:
    case Define::SteamMode:
    case Define::CombiMode:
        break;
    default:
        return false;
    }

    if (mode != mode_)
    {
        mode_ = mode;
        interface->setMode(mode);

        return true;
    }

    return false;
}

void Oven::setDefault(Define::Mode mode)
{
    setMode_(mode);
    switch (mode)
    {
    case Define::DryMode:
        setHumidity_(0);
        setTemp_(160);
        break;
    case Define::SteamMode:
        setHumidity_(100);
        setTemp_(100);
        break;
    case Define::CombiMode:
        setHumidity_(50);
        setTemp_(100);
        break;
    default:
        return;
    }

    setTime(0);
    setInterTempEnabled_(false);
    setInterTemp_(minInterTemp());
    setFan_(4);

    cooking_ = false;
    preheating_ = false;
    cooldown_ = false;
    damper_ = false;
    humidification_ = false;
    door_ = interface->door();
    paused_ = false;

    emit changed(this);
}

void Oven::setHumidity(int percentage)
{
    if (setHumidity_(percentage))
        emit changed(this);
}

bool Oven::setHumidity_(int percentage)
{
    if (percentage < 0 || percentage > 100)
        return false;

    if (percentage != humidity_)
    {
        humidity_ = percentage;
        interface->setHumidity(percentage);

        return true;
    }

    return false;
}

void Oven::setTemp(int celsius)
{
    if (setTemp_(celsius))
        emit changed(this);
}

bool Oven::setTemp_(int celsius)
{
    if (celsius < minTemp() || celsius > maxTemp())
        return false;

    if (celsius != temp_)
    {
        temp_ = celsius;
        interface->setTemp(celsius);

        return true;
    }

    return false;
}

int Oven::time()
{
    int left = cookingTimer.remainingTime();
    int interval = cookingTimer.interval();
    if (left > interval)
        left = interval;

    if (cooking())
        return ceil(left / 1000.0);

    return time_;
}

void Oven::setTime(int secs)
{
    time_ = secs;
    cookingTimer.setInterval(secs * 1000);
}

void Oven::setInterTempEnabled(bool enabled)
{
    if (setInterTempEnabled_(enabled))
        emit changed(this);
}

bool Oven::setInterTempEnabled_(bool enabled)
{
    if (interTempEnabled_ != enabled)
    {
        interTempEnabled_ = enabled;
        if (interTempEnabled_)
            interface->setInterTemp(interTemp_);
        else
            interface->setInterTemp(0);

        return true;
    }

    return false;
}

void Oven::setInterTemp(int celsius)
{
    if (setInterTemp_(celsius))
        emit changed(this);
}

bool Oven::setInterTemp_(int celsius)
{
    if (celsius < minInterTemp() || celsius > maxInterTemp())
        return false;

    if (celsius != interTemp_)
    {
        interTemp_ = celsius;
        if (interTempEnabled_)
            interface->setInterTemp(celsius);

        return true;
    }

    return false;
}

void Oven::setFan(int speed)
{
    if (setFan_(speed))
        emit changed(this);
}

bool Oven::setFan_(int speed)
{
    if (speed < minFan() || speed > maxFan())
        return false;

    if (speed == fan_)
        return false;

    int rpm = 0;
    switch (speed)
    {
    case 1:
        rpm = 500;
        break;
    case 2:
        rpm = 725;
        break;
    case 3:
        rpm = 950;
        break;
    case 4:
        rpm = 1175;
        break;
    case 5:
        rpm = 1400;
        break;
    }

    fan_ = speed;
    interface->setFan(rpm);

    return true;
}

bool Oven::isInterTempValid()
{
    return isInterTempValid_ && interTempValidTime.elapsed() > 3000;
}

int Oven::msecs()
{
    int left = cookingTimer.remainingTime();
    int interval = cookingTimer.interval();
    if (left > interval)
        left = interval;

    if (cooking())
        return left;

    return time_ * 1000;
}

bool Oven::cookingStartable()
{
    if (/*door() || */cooking() || time() <= 0)
        return false;

    return true;
}

bool Oven::preheatingStartable()
{
    if (door())
        return false;

    return true;
}

bool Oven::cooldownStartable()
{
    return true;
}

bool Oven::damperOpenable()
{
    return true;
}

bool Oven::humidificationStartable()
{
    return true;
}

void Oven::stop()
{
    if (cooking())
        stopCooking();

    if (preheating())
        stopPreheating();

    if (damper())
        closeDamper();

    if (cooldown())
        stopCooldown();

    if (humidification())
        stopHumidification();

    if (paused())
        paused_ = false;
}

void Oven::startCooking()
{
    if (!cooking())
    {
        if (door())
        {
            paused_ = true;
        }
        else
        {
            paused_ = false;

            if (preheating())
                stopPreheating();

            if (cooldown())
                stopCooldown();

            cooking_ = true;
            cookingTimer.start();
            interface->startCooking();

            DirtyLevel::cookStart();
        }

        emit changed(this);
    }
}

void Oven::stopCooking()
{
    if (cooking())
    {
        if (time() > 0)
        {
            paused_ = true;
            setTime(time());
            cookingTimer.stop();
        }
        else
        {
            SoundPlayer::playStop();

            setTime(0);
        }

        cooking_ = false;
        interface->stopCooking();

        DirtyLevel::cookEnd();

        emit changed(this);
    }
}

void Oven::startPreheating()
{
//    if (preheatingStartable())
//    {
        if (cooking())
            stopCooking();

        preheating_ = true;
        interface->startPreheating();

        emit changed(this);
//    }
}

void Oven::stopPreheating()
{
    if (preheating())
    {
        preheating_ = false;
        interface->stopPreheating();

        emit changed(this);

        if (paused())
            startCooking();
    }
}

void Oven::startCooldown()
{
    if (cooldownStartable())
    {
        if (cooking())
            stopCooking();

        cooldown_ = true;
        interface->startCooldown();

        emit changed(this);
    }
}

void Oven::stopCooldown()
{
    if (cooldown())
    {
        cooldown_ = false;
        interface->stopCooldown();

        emit changed(this);

        if (paused())
            startCooking();
    }
}

void Oven::startHumidification(int secs)
{
    if (humidificationStartable())
    {
        if (humidificationRepeat)
        {

        }
        else
        {
            humidification_ = true;
            humidificationTimer.start(secs * 1000);
            interface->startHumidification();

            emit changed(this);
        }
    }
}

void Oven::stopHumidification()
{
    if (humidification())
    {
        humidification_ = false;
        if (humidificationTimer.isActive())
            humidificationTimer.stop();

        interface->stopHumidification();

        emit changed(this);
    }
}

void Oven::repeatHumidification(int runtime, int delay, int count)
{
    humidificationRepeatInfinitely = count == 0;
    humidificationRepeatRuntime = runtime * 1000;
    humidificationRepeatDelay = delay * 1000;
    humidificationRepeatCount = count;

    if (humidificationRepeat)
    {
        if (humidificationRepeatPaused)
        {
            if (humidificationRepeatPausedOnRuntime)
                humidificationRepeatRemainingTime = humidificationRepeatRuntime;
            else
                humidificationRepeatRemainingTime = humidificationRepeatDelay;
        }
        else
        {
            runRepeatHumidification();
        }
    }
    else
    {
        humidificationRepeat = true;

        if (humidification())
        {
            humidificationRepeatPaused = true;
            humidificationRepeatPausedOnRuntime = false;
            humidificationRepeatRemainingTime = humidificationRepeatDelay;
        }
        else
        {
            humidificationRepeatPaused = false;
            humidificationRepeatRemainingTime = 0;
            runRepeatHumidification();
        }
    }
}

void Oven::stopRepeatHumidification()
{
    if (!humidificationRepeat)
        return;

    humidificationRepeat = false;

    humidificationRepeatRuntimeTimer.stop();
    humidificationRepeatDelayTimer.stop();

    if (humidification())
        stopHumidification();
}

void Oven::pauseRepeatHumidification()
{
    if (!humidificationRepeat)
        return;

    humidificationRepeatPaused = true;

    if (humidificationRepeatRuntimeTimer.isActive())
    {
        humidificationRepeatPausedOnRuntime = true;
        humidificationRepeatRemainingTime = humidificationRepeatRuntimeTimer.remainingTime();
        humidificationRepeatRuntimeTimer.stop();
    }
    else
    {
        humidificationRepeatPausedOnRuntime = false;
        humidificationRepeatRemainingTime = humidificationRepeatDelayTimer.remainingTime();
        humidificationRepeatDelayTimer.stop();
    }
}

void Oven::resumeRepeatHumidification()
{
    if (!humidificationRepeat || !humidificationRepeatPaused)
        return;

    humidificationRepeatPaused = false;

    if (humidificationRepeatPausedOnRuntime)
    {
        humidificationRepeatRuntimeTimer.start(humidificationRepeatRemainingTime);

        humidification_ = true;
        interface->startHumidification();
        emit changed(this);
    }
    else
    {
        humidificationRepeatDelayTimer.start(humidificationRepeatRemainingTime);
    }
}

void Oven::runRepeatHumidification()
{
    if (humidificationRepeatInfinitely || humidificationRepeatCount-- > 0)
    {
        humidificationRepeatRuntimeTimer.start(humidificationRepeatRuntime);

        humidification_ = true;
        interface->startHumidification();
        emit changed(this);
    }
}

void Oven::idleRepeatHumidification()
{
    humidificationRepeatDelayTimer.start(humidificationRepeatDelay);

    humidification_ = false;
    interface->stopHumidification();
    emit changed(this);
}

void Oven::openDamper(int secs)
{
    if (!damper())
    {
        damper_ = true;
        interface->openDamper();

        emit changed(this);
    }

    damperTimer.start(secs * 1000);
}

void Oven::closeDamper()
{
    if (damper())
    {
        damper_ = false;
        if (damperTimer.isActive())
            damperTimer.stop();

        interface->closeDamper();

        emit changed(this);
    }
}

void Oven::repeatDamper(int runtime, int delay, int count)
{
    damperRepeatInfinitely = count == 0;
    damperRepeatRuntime = runtime * 1000;
    damperRepeatDelay = delay * 1000;
    damperRepeatCount = count;

    if (damperRepeat)
    {
        if (damperRepeatPaused)
        {
            if (damperRepeatPausedOnRuntime)
                damperRepeatRemainingTime = damperRepeatRuntime;
            else
                damperRepeatRemainingTime = damperRepeatDelay;
        }
        else
        {
            runRepeatDamper();
        }
    }
    else
    {
        damperRepeat = true;

        if (damper())
        {
            damperRepeatPaused = true;
            damperRepeatPausedOnRuntime = false;
            damperRepeatRemainingTime = damperRepeatDelay;
        }
        else
        {
            damperRepeatPaused = false;
            damperRepeatRemainingTime = 0;
            runRepeatDamper();
        }
    }
}

void Oven::stopRepeatDamper()
{
    if (!damperRepeat)
        return;

    damperRepeat = false;

    damperRepeatRuntimeTimer.stop();
    damperRepeatDelayTimer.stop();

    if (damper())
        closeDamper();
}

void Oven::pauseRepeatDamper()
{
    if (!damperRepeat)
        return;

    damperRepeatPaused = true;

    if (damperRepeatRuntimeTimer.isActive())
    {
        damperRepeatPausedOnRuntime = true;
        damperRepeatRemainingTime = damperRepeatRuntimeTimer.remainingTime();
        damperRepeatRuntimeTimer.stop();
    }
    else
    {
        damperRepeatPausedOnRuntime = false;
        damperRepeatRemainingTime = damperRepeatDelayTimer.remainingTime();
        damperRepeatDelayTimer.stop();
    }
}

void Oven::resumeRepeatDamper()
{
    if (!damperRepeat || !damperRepeatPaused)
        return;

    damperRepeatPaused = false;

    if (damperRepeatPausedOnRuntime)
    {
        damperRepeatRuntimeTimer.start(damperRepeatRemainingTime);

        damper_ = true;
        interface->closeDamper();
        emit changed(this);
    }
    else
    {
        damperRepeatDelayTimer.start(damperRepeatRemainingTime);
    }
}

void Oven::runRepeatDamper()
{
    if (damperRepeatInfinitely || damperRepeatCount-- > 0)
    {
        damperRepeatRuntimeTimer.start(damperRepeatRuntime);

        damper_ = true;
        interface->openDamper();
        emit changed(this);
    }
}

void Oven::idleRepeatDamper()
{
    damperRepeatDelayTimer.start(damperRepeatDelay);

    damper_ = false;
    interface->closeDamper();
    emit changed(this);
}

void Oven::setCurrentHumidity(int percentage)
{
    if (currentHumidity() != percentage)
    {
        currentHumidity_ = percentage;
        emit changed(this);
    }
}

void Oven::setCurrentTemp(int celsius)
{
    if (currentTemp() != celsius)
    {
        currentTemp_ = celsius;
        emit changed(this);
    }
}

void Oven::setCurrentInterTemp(int celsius)
{
    if (currentInterTemp() != celsius)
    {
        currentInterTemp_ = celsius;

        if (isInterTempValid_)
        {
            if (currentInterTemp_ == currentTemp_)
                isInterTempValid_ = false;
        }
        else
        {
            if (currentInterTemp_ + 20 < currentTemp_)
            {
                isInterTempValid_ = true;
                interTempValidTime = QTime::currentTime();
            }
        }

        emit changed(this);
    }
}





int Oven::maxTemp()
{
    switch (mode())
    {
    case Define::DryMode:
        return 300;
    case Define::SteamMode:
        return 130;
    case Define::CombiMode:
        return 300;
    default:
        return 0;
    }
}

int Oven::minTemp()
{
    switch (mode())
    {
    case Define::DryMode:
        return 30;
    case Define::SteamMode:
        return 30;
    case Define::CombiMode:
        return 30;
    default:
        return 0;
    }
}

int Oven::maxInterTemp()
{
    switch (mode())
    {
    case Define::DryMode:
        return 99;
    case Define::SteamMode:
        return 99;
    case Define::CombiMode:
        return 99;
    default:
        return 0;
    }
}

int Oven::minInterTemp()
{
    switch (mode())
    {
    case Define::DryMode:
        return 30;
    case Define::SteamMode:
        return 30;
    case Define::CombiMode:
        return 30;
    default:
        return 0;
    }
}

int Oven::maxFan()
{
    return 5;
}

int Oven::minFan()
{
    return 1;
}

void Oven::onDoorOpened()
{
    door_ = true;

    emit changed(this);

    if (cooking())
    {
        stopCooking();

        if (damperRepeat)
            pauseRepeatDamper();

        openDamper(7);
    }
}

void Oven::onDoorClosed()
{
    door_ = false;

    emit changed(this);

    if (!cooldown() && !preheating() && paused())
        startCooking();

    if (damper())
        closeDamper();

    if (damperRepeat)
        resumeRepeatDamper();
}