/***************************************************************************
   This file is part of LinTV
   lintv.cpp
   Called by the main function to either record with or without a GUI or
   just simply display the LinTV main window & start playing video.

   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.
 ***************************************************************************/

/* LinTV Includes */
#include "lintv.h"
#include "lintvview.h"
#include "lintvplay.h"
#include "lintvdeviceinfo.h"
#include "lintvscreenshotwidgets.h"
#include "lintvtools.h"
#include "lintvsettings.h"
#include "lintvcolordialog.h"
#include "lintvdeviceinfodialog.h"
#include "lintvstatuswidget.h"
#include "lintvmixer.h"
#include "lintvadaptor.h"

/* KDE Includes */
#include <ksystemtrayicon.h>
#include <kmenu.h>
#include <kaction.h>
#include <knotification.h>
#include <kmessagebox.h>
#include <krun.h>
#include <kapplication.h>
#include <kstandarddirs.h>

/* Qt Includes */
#include <QTimer>
#include <QFileInfo> 
#include <QSystemTrayIcon>

bool LinTV::m_initDeviceInfoDialog = false;
bool LinTV::m_initGui = false;
bool LinTV::m_initLinTVPlay = false;

LinTV::LinTV()
{
    m_hasGui = true;
    m_recordSchedule = false;

    // Intialize the GUI
    initializeGui();
    
    loadCommon();

    // No devices, no init backend...
    if (m_numOfDev > 0) {
        // Initialize the LinTVPlay and LinTVRecord backends
        initializeBackend(m_devices[0].deviceNode, LinTVSettings::lastInput(), 3);
    }

    m_reloadLinTVPlay = true;

    initializeLinTVPlay();
    initializeLinTVRecord();

    m_view->resize(LinTVSettings::width(), LinTVSettings::height());
    m_view->show();

    if (m_numOfDev > 0) {
        m_lintvPlay->start();
    }
}

LinTV::LinTV(bool hasGui, const QString & device, int input, int channel, int minutes,
                                                const QString & filename)
{
    m_hasGui = hasGui;

    loadCommon();


    // Initialize the LinTVPlay and LinTVRecord backends
    initializeBackend(device, input, channel);
    initializeLinTVRecord();

    m_lintvRecord->setRecordTime(minutes);

    m_exitWhenDone = true;
    m_recordSchedule = true;
    m_program = updateProgramStatus(device, input, channel, filename);

    if (m_hasGui) {
        QTime time = QTime::currentTime();
        m_tray = new QSystemTrayIcon(KSystemTrayIcon::loadIcon("record"), this);
                
        KMenu *menu = new KMenu();

        m_toggleLinTVAction = new KAction(KIcon("lintv"),
                                      i18n("Show LinTV"),
                                      this);
        menu->addAction(m_toggleLinTVAction);
        connect(m_toggleLinTVAction, SIGNAL(triggered()), this, SLOT(slotToggleLinTV()));

        KAction *action = new KAction(KIcon("lintvscheduler"),
                                    i18n("Open LinTV Scheduler"),
                                    this);

        menu->addAction(action);
        connect(action, SIGNAL(triggered()), this, SLOT(slotOpenLinTVScheduler()));

        menu->addSeparator();
        action = new KAction(KIcon("dialog-cancel"),
                              i18n("Cancel Recording"),
                              this);

        menu->addAction(action);
        connect(action, SIGNAL(triggered()), this, SLOT(cancelRecording()));
        m_tray->setContextMenu(menu);

        m_tray->setToolTip(i18n("LinTV is currently recording \"%1\" to file: \"%2\"", filename,
                                                                      m_lintvRecord->filename()));

        m_tray->show();

        if (m_initGui) {
            m_view->setRecordButton(true);
            m_view->hide();
        }

        KNotification::event("StartedRecording", i18n("Started Recording \"%1\" at %2", filename, 
                                                      time.toString("h':'mm A")),
                                 QPixmap(), 0, KNotification::CloseOnTimeout);

        // Make sure the volume is not muted
        LinTVMixer mixer(LinTVSettings::audioInput(), 80, this);
        if (LinTVSettings::muted()) {
            mixer.mute();
        } else {
            mixer.unmute();
        }
    }

    m_lintvRecord->start();
}

LinTV::~LinTV()
{
    if (m_initLinTVPlay) {
        m_lintvPlay->stop();
        m_lintvPlay->muteVolume(true);
        delete m_lintvPlay;
    }

    if (m_hasGui && m_initGui) {
        m_view->saveSettings();
    }

    LinTVSettings::self()->writeConfig();

    m_lintvRecord->stop();
    delete m_lintvRecord;
}

LinTVProgram LinTV::updateProgramStatus(const QString & device, int input, int channel,
                                                      const QString & programName)
{
    LinTVProgram program;

    program = LinTVProgramHandler::findProgram(programName);
 
    // If the returned program name doesn't match programName, that means
    // there isn't any scheduler for the program.
    if (program.name == programName) {

        // One more check; if the date is before the start time
        // or after the end time, don't record...
        if (QDate::currentDate() < program.startDateTime.date()) {
            ::exit(0);
        } else if (QDate::currentDate() > program.endDateTime.date()) {
            // Remove the schedule & quit if we're done recording it.
            programHandler.removeProgram(program);
            ::exit(0);
        }
        m_hasSchedule = true;

    } else {
        // Create a new program without a schedule
        program.name = programName;
        program.input = input;
        program.channel = channel;
        program.device = device;
        program.type = LinTVProgram::OneTimeOnly;

        m_hasSchedule = false;
    }

    // Check to make sure the file doesn't exist! We NEVER want to overwrite any
    // previous recordings!
    int start;
    if (program.type == LinTVProgram::OneTimeOnly) {
        start = 1;
    } else {
        start = program.recNumber;
    }

    QString filename = programName;

    QString str = filename;
    if (program.recNumber > 0 && program.recNumber < 100000 
        && program.type != LinTVProgram::OneTimeOnly) {
        str.append(QString("_%1").arg(program.recNumber, 3, 10, QChar('0')));
    }

    m_lintvRecord->setFilename(str);

    QFile file(m_lintvRecord->filename());
    if (file.exists()) {
        for (int i = start; i < 2000; i++) {
            str = filename;
            str.append(QString("_%1").arg(i, 3, 10, QChar('0')));
            m_lintvRecord->setFilename(str);
            file.setFileName(m_lintvRecord->filename());
            if (!file.exists()) {
                filename = str;
                break;
            }
        }
    }

    return program;
}

void LinTV::loadCommon()
{
    new LinTVAdaptor( this );
    QDBusConnection::sessionBus().registerObject( "/LinTV" , this );


    // tell the KXmlGuiWindow that this is indeed the main widget
    m_exitWhenDone = false;
    m_recordSchedule = false;
    m_reloadLinTVPlay = false;
    m_channel = 3;
    m_input = 0;

    // Check to see if we have the necessary programs to run LinTV.
    // If not, we have to print it out and log it to a file!
    if (!LinTVTools::checkProgram("mplayer", false)
        || !LinTVTools::checkProgram("mencoder", false)) {
        QString errorMsg = i18n("Mplayer or mencoder executable could not be found.\n"
                                "You must install Mplayer and mencoder in order to use LinTV.");
        if (m_hasGui) {
            KMessageBox::error(0L, errorMsg, i18n("Missing Dependencies"));
        }
        ::exit(255);
    }

    // First, detect all the devices in the system
    detectDevices();
}

void LinTV::detectDevices()
{
    LinTVDeviceInfo deviceInfo;
    m_devices = deviceInfo.detectDevices();
    m_numOfDev = m_devices.size();

    if (m_numOfDev == 0) {
        if (m_hasGui) {
            QWidget *parent;
            if (m_initGui) {
                parent = m_view;
            } else {
                parent = 0L;
            }
            m_view->statusWidget()->setStatus(LinTV::NoDevices);
            /*KMessageBox::error(parent, i18n("No devices were detected."),
                                       i18n("No devices."));*/
        }
    }
}

void LinTV::initializeGui()
{
    // Don't intialize the GUI if KApplication isn't using a GUI.
    if (!m_hasGui) {
        return;
    }

    QStringList devicesList;
    for (int i = 0; i < m_devices.size(); i++) {
        devicesList << m_devices[i].deviceName;
    }
          
    m_view = new LinTVView(devicesList);

    if (m_recordSchedule) {
        m_view->setRecordButton(true);
        m_view->setRecordMode(true);
        m_view->statusWidget()->setStatus(LinTV::Recording);
    } else {
        m_view->setRecordButton(false);
        m_view->setRecordMode(false);

        if (m_numOfDev > 0) {
            m_view->statusWidget()->setStatus(LinTV::NotPlaying);
        } else {
            m_view->statusWidget()->setStatus(LinTV::NoDevices);
        }

        m_view->statusWidget()->resetTimer();
    }
  
    m_colorDialog = new LinTVColorDialog(m_view);

    connect(m_view, SIGNAL(showColorDialog()),
            this, SLOT(slotShowColorDialog()));
    connect(m_view, SIGNAL(showDeviceInfoDialog()),
            this, SLOT(slotShowDeviceInfoDialog()));
    connect(m_view, SIGNAL(settingsChanged()),
            this, SLOT(slotSettingsChanged()));

    connect(m_view, SIGNAL(exitEvent()),
            this, SLOT(exitEvent()));

    if (m_view->statusWidget()->status() == LinTV::NoDevices) {
        m_view->videoOutputWidget()->setScaledContents(true);

        QPixmap background(KStandardDirs::locate("data", "lintv/pics/background.png"));
        m_view->videoOutputWidget()->setPixmap(background);
    }

    m_initGui = true;
}

void LinTV::initializeBackend(const QString & device, int input, int channel)
{
    QString deviceStr;
    m_curDev = 0;

    // If the device is not the device node, find the correct one...
    if (device.startsWith("/dev")) {
        deviceStr = device;
    } else {
        deviceStr = "/dev/video0";
        for (int i = 0; i < m_devices.size(); i++) {
            if (device == m_devices[i].deviceName) {
                deviceStr = m_devices[i].deviceNode;
                m_curDev = i;
                break;
            }
        }
    }

    m_input = input;
    m_channel = channel;
}

void LinTV::initializeLinTVPlay()
{
    // Currrently we need a GUI to use LinTVPlay...
    if (!m_hasGui) {
        return;
    }

    // We need a GUI to start displaying video in the first place anyway!
    if (!m_initGui) {
        initializeGui();
    }

    if (m_numOfDev != 0) {
        LinTVDevice device = m_devices[m_curDev];
        LinTVDeviceInfo::DeviceType type;
        type = LinTVDeviceInfo::deviceType(device);

        m_lintvPlay = new LinTVPlay(m_view->videoOutputWidget(), 
                                    type, device.deviceNode, 
                                    m_input);

        // Get the volume into the UI & backend
        m_lintvPlay->setVolume(LinTVSettings::volume());


        // Load the mute state into the UI & backend
        m_lintvPlay->muteVolume(LinTVSettings::muted());
        m_lintvPlay->setChannel(m_channel);
        connect(m_lintvPlay, SIGNAL(startedPlayback()),
                this, SLOT(slotStartedPlaying()));
        connect(m_lintvPlay, SIGNAL(finished(int, QProcess::ExitStatus)),
                this, SLOT(slotLinTVPlayExited(int, QProcess::ExitStatus)));

        m_view->setChannel(m_channel);
        m_view->setVolume(LinTVSettings::volume());

        m_view->setInputs(device.deviceInputs);
        
        // Load the current input...
        m_view->setInput(m_input);
        m_view->statusWidget()->setInput(device.deviceInputs[m_input]);

        m_view->setDeviceIndex(m_curDev);

        m_view->setMute(LinTVSettings::muted());
        connect(m_view, SIGNAL(volumeChanged(int )),
                m_lintvPlay, SLOT(setVolume(int )));
        connect(m_view, SIGNAL(muteToggled(bool )),
                m_lintvPlay, SLOT(muteVolume(bool )));
        connect(m_view, SIGNAL(pauseToggled(bool )),
                this, SLOT(slotPauseVideo(bool )));

        connect(m_view, SIGNAL(channelUp()),
                this, SLOT(slotChannelUp()));
        connect(m_view, SIGNAL(channelDown()),
                this, SLOT(slotChannelDown()));

        connect(m_view, SIGNAL(inputChanged(int )),
                this, SLOT(slotSetInput(int )));

        connect(m_colorDialog, SIGNAL(brightnessChanged(int )),
                m_lintvPlay, SLOT(setBrightness(int )));
        connect(m_colorDialog, SIGNAL(contrastChanged(int )),
                m_lintvPlay, SLOT(setContrast(int )));
        connect(m_colorDialog, SIGNAL(hueChanged(int )),
                m_lintvPlay, SLOT(setHue(int )));
        connect(m_colorDialog, SIGNAL(saturationChanged(int )),
                m_lintvPlay, SLOT(setSaturation(int )));
                
        connect(m_view, SIGNAL(startRecordingToggled(bool )),
                this, SLOT(slotSetStartRecording(bool )));

    } else {
        m_lintvPlay = new LinTVPlay(m_view->videoOutputWidget());
    }

    m_initLinTVPlay = true;
}

void LinTV::initializeLinTVRecord()
{
    if (m_numOfDev != 0) {
        LinTVDevice device = m_devices[m_curDev];
        LinTVDeviceInfo::DeviceType type;
        type = LinTVDeviceInfo::deviceType(device);

        // Initialize LinTVRecord
        m_lintvRecord = new LinTVRecord(type, device.deviceNode, m_input);

        // Load the default channel (for now)
        // TODO: Save the last channel and reload at startup!!
        m_lintvRecord->setChannel(m_channel);
        m_lintvRecord->setInput(m_input);

        connect(m_lintvRecord, SIGNAL(startedRecording()),
                this, SLOT(slotStartedRecording()));
        connect(m_lintvRecord, SIGNAL(finished(int, QProcess::ExitStatus)),
                this, SLOT(slotLinTVRecordExited(int, QProcess::ExitStatus)));
    } else {
        m_lintvRecord = new LinTVRecord(QString());
    }
}

void LinTV::slotChannelUp()
{
    m_channel++;
    m_lintvPlay->setChannel(m_channel);
    m_view->setChannel(m_channel);

    if (!m_lintvRecord->isRecording()) {
        m_lintvRecord->setChannel(m_channel);
    }
}

void LinTV::slotChannelDown()
{
    m_channel--;
    m_lintvPlay->setChannel(m_channel);
    m_view->setChannel(m_channel);

    if (!m_lintvRecord->isRecording()) {
        m_lintvRecord->setChannel(m_channel);
    }
}

int LinTV::selectDevice(QString deviceName)
{
    int device = -1;
    int i;

    for (i = 0; i < m_numOfDev; i++) {
        if (deviceName == m_devices[i].deviceName && i != m_curDev) {
            device = i;
            break;
        }
    }

    if (i == m_devices.size() && device == -1) {
        device = 0;
    }

    if (device == m_curDev) {
        return 0;
    }

    m_curDev = device;

    if (m_hasGui && m_initGui) {
        m_lintvPlay->stop();

        LinTVDeviceInfo::DeviceType type = LinTVDeviceInfo::deviceType(m_devices[m_curDev]);
        m_lintvPlay->setDeviceType(type);

        m_lintvPlay->setDeviceStr(m_devices[device].deviceNode);
        m_view->setInputs(m_devices[device].deviceInputs);
        m_lintvPlay->start();
    }

    return device;
}

void LinTV::slotSetInput(int input)
{
    if (input == m_lintvPlay->input()) {
        return;
    }

    m_lintvPlay->setInput(input);
    m_view->statusWidget()->resetTimer();

    if (m_hasGui && m_initGui) {
        m_view->statusWidget()->setInput(m_devices[m_curDev].deviceInputs[input]);
    }
}

/* Slots */
void LinTV::slotPauseVideo(bool pause)
{
    if (m_lintvRecord->isRecording()) {
        return;
    }

    if (!m_lintvPlay->isPlaying()) {
        return;
    }
    // Toggle the video pause...
    m_lintvPlay->pauseVideo();

    if (pause) {
        // We have to make sure the volume remains consistent....
        m_lintvPlay->muteVolume(true);

        m_view->statusWidget()->setStatus(LinTV::Paused);
    } else {
        if (!LinTVSettings::muted()) {
            m_lintvPlay->muteVolume(false);
        }

        m_view->statusWidget()->setStatus(LinTV::Playing);
    }
}

void LinTV::slotSetStartRecording(bool selected)
{
    if (selected) {
        m_reloadLinTVPlay = false;

        // Stop playing from the device
        m_lintvPlay->stop(true);

        if (!m_recordSchedule) {
            // Reset the file name
            m_lintvRecord->setFilename(QString());
        }


        // Start recording...
        m_lintvRecord->start();
        m_view->setRecordMode(true);
        m_view->statusWidget()->setStatus(LinTV::Recording);
        m_view->statusWidget()->resetTimer();

    } else {
        // If we were recording a schedule, we are canceling it...
        if (m_recordSchedule) {
            if (!cancelRecording()) {
                return;
            }
        }

        // Stop playing the video
        m_lintvPlay->stop(true);


        // Stop recording
        m_lintvRecord->stop();

        // Reset the device settings
        LinTVDevice device = m_devices[m_curDev];
        m_lintvPlay->setDeviceType(LinTVDeviceInfo::deviceType(device));
        m_lintvPlay->setDeviceStr(device.deviceNode);
        m_lintvPlay->start();

        m_view->setRecordMode(false);
        m_view->statusWidget()->setStatus(LinTV::NotPlaying);
        m_view->statusWidget()->resetTimer();
    }
}

void LinTV::slotAdjustColors()
{
    m_colorDialog->show();
}

void LinTV::slotOpenLinTVScheduler()
{
    QX11Info info;
    QWidget *parent;
    if (m_initGui) {
        parent = m_view;
    } else {
        parent = 0L;
    }
    KRun::runCommand(QString("lintvscheduler --display :%1").arg(info.appScreen()), parent);
}

bool LinTV::cancelRecording()
{
    int rc = KMessageBox::questionYesNo(0L, i18n("Are you sure you want to cancel"
                                                   " the current recording (\"%1\")?\n", 
                                                  m_program.name), 
                                                  i18n("Are you sure?"), 
                                                  KStandardGuiItem::yes(), 
                                                  KStandardGuiItem::no());

    if (rc == KMessageBox::Yes) {
        m_canceledRecord = true;
        m_lintvRecord->stop();
        return true;
    } else {
        return false;
    }
}

void LinTV::slotStartedPlaying()
{
    if (m_hasGui) {
        m_view->checkSize();
        if (!m_lintvRecord->isRecording()) {
            m_view->statusWidget()->setStatus(LinTV::Playing);
        }
    }
}

void LinTV::slotStartedRecording()
{
    // this should have lintv play the file that is currently recording...

    m_canceledRecord = false;

    if (m_hasGui && m_initGui) {
        m_lintvPlay->stop();
        m_lintvPlay->setDeviceType(LinTVDeviceInfo::File);
        m_lintvPlay->setDeviceStr(m_lintvRecord->filename());
        if (!m_recordSchedule) {
            m_lintvPlay->start();
        }

        m_view->setRecordMode(true);
        m_view->statusWidget()->setStatus(LinTV::Recording);
    }

    if (m_hasSchedule) {
        programHandler.startedRecording(m_program);

        refreshPrograms();
    }
}

void LinTV::slotToggleLinTV()
{
    // This toggles whether LinTV is shown or hidden. This 
    // assumes that the tray icon is shown....otherwise this
    // slot won't be called

    if (!m_initLinTVPlay) {
        initializeLinTVPlay();
        m_lintvPlay->setDeviceType(LinTVDeviceInfo::File);
        m_lintvPlay->setDeviceStr(m_lintvRecord->filename());
    }

    if (m_view->isVisible()) {
        m_view->hide();

        m_toggleLinTVAction->setText(i18n("Show LinTV"));
        m_lintvPlay->stop();
    } else {
        m_view->show();

        m_toggleLinTVAction->setText(i18n("Hide LinTV"));
        m_lintvPlay->start();
    }
}

void LinTV::slotSettingsChanged()
{
    if (m_lintvPlay->videoWidth() != LinTVSettings::videoWidth() ||
        m_lintvPlay->videoHeight() != LinTVSettings::videoHeight()) {
        kDebug() << LinTVSettings::videoWidth();
        kDebug() << LinTVSettings::videoHeight();
        m_lintvPlay->videoResolutionChanged();
    }

    if (m_lintvPlay->videoCropTop() != LinTVSettings::videoCropTop() ||
        m_lintvPlay->videoCropLeft() != LinTVSettings::videoCropLeft() ||
        m_lintvPlay->videoCropRight() != LinTVSettings::videoCropRight() ||
        m_lintvPlay->videoCropBottom() != LinTVSettings::videoCropBottom()) {
        m_lintvPlay->videoCropAreaChanged();
    }

    if (m_lintvPlay->videoStandard() != LinTVSettings::videoStandard()) {
        m_lintvPlay->videoStandardChanged();
    }

    m_view->screenshotWidget()->loadScreenshots();
}

void LinTV::slotLinTVPlayExited(int , QProcess::ExitStatus)
{
    if (m_hasGui && m_initLinTVPlay && !m_lintvRecord->isRecording() && m_reloadLinTVPlay) {
        m_view->statusWidget()->setStatus(LinTV::NotPlaying);

        m_lintvPlay->start();
    }
}

void LinTV::slotLinTVRecordExited(int, QProcess::ExitStatus)
{
    if (!m_recordSchedule) {
        return;
    }

    m_recordSchedule = false;

    // Reset the filename after completing a recording so if the user starts
    // recording again, it'll just create a new file...
    m_lintvRecord->setFilename(QString());

    programHandler.stoppedRecording(m_program);

    if (m_hasGui) {
        QTime time = QTime::currentTime();
        if (m_canceledRecord) {
            KNotification::event("CanceledRecording", i18n("User canceled \"%1\"", m_program.name), 
                                                   QPixmap(), 0, KNotification::CloseOnTimeout);
        } else {
            KNotification::event("FinishedRecording", i18n("Finished Recording \"%1\" at %2", 
                            m_program.name, time.toString("h':'mm A")), QPixmap(),
                                                      0, KNotification::CloseOnTimeout);
        }

        refreshPrograms();

        if (m_initGui) {
            m_view->setRecordButton(false);

            m_view->setRecordMode(false);
            m_view->statusWidget()->setStatus(LinTV::NotPlaying);
            m_tray->hide();
            if (m_view->isVisible()) {
                return;
            }
        }
    }

    if (m_exitWhenDone) {
        kapp->quit();
    }
}

void LinTV::refreshPrograms()
{
    QDBusConnection::sessionBus().send(QDBusMessage::createMethodCall("org.kde.lintvscheduler",
                                                  "/LinTVScheduler", "", "refreshPrograms"));
}

void LinTV::slotShowColorDialog()
{
    m_colorDialog->show();
}

void LinTV::slotShowDeviceInfoDialog()
{
    if (m_view->statusWidget()->status() == LinTV::NoDevices) {
        return;
    }

    if (!m_initDeviceInfoDialog) {
        m_deviceInfoDialog = new LinTVDeviceInfoDialog(m_devices, m_view);
        m_initDeviceInfoDialog = true;
    }

    m_deviceInfoDialog->show();
}

void LinTV::exitEvent()
{
    static int closeTries = 0;

    if (m_lintvRecord->isRecording()) {
        closeTries++;
    } else {
        m_reloadLinTVPlay = false;
        if (m_lintvPlay->isPlaying()) {
            m_lintvPlay->stop();
        }
        m_view->saveSettings();
        kapp->quit();
    }

    if (closeTries == 3) {
        KMessageBox::information(m_view, i18n("LinTV is currently recording: all recordings" 
                                            " must be stopped before exiting LinTV."),
                                       i18n("Stop recording before exiting"));
        closeTries = 0;
    }
}


#include "lintv.moc"
