#include "cookprogram.h"

#include <QTimer>

#include "unistd.h"

#include "cookbook.h"

namespace {
Define::CookType toCookType(QString type)
{
    if (type == "poultry")
        return Define::Poultry;
    if (type == "meat")
        return Define::Meat;
    if (type == "fish")
        return Define::Fish;
    if (type == "desert")
        return Define::Desert;
    if (type == "vegetable")
        return Define::Vegetable;
    if (type == "bread")
        return Define::Bread;
    if (type == "etc")
        return Define::Etc;

    return Define::InvalidCookType;
}

QString toString(Define::CookType type)
{
    switch (type)
    {
    case Define::Poultry:
        return "poultry";
    case Define::Meat:
        return "meat";
    case Define::Fish:
        return "fish";
    case Define::Desert:
        return "desert";
    case Define::Vegetable:
        return "vegetable";
    case Define::Bread:
        return "bread";
    case Define::Etc:
        return "etc";
    default:
        return QString();
    }
}

Define::Mode toMode(QString mode)
{
    if (mode == "steam")
        return Define::SteamMode;
    if (mode == "combi")
        return Define::CombiMode;
    if (mode == "dry")
        return Define::DryMode;

    return Define::InvalidMode;
}

QString toString(Define::Mode mode)
{
    switch (mode)
    {
    case Define::SteamMode:
        return "steam";
    case Define::CombiMode:
        return "combi";
    case Define::DryMode:
        return "dry";
    default:
        return QString();
    }
}

QString name(Define::CookType type, QString root)
{
    CookBook book(type);
    return book.name(root);
}
}

namespace {

const int maxAuto = 20;
const int maxManual = 20;

struct AutoEntry
{
    int id;
    QString name;
    Define::CookType type;
    QString root;
    int configs[5];

    bool operator==(const AutoEntry &other)
    {
        for (int i = 0; i < 5; i++)
            if (configs[i] != other.configs[i])
                return false;

        return id == other.id
                && name == other.name
                && type == other.type
                && root == other.root;
    }
};

struct ManualEntry
{
    int id;
    QString name;
    Define::Mode mode;
    int humidity;
    int temp;
    int time;
    int fan;
    int coreTemp;

    bool operator==(const ManualEntry &other)
    {
        return id == other.id
                && name == other.name
                && mode == other.mode
                && humidity == other.humidity
                && temp == other.temp
                && time == other.time
                && fan == other.fan
                && coreTemp == other.coreTemp;
    }
};

QList<AutoEntry> savedAutoList;
QList<ManualEntry> savedManualList;
QList<AutoEntry> currentAutoList;
QList<ManualEntry> currentManualList;

void readAuto()
{
    QFile file("/prime/program/auto.csv");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "File not found: " + file.fileName();
        return;
    }

    QString errorMessage = QString("%3: %1, line %2").arg(file.fileName());
    int lineCount = 0;
    while (!file.atEnd())
    {
        lineCount++;

        QString line = QString::fromUtf8(file.readLine()).trimmed();
        if (line.isEmpty())
            continue;

        QString error = errorMessage.arg(lineCount);

        QString id = line.section(',', 0, 0).trimmed();
        if (id.isEmpty())
        {
            qDebug() << error.arg("Invalid id");
            continue;
        }

        QString name = line.section(',', 1, 1).trimmed();
        if (name.isEmpty())
        {
            qDebug() << error.arg("Invalid name");
            continue;
        }

        QString type = line.section(',', 2, 2).trimmed();
        if (type.isEmpty())
        {
            qDebug() << error.arg("Invalid type");
            continue;
        }

        QString root = line.section(',', 3, 3).trimmed();
        if (root.isEmpty())
        {
            qDebug() << error.arg("Invalid root");
            continue;
        }

        QString config1 = line.section(',', 4, 4).trimmed();
        if (config1.isEmpty())
        {
            qDebug() << error.arg("Invalid config1");
            continue;
        }

        QString config2 = line.section(',', 5, 5).trimmed();
        if (config2.isEmpty())
        {
            qDebug() << error.arg("Invalid config2");
            continue;
        }

        QString config3 = line.section(',', 6, 6).trimmed();
        if (config3.isEmpty())
        {
            qDebug() << error.arg("Invalid config3");
            continue;
        }

        QString config4 = line.section(',', 7, 7).trimmed();
        if (config4.isEmpty())
        {
            qDebug() << error.arg("Invalid config4");
            continue;
        }

        QString config5 = line.section(',', 8, 8).trimmed();
        if (config5.isEmpty())
        {
            qDebug() << error.arg("Invalid config5");
            continue;
        }

        AutoEntry e;

        bool ok;
        e.id = id.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid id");
            continue;
        }

        e.name = name.replace("\\c", ",");

        e.type = toCookType(type);
        if (e.type == Define::InvalidCookType)
        {
            qDebug() << error.arg("Invalid type");
            continue;
        }

        e.root = root;

        e.configs[0] = config1.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid config1");
            continue;
        }

        e.configs[1] = config2.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid config2");
            continue;
        }

        e.configs[2] = config3.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid config3");
            continue;
        }

        e.configs[3] = config4.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid config4");
            continue;
        }

        e.configs[4] = config5.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid config5");
            continue;
        }

        savedAutoList.append(e);
    }

    currentAutoList = savedAutoList;
}

void readManual()
{
    QFile file("/prime/program/manual.csv");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "File not found: " + file.fileName();
        return;
    }

    QString errorMessage = QString("%3: %1, line %2").arg(file.fileName());
    int lineCount = 0;
    while (!file.atEnd())
    {
        lineCount++;

        QString line = QString::fromUtf8(file.readLine()).trimmed();
        if (line.isEmpty())
            continue;

        QString error = errorMessage.arg(lineCount);

        QString id = line.section(',', 0, 0).trimmed();
        if (id.isEmpty())
        {
            qDebug() << error.arg("Invalid id");
            continue;
        }

        QString name = line.section(',', 1, 1).trimmed();
        if (name.isEmpty())
        {
            qDebug() << error.arg("Invalid name");
            continue;
        }

        QString mode = line.section(',', 2, 2).trimmed();
        if (mode.isEmpty())
        {
            qDebug() << error.arg("Invalid mode");
            continue;
        }

        QString humidity = line.section(',', 3, 3).trimmed();
        if (humidity.isEmpty())
        {
            qDebug() << error.arg("Invalid humidity");
            continue;
        }

        QString temp = line.section(',', 4, 4).trimmed();
        if (temp.isEmpty())
        {
            qDebug() << error.arg("Invalid temp");
            continue;
        }

        QString time = line.section(',', 5, 5).trimmed();
        if (time.isEmpty())
        {
            qDebug() << error.arg("Invalid time");
            continue;
        }

        QString fan = line.section(',', 6, 6).trimmed();
        if (fan.isEmpty())
        {
            qDebug() << error.arg("Invalid fan");
            continue;
        }

        QString coreTemp = line.section(',', 7, 7).trimmed();
        if (coreTemp.isEmpty())
        {
            qDebug() << error.arg("Invalid coreTemp");
            continue;
        }

        ManualEntry e;

        bool ok;
        e.id = id.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid id");
            continue;
        }

        e.name = name.replace("\\c", ",");

        e.mode = toMode(mode);
        if (e.mode == Define::InvalidMode)
        {
            qDebug() << error.arg("Invalid mode");
            continue;
        }

        e.humidity = humidity.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid humidity");
            continue;
        }

        e.temp = temp.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid temp");
            continue;
        }

        e.time = time.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid time");
            continue;
        }

        e.fan = fan.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid fan");
            continue;
        }

        e.coreTemp = coreTemp.toInt(&ok);
        if (!ok)
        {
            qDebug() << error.arg("Invalid coreTemp");
            continue;
        }

        savedManualList.append(e);
    }

    currentManualList = savedManualList;
}

void writeAuto()
{
    QFile file("/prime/program/auto.csv");
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        qDebug() << "File not opened: " + file.fileName();
        return;
    }

    QTextStream stream(&file);
    stream.setCodec("UTF-8");
    foreach (AutoEntry e, savedAutoList)
    {
        stream << e.id << ","
               << e.name.replace(",", "\\c").toUtf8() << ","
               << toString(e.type) << ","
               << e.root;
        for (int i = 0; i < 5; i++)
            stream << "," << e.configs[i];

        stream << "\n";
    }

    file.close();
    sync();
}

void writeManual()
{
    QFile file("/prime/program/manual.csv");
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        qDebug() << "File not opened: " + file.fileName();
        return;
    }

    QTextStream stream(&file);
    stream.setCodec("UTF-8");
    foreach (ManualEntry e, savedManualList)
    {
        stream << e.id << ","
               << e.name.replace(",", "\\c").toUtf8() << ","
               << toString(e.mode) << ","
               << e.humidity << ","
               << e.temp << ","
               << e.time << ","
               << e.fan << ","
               << e.coreTemp << "\n";
    }

    stream.flush();
    file.close();
    sync();
}

bool initialized = false;
void initialize()
{
    initialized = true;

    readAuto();
    readManual();
}

void checkInitialized()
{
    if (!initialized)
        initialize();
}

int newAutoId()
{
    for (int i = 0; i < 1000; i++)
    {
        bool absent = true;
        foreach (AutoEntry e, currentAutoList)
            if (e.id == i)
            {
                absent = false;
                break;
            }

        if (absent)
            return i;
    }

    return -1;
}

int newManualId()
{
    for (int i = 0; i < 1000; i++)
    {
        bool absent = true;
        foreach (ManualEntry e, currentManualList)
            if (e.id == i)
            {
                absent = false;
                break;
            }

        if (absent)
            return i;
    }

    return -1;
}
}

void CookProgram::add(AutoCookSetting cook)
{
    checkInitialized();

    AutoEntry e;
    e.id = newAutoId();
    e.name = cook.name;
    e.type = cook.type;
    e.root = cook.root;

    for (int i = 0; i < 5; i++)
        e.configs[i] = cook.configs[i];

    currentAutoList.append(e);
}

void CookProgram::add(ManualCookSetting cook)
{
    checkInitialized();

    ManualEntry e;
    e.id = newManualId();
    e.name = "Manual";
    e.mode = cook.mode;
    e.humidity = cook.humidity;
    e.temp = cook.temp;
    e.time = cook.time;
    e.fan = cook.fan;
    e.coreTemp = cook.coreTempEnabled ? cook.coreTemp : -1;

    currentManualList.append(e);
}

void CookProgram::remove(CookRecord record)
{
    checkInitialized();

    switch (record.type)
    {
    case CookRecord::Auto:
    {
        AutoEntry e;
        e.id = record.id;
        e.name = record.name;
        e.type = record.autoRecord.setting.type;
        e.root = record.autoRecord.setting.root;
        for (int i = 0; i < 5; i++)
            e.configs[i] = record.autoRecord.setting.configs[i];

        currentAutoList.removeAll(e);
    }
        break;
    case CookRecord::Manual:
    {
        ManualEntry e;
        e.id = record.id;
        e.name = record.name;
        e.mode = record.manualRecord.setting.mode;
        e.humidity = record.manualRecord.setting.humidity;
        e.temp = record.manualRecord.setting.temp;
        e.time = record.manualRecord.setting.time;
        e.fan = record.manualRecord.setting.fan;
        e.coreTemp = record.manualRecord.setting.coreTempEnabled ? record.manualRecord.setting.coreTemp : -1;

        currentManualList.removeAll(e);
    }
        break;
    }
}

QList<CookRecord> CookProgram::listAuto()
{
    checkInitialized();

    QList<CookRecord> list;
    foreach (AutoEntry e, currentAutoList)
    {
        CookRecord r;
        r.type = CookRecord::Auto;
        r.id = e.id;
        r.name = e.name;
        r.autoRecord.setting.name = name(e.type, e.root);
        r.autoRecord.setting.type = e.type;
        r.autoRecord.setting.root = e.root;

        for (int i = 0; i < 5; i++)
            r.autoRecord.setting.configs[i] = e.configs[i];

        list.append(r);
    }

    return list;
}

QList<CookRecord> CookProgram::listManual()
{
    checkInitialized();

    QList<CookRecord> list;
    foreach (ManualEntry e, currentManualList)
    {
        CookRecord r;
        r.type = CookRecord::Manual;
        r.id = e.id;
        r.name = e.name;
        r.manualRecord.setting.mode = e.mode;
        r.manualRecord.setting.humidity = e.humidity;
        r.manualRecord.setting.temp = e.temp;
        r.manualRecord.setting.time = e.time;
        r.manualRecord.setting.fan = e.fan;
        r.manualRecord.setting.coreTempEnabled = e.coreTemp > 0;
        r.manualRecord.setting.coreTemp = e.coreTemp;

        list.append(r);
    }

    return list;
}

void CookProgram::rename(CookRecord record, QString name)
{
    checkInitialized();

    switch (record.type)
    {
    case CookRecord::Auto:
    {
        AutoEntry e;
        e.id = record.id;
        e.name = record.name;
        e.type = record.autoRecord.setting.type;
        e.root = record.autoRecord.setting.root;
        for (int i = 0; i < 5; i++)
            e.configs[i] = record.autoRecord.setting.configs[i];

        int index = currentAutoList.indexOf(e);
        if (index != -1)
        {
            AutoEntry &e = currentAutoList[index];
            e.name = name;
        }
    }
        break;
    case CookRecord::Manual:
    {
        ManualEntry e;
        e.id = record.id;
        e.name = record.name;
        e.mode = record.manualRecord.setting.mode;
        e.humidity = record.manualRecord.setting.humidity;
        e.temp = record.manualRecord.setting.temp;
        e.time = record.manualRecord.setting.time;
        e.fan = record.manualRecord.setting.fan;
        e.coreTemp = record.manualRecord.setting.coreTempEnabled ? record.manualRecord.setting.coreTemp : -1;

        int index = currentManualList.indexOf(e);
        if (index != -1)
        {
            ManualEntry &e = currentManualList[index];
            e.name = name;
        }
    }
        break;
    }
}

void CookProgram::save()
{
    checkInitialized();

    if (currentAutoList != savedAutoList)
    {
        savedAutoList = currentAutoList;
        writeAuto();
    }

    if (currentManualList != savedManualList)
    {
        savedManualList = currentManualList;
        writeManual();
    }
}

void CookProgram::discard()
{
    checkInitialized();

    if (isModified())
    {
        currentAutoList = savedAutoList;
        currentManualList = savedManualList;
    }
}

bool CookProgram::isModified()
{
    return currentAutoList != savedAutoList || currentManualList != savedManualList;
}