#include "autocook.h"
#include "soundplayer.h"

AutoCook::AutoCook() : currentStepIndex(0), done_(false), doorOpened(false), checkingCoreTemp(false)
{

}

AutoCook::AutoCook(Cook cook) : AutoCook()
{
    this->cook = cook;
    for (int idx = 0; idx < this->cook.steps.size(); idx++)
        this->cook.steps[idx].time *= 1000;


    startStep();
}

void AutoCook::startStep()
{
    Oven *oven = Oven::getInstance();

    CookStep &currentStep = cook.steps[currentStepIndex];
    switch (Define::classify(currentStep.type))
    {
    case Define::PreheatClass:
        startHumidity = oven->currentHumidity();
        startTemp = oven->currentTemp();
        isWaitingDoorOpened_ = false;
        oven->setMode(currentStep.mode);
        oven->setHumidity(currentStep.humidity);
        oven->setTemp(currentStep.temp);
        oven->setFan(currentStep.fan);
        oven->startPreheating();
        break;
    case Define::DoorClass:
        isWaitingDoorOpened_ = true;
        break;
    case Define::CookClass:
        startHumidity = oven->currentHumidity();
        startTemp = oven->currentTemp();
        isWaitingDoorOpened_ = false;
        oven->setMode(currentStep.mode);
        oven->setHumidity(currentStep.humidity);
        oven->setTemp(currentStep.temp);
        oven->setFan(currentStep.fan);
        oven->setTime(remainingTime() + 300);
        oven->setInterTempEnabled(false);
        if (cook.isCoreTempValid())
            oven->setInterTemp(cook.coreTemp());

        if (currentStep.dehumidification)
        {
            if (currentStep.dehumidificationRepeatDelay)
                oven->repeatDamper(currentStep.dehumidification, currentStep.dehumidificationRepeatDelay, currentStep.dehumidificationRepeatCount);
            else
                oven->openDamper(currentStep.dehumidification);
        }

        if (currentStep.humidification)
        {
            if (currentStep.humidificationRepeatDelay)
                oven->repeatHumidification(currentStep.humidification, currentStep.humidificationRepeatDelay, currentStep.humidificationRepeatCount);
            else
                oven->startHumidification(currentStep.humidification);
        }

        stepStartTime.start();
        oven->startCooking();
    case Define::InvalidClass:
        break;
    }

    advance();
}

void AutoCook::nextStep()
{
    Oven *oven = Oven::getInstance();

    CookStep &currentStep = cook.steps[currentStepIndex];
    CookStep &nextStep = cook.steps[currentStepIndex + 1];

    Define::StepClass currentClass = Define::classify(currentStep.type);
    Define::StepClass nextClass = Define::classify(nextStep.type);

    if (currentClass == Define::PreheatClass && nextClass == Define::DoorClass)
    {
        oven->stopPreheating();
        isWaitingDoorOpened_ = true;
    }
    else if (currentClass == Define::DoorClass && nextClass == Define::CookClass)
    {
        startHumidity = oven->currentHumidity();
        startTemp = oven->currentTemp();
        oven->setMode(nextStep.mode);
        oven->setHumidity(nextStep.humidity);
        oven->setTemp(nextStep.temp);
        oven->setFan(nextStep.fan);
        oven->setTime(remainingTime() + 300);

        if (cook.isCoreTempValid())
            oven->setInterTemp(cook.coreTemp());

        stepStartTime.start();
        oven->startCooking();

        if (nextStep.dehumidification)
        {
            if (nextStep.dehumidificationRepeatDelay)
                oven->repeatDamper(nextStep.dehumidification, nextStep.dehumidificationRepeatDelay, nextStep.dehumidificationRepeatCount);
            else
                oven->openDamper(nextStep.dehumidification);
        }

        if (nextStep.humidification)
        {
            if (nextStep.humidificationRepeatDelay)
                oven->repeatHumidification(nextStep.humidification, nextStep.humidificationRepeatDelay, nextStep.humidificationRepeatCount);
            else
                oven->startHumidification(nextStep.humidification);
        }

        if (checkingCoreTemp && currentStepIndex + 2 >= cook.steps.size())
        {
            lastCoreTempIncreasedTime.start();
        }
    }
    else if (currentClass == Define::CookClass && nextClass == Define::CookClass)
    {
        startHumidity = oven->currentHumidity();
        startTemp = oven->currentTemp();
        oven->setMode(nextStep.mode);
        oven->setHumidity(nextStep.humidity);
        oven->setTemp(nextStep.temp);
        oven->setFan(nextStep.fan);
        stepStartTime.start();

        if (currentStep.dehumidification)
        {
            if (nextStep.dehumidification)
            {
                if (currentStep.dehumidificationRepeatDelay)
                {
                    if (nextStep.dehumidificationRepeatDelay)
                        oven->repeatDamper(nextStep.dehumidification, nextStep.dehumidificationRepeatDelay, nextStep.dehumidificationRepeatCount);
                    else
                    {
                        oven->stopRepeatDamper();
                        oven->openDamper(nextStep.dehumidification);
                    }
                }
                else
                {
                    if (nextStep.dehumidificationRepeatDelay)
                        oven->repeatDamper(nextStep.dehumidification, nextStep.dehumidificationRepeatDelay, nextStep.dehumidificationRepeatCount);
                    else
                        oven->openDamper(nextStep.dehumidification);
                }
            }
            else
            {
                if (currentStep.dehumidificationRepeatDelay)
                    oven->stopRepeatDamper();
            }
        }
        else
        {
            if (nextStep.dehumidification)
            {
                if (nextStep.dehumidificationRepeatDelay)
                    oven->repeatDamper(nextStep.dehumidification, nextStep.dehumidificationRepeatDelay, nextStep.dehumidificationRepeatCount);
                else
                    oven->openDamper(nextStep.dehumidification);
            }
        }

        if (nextStep.humidification != currentStep.humidification
                || nextStep.humidificationRepeatDelay != currentStep.humidificationRepeatDelay
                || nextStep.humidificationRepeatCount != currentStep.humidificationRepeatCount)
        {
            if (nextStep.humidificationRepeatDelay)
            {
                if (currentStep.humidificationRepeatDelay)
                    oven->repeatHumidification(nextStep.humidification, nextStep.humidificationRepeatDelay, nextStep.humidificationRepeatCount);
                else
                {
                    oven->stopHumidification();
                    oven->repeatHumidification(nextStep.humidification, nextStep.humidificationRepeatDelay, nextStep.humidificationRepeatCount);
                }
            }
            else
            {
                if (currentStep.humidificationRepeatDelay)
                {
                    oven->stopRepeatHumidification();
                    oven->startHumidification(nextStep.humidification);
                }
                else
                    oven->startHumidification(nextStep.humidification);
            }
        }

        if (checkingCoreTemp && currentStepIndex + 2 >= cook.steps.size())
        {
            lastCoreTempIncreasedTime.start();
        }
    }
    else if (currentClass == Define::CookClass && nextClass == Define::DoorClass)
    {
        if (currentStep.dehumidification)
        {
            if (currentStep.dehumidificationRepeatDelay)
                oven->stopRepeatDamper();
            else
                oven->closeDamper();
        }

        if (currentStep.humidification)
        {
            if (currentStep.humidificationRepeatDelay)
                oven->stopRepeatHumidification();
            else
                oven->stopHumidification();
        }

        oven->setInterTempEnabled(false);
        oven->stopCooking();
        isWaitingDoorOpened_ = true;
    }

    currentStepIndex++;

    advance();
}

bool AutoCook::advance()
{
    Oven *oven = Oven::getInstance();

    CookStep &currentStep = cook.steps[currentStepIndex];
    switch (Define::classify(currentStep.type))
    {
    case Define::PreheatClass:
        if (oven->currentTemp() >= currentStep.temp)
        {
            nextStep();

            return true;
        }
        break;
    case Define::DoorClass:
        if (isWaitingDoorOpened_)
        {
            if (oven->door())
            {
                isWaitingDoorOpened_ = false;

                return true;
            }
        }
        else
        {
            if (!oven->door())
            {
                nextStep();

                return true;
            }
        }
        break;
    case Define::CookClass:
    {
        if (oven->door())
        {
            if (!doorOpened)
            {
                doorOpened = true;

                if (checkingCoreTemp && currentStepIndex + 1 >= cook.steps.size())
                {
                    currentStep.time -= lastCoreTempIncreasedTime.elapsed();
                }
                else
                {
                    currentStep.time -= stepStartTime.elapsed();
                }
            }

            return false;
        }
        else if (doorOpened)
        {
            doorOpened = false;
            stepStartTime.start();
            lastCoreTempIncreasedTime.start();
            lastCoreTempChangedTime.start();
        }

        int remainingCurrentStepTime = currentStep.time;
        if (checkingCoreTemp && currentStepIndex + 1 >= cook.steps.size())
            remainingCurrentStepTime -= lastCoreTempIncreasedTime.elapsed();
        else
            remainingCurrentStepTime -= stepStartTime.elapsed();

        if (remainingCurrentStepTime <= 0)
        {
            if (currentStepIndex + 1 < cook.steps.size())
            {
                nextStep();

                return true;
            }
            else
            {
                done_ = true;

                SoundPlayer::playStop();

                if (currentStep.dehumidification)
                {
                    if (currentStep.dehumidificationRepeatDelay)
                        oven->stopRepeatDamper();
                    else
                        oven->closeDamper();
                }

                if (currentStep.humidification)
                {
                    if (currentStep.humidificationRepeatDelay)
                        oven->stopRepeatHumidification();
                    else
                        oven->stopHumidification();
                }

                oven->stopCooking();

                return true;
            }
        }
        else if (cook.isCoreTempValid() && oven->isInterTempValid())
        {
            if (!checkingCoreTemp)
            {
                checkingCoreTemp = true;
                lastCoreTemp = oven->currentInterTemp();
                lastIncreasedCoreTemp = lastCoreTemp;
                lastCoreTempChangedTime.start();
                lastCoreTempIncreasedTime.start();
            }

            if (cook.coreTemp() != oven->interTemp())
                oven->setInterTemp(cook.coreTemp());

            if (!oven->interTempEnabled())
                oven->setInterTempEnabled(true);

            int currentCoreTemp = oven->currentInterTemp();
            if (currentCoreTemp >= cook.coreTemp())
            {
                done_ = true;

                SoundPlayer::playStop();

                if (currentStep.dehumidification)
                {
                    if (currentStep.dehumidificationRepeatDelay)
                        oven->stopRepeatDamper();
                    else
                        oven->closeDamper();
                }

                if (currentStep.humidification)
                {
                    if (currentStep.humidificationRepeatDelay)
                        oven->stopRepeatHumidification();
                    else
                        oven->stopHumidification();
                }

                oven->stopCooking();

                currentStepIndex = cook.steps.size() - 1;

                return true;
            }
            else if (currentCoreTemp != lastCoreTemp)
            {
                if (currentCoreTemp > lastIncreasedCoreTemp)
                {
                    if (currentStepIndex + 1 < cook.steps.size())
                    {
                        int otherStepsTime = 0;
                        for (int idx = currentStepIndex; idx + 1 < cook.steps.size(); idx++)
                            otherStepsTime += cook.steps[idx].time;

                        otherStepsTime -= stepStartTime.elapsed();

                        int expectedRemainingTime = (cook.coreTemp() - currentCoreTemp) * lastCoreTempChangedTime.elapsed() / (currentCoreTemp - lastCoreTemp);
                        int expectedLastStepRemainingTime = expectedRemainingTime - otherStepsTime;

                        CookStep &lastStep = cook.steps[cook.steps.size() - 1];
                        lastStep.time = qMax(expectedLastStepRemainingTime, 30000);

                        lastIncreasedCoreTemp = currentCoreTemp;
                        lastCoreTempIncreasedTime.start();
                    }
                    else
                    {
                        CookStep &lastStep = cook.steps[cook.steps.size() - 1];

                        int expectedRemainingTime = (cook.coreTemp() - currentCoreTemp) * lastCoreTempChangedTime.elapsed() / (currentCoreTemp - lastCoreTemp);
                        if (expectedRemainingTime > 30000)
                        {
                            lastStep.time = expectedRemainingTime;
                            lastCoreTempIncreasedTime.start();
                        }
                        else
                        {
                            int currentRemainingTime = lastStep.time - lastCoreTempIncreasedTime.elapsed();
                            if (currentRemainingTime > 30000)
                            {
                                lastStep.time = 30000;
                                lastCoreTempIncreasedTime.start();
                            }
                            else if (expectedRemainingTime > currentRemainingTime)
                            {
                                lastStep.time = expectedRemainingTime;
                                lastCoreTempIncreasedTime.start();
                            }
                        }
                    }
                }

                lastCoreTemp = currentCoreTemp;
                lastCoreTempChangedTime.start();
            }
        }
        else if (oven->interTempEnabled())
        {
            oven->setInterTempEnabled(false);
        }
    }
    case Define::InvalidClass:
            break;
    }

    return false;
}

int AutoCook::remainingTime()
{
    if (done_)
    {
        return 0;
    }
    else if (checkingCoreTemp && currentStepIndex + 1 >= cook.steps.size())
    {
        CookStep &step = cook.steps[cook.steps.size() - 1];
        int t = step.time;
        if (!doorOpened)
            t -= lastCoreTempIncreasedTime.elapsed();

        return t / 1000;
    }
    else
    {
        int t = 0;
        for (int i = currentStepIndex; i < cook.steps.size(); i++)
            t += cook.steps[i].time;

        if (!doorOpened && Define::classify(cook.steps[currentStepIndex].type) == Define::CookClass)
            t -= stepStartTime.elapsed();

        return t / 1000;
    }
}

int AutoCook::msecs()
{
    if (done_)
    {
        return 0;
    }
    else if (checkingCoreTemp && currentStepIndex + 1 >= cook.steps.size())
    {
        CookStep &step = cook.steps[cook.steps.size() - 1];
        int t = step.time;
        if (!doorOpened)
            t -= lastCoreTempIncreasedTime.elapsed();

        return t;
    }
    else
    {
        int t = 0;
        for (int i = currentStepIndex; i < cook.steps.size(); i++)
            t += cook.steps[i].time;

        if (!doorOpened && Define::classify(cook.steps[currentStepIndex].type) == Define::CookClass)
            t -= stepStartTime.elapsed();

        return t;
    }
}