/***************************************************************************
   This file is part of LinTV
   lintvprogram.cpp
   A class which allows for adding, editing, removing, or updating schedules
   from the both the scheduler daemon (atd) and the LinTV configuration.

   Author: Jesse L. Zamora <xtremek2009@gmail.com>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
 ***************************************************************************/
 
#include "lintvprogram.h"
#include "lintvprocess.h"

#include <KConfig>
#include <KConfigGroup>
#include <kdebug.h>
#include <KTemporaryFile>

#include <QString>
#include <QStringList>

LinTVProgramList LinTVProgramHandler::m_programList;
bool LinTVProgramHandler::m_initialized = false;
QString LinTVProgramHandler::m_dateTimeFormat = "hh':'mm_MM'/'dd'/'yyyy";
QString LinTVProgramHandler::m_dateFormat = "MM'/'dd'/'yy";

LinTVProgramHandler::LinTVProgramHandler()
{
    m_programsConfig = new KConfig("lintvprogramsrc", KConfig::NoGlobals);
    init();
}

void LinTVProgramHandler::init()
{
    if (!m_initialized) {
        m_initialized = true;
    } else {
        return;
    }

    m_programList.clear();

    // Load all the jobs from the lintvprogramsrc file...
    KConfig config("lintvprogramsrc", KConfig::NoGlobals);
    QStringList programsList = config.groupList();

    for (int i = 0; i < programsList.size(); i++) {
        KConfigGroup group(&config, programsList[i]);
        LinTVProgram program;

        program.name = programsList[i];
        program.type = toType(group.readEntry("Type", QString()));
        
        QString timeStr = group.readEntry("StartTime", QString());
        program.startDateTime = QDateTime::fromString(timeStr, dateTimeFormat());
        timeStr = group.readEntry("EndTime", QString());
        program.endDateTime = QDateTime::fromString(timeStr, dateTimeFormat());

        program.daysToRun = group.readEntry("DaysToRun", QStringList());

        program.device = group.readEntry("Device", QString());
        program.input = group.readEntry("Input", 0);
        program.channel = group.readEntry("Channel", 3);
        program.recNumber = group.readEntry("RecNumber", 0);
        program.status = toStatus(group.readEntry("Status", QString()));

        m_programList << program;
    }
}

LinTVProgramHandler::~LinTVProgramHandler()
{
    delete m_programsConfig;
}

LinTVProgram LinTVProgramHandler::createProgram(QString name, LinTVProgram::Type type, 
                                     QDateTime startTime, QDateTime endTime, 
                                     QStringList daysToRun, QString device, 
                                     int input, int channel)
{

    LinTVProgram program;

    program.name = name;
    program.type = type;
    program.startDateTime = startTime;
    program.endDateTime = endTime;
    program.daysToRun = daysToRun;
    program.device = device;
    program.input = input;
    program.channel = channel;
    program.status = LinTVProgram::Queued;

    return program;
}

void LinTVProgramHandler::addProgram(QString name, LinTVProgram::Type type, 
                                     QDateTime startTime, QDateTime endTime, 
                                     QStringList daysToRun, QString device, 
                                     int input, int channel)
{
    addProgram(createProgram(name, type, startTime, endTime, daysToRun, 
                             device, input, channel));
}

void LinTVProgramHandler::addProgram(const LinTVProgram &program)
{
    LinTVProgram prog = program;

    // Add the program to the programs list...
    m_programList << prog;

    writeProgramConfig(prog);

    saveJobs();
}

void LinTVProgramHandler::updateProgram(QString origName, const LinTVProgram &program)
{

    LinTVProgram prog = program;

    // Now let's find which job we're saving...
    int jobIndex = 0;
    for (int i = 0; i < m_programList.size(); i++)
    {
        if (origName == m_programList[i].name) {
            jobIndex = i;
            break;
        }
    }

    m_programList[jobIndex] = prog;
    updateConfigProgram(origName, prog);

    saveJobs();
}

void LinTVProgramHandler::removeProgram(const LinTVProgram &program)
{
    removeConfigProgram(program.name);

    for (int i = 0; i < m_programList.size(); i++) {
        if (program.name == m_programList[i].name) {
            m_programList.removeAt(i);
            break;
        }
    }

    saveJobs();
}

void LinTVProgramHandler::updateConfigProgram(QString origName, const LinTVProgram &program)
{
    if (origName != program.name) {
        KConfigGroup oldGroup(m_programsConfig, origName);
        oldGroup.deleteGroup();
    }

    writeProgramConfig(program);
}

void LinTVProgramHandler::removeConfigProgram(QString name)
{
    KConfigGroup group(m_programsConfig, name);
    group.deleteGroup();

    m_programsConfig->sync();
}

void LinTVProgramHandler::writeProgramConfig(const LinTVProgram & program)
{
    KConfigGroup group(m_programsConfig, program.name);

    // Save the new configuration...
    group.writeEntry("Type", toString(program.type));
    group.writeEntry("StartTime", program.startDateTime.toString(dateTimeFormat()));
    group.writeEntry("EndTime", program.endDateTime.toString(dateTimeFormat()));
    group.writeEntry("DaysToRun", program.daysToRun);
    group.writeEntry("Device", program.device);
    group.writeEntry("Input", program.input);
    group.writeEntry("Channel", program.channel);
    group.writeEntry("RecNumber", program.recNumber);
    group.writeEntry("Status", toString(program.status));
    m_programsConfig->sync();
}


LinTVProgram LinTVProgramHandler::findProgram(const QString & name)
{
    init();

    for (int i = 0; i < m_programList.size(); i++) {
        if (name == m_programList[i].name) {
            return m_programList[i];
        }
    }

    return LinTVProgram();
}

void LinTVProgramHandler::startedRecording(const LinTVProgram &program)
{
    LinTVProgram prog = program;

    prog.status = LinTVProgram::Recording;

    writeProgramConfig(prog);
}

void LinTVProgramHandler::stoppedRecording(const LinTVProgram &program)
{
    LinTVProgram prog = program;

    // If we've recorded once, this is it. We just remove
    // the schedule completely.
    if (prog.type == LinTVProgram::OneTimeOnly) {
        removeProgram(program);
        return;
    }
    prog.status = LinTVProgram::Queued;

    writeProgramConfig(prog);
}

QString LinTVProgramHandler::dayToString(int day)
{
    switch (day) {
    case 1:
        return PROGRAM_DAY_MONDAY;
    case 2:
        return PROGRAM_DAY_TUESDAY;
    case 3:
        return PROGRAM_DAY_WEDNESDAY;
    case 4:
        return PROGRAM_DAY_THURSDAY;
    case 5:
        return PROGRAM_DAY_FRIDAY;
    case 6:
        return PROGRAM_DAY_SATURDAY;
    case 7:
        return PROGRAM_DAY_SUNDAY;
    };
    return QString();
}

Qt::DayOfWeek LinTVProgramHandler::stringToDay(QString day)
{
    if (day == PROGRAM_DAY_SUNDAY) {
        return Qt::Sunday;
    } else if (day == PROGRAM_DAY_MONDAY) {
        return Qt::Monday;
    } else if (day == PROGRAM_DAY_TUESDAY) {
        return Qt::Tuesday;
    } else if (day == PROGRAM_DAY_WEDNESDAY) {
        return Qt::Wednesday;
    } else if (day == PROGRAM_DAY_THURSDAY) {
        return Qt::Thursday;
    } else if (day == PROGRAM_DAY_FRIDAY) {
        return Qt::Friday;
    } else if (day == PROGRAM_DAY_SATURDAY) {
        return Qt::Saturday;
    }
    return Qt::Monday;
}

QDateTime LinTVProgramHandler::nextRunTime(const LinTVProgram & program)
{
    if (program.type == LinTVProgram::OneTimeOnly) {
        return program.startDateTime;
    }

    QDateTime start = program.startDateTime;
    int count = 0;
    QDateTime current = QDateTime::currentDateTime();

    while (count < 1000000) {
        if (program.type == LinTVProgram::EveryWeek) {
            if (start >= current && start < program.endDateTime) {
                return start;
            }
        }

        if (program.daysToRun.contains(dayToString(start.date().dayOfWeek()))) {
            if (start >= QDateTime::currentDateTime() && start < program.endDateTime) {
                return start;
            }
        }

        if (program.type == LinTVProgram::EveryDay) {
            start = start.addDays(1);
        } else {
            start = start.addDays(7);
        }

        count++;
    }

    return program.startDateTime;
}

QString LinTVProgramHandler::toString(LinTVProgram::Status status)
{
    if (status == LinTVProgram::Queued) {
        return "Queued";
    } else if (status == LinTVProgram::Recording) {
        return "Recording";
    } /*else if (status == LinTVProgram::Canceled) {
        return "Canceled";
    } else if (status == LinTVProgram::Failed) {
        return "Failed";
    }*/

    return "Queued";
}

QString LinTVProgramHandler::toString(LinTVProgram::Type type)
{
    if (type == LinTVProgram::OneTimeOnly) {
        return "OneTimeOnly";
    } else if (type == LinTVProgram::EveryDay) {
        return "EveryDay";
    } else {
        return "EveryWeek";
    }

    return QString();
}

LinTVProgram::Status LinTVProgramHandler::toStatus(QString string)
{
    if (string == "Queued") {
        return LinTVProgram::Queued;
    } else if (string == "Recording") {
        return LinTVProgram::Recording;
    } /*else if (string == "Canceled") {
        return LinTVProgram::Canceled;
    } else if (string == "Failed") {
        return LinTVProgram::Failed;
    }*/

    return LinTVProgram::Queued;
}

LinTVProgram::Type LinTVProgramHandler::toType(QString string)
{
    if (string == "OneTimeOnly") {
        return LinTVProgram::OneTimeOnly;
    } else if (string == "EveryDay") {
        return LinTVProgram::EveryDay;
    } else if (string == "EveryWeek") {
        return LinTVProgram::EveryWeek;
    }

    return LinTVProgram::OneTimeOnly;
}

QString LinTVProgramHandler::createProgramStr(const LinTVProgram &program)
{
    int duration;
    QString str;

    duration = program.startDateTime.time().secsTo(program.endDateTime.time()) / 60;
    str = QString("lintv --record %1 --filename \"%2\" --device \"%3\" --input %4"
		  " --channel %5")
                                                .arg(duration).arg(program.name)
                                                .arg(program.device).arg(program.input)
						.arg(program.channel);
    kDebug() << str;

    return str;
}

void LinTVProgramHandler::saveJobs()
{
    QFile temp("lintv-crontab");

    if (!temp.open(QIODevice::WriteOnly)) {
        kDebug() << "There was an error when writing to the temporary file" << temp.fileName();
        kDebug() << "Please make sure you have write access to that directory!";
        return;
    }

    QTextStream stream(&temp);

    for (int i = 0; i < m_programList.size(); i++) {
        QString cronStr;

        LinTVProgram program = m_programList[i];

        QDateTime start = program.startDateTime;

        cronStr.append(QString("%1 %2 ").arg(start.time().minute())
                                        .arg(start.time().hour()));

        if (program.type == LinTVProgram::OneTimeOnly) {
            cronStr.append(QString("%1 %2 * ").arg(start.date().day())
                                              .arg(start.date().month()));
        } else {
            cronStr.append("* ");

            // Find the months to run
            QStringList months;
            QDate date = start.date();
            int count = 0;
            while (count < 1000000) {
                if (date > program.endDateTime.date()) {
                    break;
                }

                months << QString("%1").arg(date.month());
                count++;
                date = date.addMonths(1);
            }
            cronStr.append(QString("%1 ").arg(months.join(",")));

            if (program.daysToRun.size() == 7) {
                cronStr.append("* ");
            } else {
                cronStr.append(QString("%1 ").arg(program.daysToRun.join(",")));
            }
        }

        cronStr.append(createProgramStr(program));

        //kDebug() << cronStr;

        stream << QString("# LinTV program \"%1\"\n").arg(program.name)
               << cronStr << "\n";
    }

    temp.close();

    LinTVProcess proc;
    proc.setOutputChannelMode(KProcess::ForwardedChannels);
    proc << "crontab";
    proc << temp.fileName();
    proc.start();
    proc.waitForFinished();

    temp.remove();
}

#include "lintvprogram.moc"