/* vim: sw=4 et
 *
 * Copyright (C) 2002 George Staikos <staikos@kde.org>
 *               2003 Dirk Ziegelmeier <dziegel@gmx.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "kdetv.h"

#include <qapplication.h>
#include <qmessagebox.h>
#include <qtimer.h>
#include <qimage.h>
#include <qdatetime.h>

#include <dcopclient.h>
#include <dcopobject.h>
#include <kapplication.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kfiledialog.h>
#include <kaction.h>
#include <kxmlguifactory.h>

#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#include "pluginfactory.h"
#include "viewmanager.h"
#include "channel.h"
#include "channelstore.h"
#include "kdetvview.h"
#include "audiomanager.h"
#include "volumecontroller.h"
#include "sourcemanager.h"
#include "osdmanager.h"
#include "miscmanager.h"
#include "statusmanager.h"
#include "vbimanager.h"
#include "filtermanager.h"
#include "controlinterface.h"


Kdetv::Kdetv( QObject *parent, const char *name)
    : DCOPObject( "KdetvIface" ),
      QObject( parent, name ? name : "kdetv" ),
      _view(0),
      _guiFactory(0L),
      _actionCollection(0L),
      _number(""),
      _prevChannel(-1),
      _grabNumber(1)
{
    // Setup configuration system
    KGlobal::dirs()->addResourceType("kdetv", "share/apps/kdetv");
    _cfgHandle = new KConfig("libkdetvrc");
    _cfg = new ConfigData(_cfgHandle, kapp->config());
    if (_cfg->load() == -1) {
        kdFatal() << "Kdetv::Kdetv: Unable to read config file..." << endl;
        exit(1);
    }

    // Create plugin factory
    _pf = new PluginFactory(this);
    _pf->scanForPlugins(_cfg->pluginConfigHandle());

    // Register with DCOP
    if ( !kapp->dcopClient()->isRegistered() ) {
        kapp->dcopClient()->registerAs( "kdetviface" );
        kapp->dcopClient()->setDefaultObject( objId() );
    }

    // Create managers
    _am      = new AudioManager(_pf);
    _sm      = new StatusManager();
    _vbim    = new VbiManager(_pf);
    _fltm    = new FilterManager(_pf);
    _srcm    = new SourceManager(_pf, 0L);
    _vc      = new VolumeController(_am, _srcm,
                                    _cfg->volumeControlViaSoundCard,
                                    _cfg->mixerVolume, _cfg->tvCardVolume);
    _osd     = new OSDManager(_pf, 0L);
    _mm      = new MiscManager(_pf, 0L);
    _viewmng = new ViewManager(this);
    _cs      = new ChannelStore(this, this, "channel_store");

    connect(this, SIGNAL(channelChanged(int)),
            _sm, SIGNAL(channelChanged()));

    connect(_vc, SIGNAL( setChannel() ),
            this, SLOT( doSetChannel() ));
    _vc->setTiming(_cfg->volumeRestoreDelay, _cfg->volumeRestoreDelay);
    _vc->mute(true);

    // Migrate legacy settings
    if (_cfg->firstTime) {
        kdDebug() << "This is a first run. Let's ask to migrate..." << endl;
        // Import channels here
        _cfg->channelFile = _cs->defaultFileName();
        doMigration();
    }

    // Keyboard interface for number keys
    _keypresstimer = new QTimer(parent);
    connect(_keypresstimer, SIGNAL(timeout()),
            this, SLOT(slotKeyPressTimeout()));

    // Setup import/export file format lists
    for (QMap<QString, QString>::const_iterator it = _cs->fileFormatsRead().constBegin();
         it != _cs->fileFormatsRead().constEnd();
         ++it) {
        _fileFormatsRead.append(it.key());
    }
    for (QMap<QString, QString>::const_iterator it = _cs->fileFormatsWrite().constBegin();
         it != _cs->fileFormatsWrite().constEnd();
         ++it) {
        _fileFormatsWrite.append(it.key());
    }
}

Kdetv::~Kdetv()
{
    stop();

    _cfg->save();

    delete _viewmng;
    delete _keypresstimer;
    delete _mm;
    delete _vbim;
    delete _fltm;
    delete _osd;
    delete _vc;
    delete _srcm;
    delete _am;
    delete _sm;
    delete _pf;
    delete _cfg;
    delete _cfgHandle;
}

DCOPRef Kdetv::channelStoreIface()
{
    return DCOPRef( _cs );
}

bool Kdetv::start()
{
    kdDebug() << "Kdetv::start() Trying last device: " << _cfg->prevDev << endl;
    playDevice(_cfg->prevDev);

    if (!_srcm->hasDevice() && !_srcm->deviceList().first().isEmpty()) {
        kdDebug() << "Kdetv::start(): No device selected, trying to use default: "
                  << _srcm->deviceList().first() << endl;
        playDevice(_srcm->deviceList().first());
    }

    return _srcm->hasDevice();
}

void Kdetv::stop()
{
    if (!_srcm->device().isEmpty()) {
        _vc->volume(&_cfg->volumeLeft, &_cfg->volumeRight);
        _cfg->volumeMuted = _vc->muted();
        _cfg->saveDeviceSettings(_srcm->device());
        saveControls();
        saveChannels();
    }

    if (_cfg->autoMute) {
        _vc->mute(true);
    }

    _srcm->stopDevice();
    _chan = NULL;
}

bool Kdetv::playDevice(const QString& dev)
{
    kdDebug () << "Kdetv::playDevice(): dev: " << dev << endl;

    stop();
    QString d = dev;

    if (!d.isEmpty()) {
        if (!_srcm->setDevice(d) || !_srcm->startVideo()) {
            d = QString::null;
        }
    }

    if (d.isEmpty()) {
        return false;
    }

    _cfg->prevDev = _srcm->device();

    // Restore volume settings
    _vc->setVolume(_cfg->volumeLeft, _cfg->volumeRight);
    _vc->mute(_cfg->volumeMuted);

    // Load various settings
    _cfg->loadDeviceSettings(_srcm->device());
    if (_cfg->channelFile.isEmpty()) {
        _cfg->channelFile = _cs->defaultFileName();
    }

    _cs->load(_cfg->channelFile, _cs->defaultFormat());
    if (_cs->isEmpty() && _srcm->isTuner(d)) {
        applyControls();
        launchWizard();
    } else {
        applyControls();
        setLastChannel();
    }

    return true;
}

void Kdetv::saveControls()
{
    if(_chan && _chan->hasControls(_srcm->device())) {
        const Control::ControlList& controls = _srcm->controls();
        for(Control::ControlList::ConstIterator it = controls.constBegin();
            it != controls.constEnd();
            ++it) {
            const Control::Control* c = static_cast<Control*>(*it);
            _chan->setControl(_srcm->device(), c->internalName, c->valueAsQVariant());
        }
    } else {
        Channel::PropertyList& l = _cs->getMetaInfo()._globalControls[_srcm->device()];
        l.clear();

        const Control::ControlList& controls = _srcm->controls();
        for(Control::ControlList::ConstIterator it = controls.begin();
            it != controls.end();
            ++it) {
            const Control::Control* c = static_cast<Control*>(*it);
            l[c->internalName] = c->valueAsQVariant();
        }
    }
}

void Kdetv::applyControls()
{
    if(_chan && _chan->hasControls(_srcm->device())) {
        const Control::ControlList& controls = _srcm->controls();
        for(Control::ControlList::ConstIterator it = controls.constBegin();
            it != controls.constEnd();
            ++it) {
            Control::Control* c = static_cast<Control*>(*it);
            const QVariant& val = _chan->control(_srcm->device(), c->internalName);
            if(val.isValid()) {
                c->setValue(val);
            }
        }
    } else {
        Channel::PropertyList l = _cs->getMetaInfo()._globalControls[_srcm->device()];

        const Control::ControlList& controls = _srcm->controls();
        for(Control::ControlList::ConstIterator it = controls.begin();
            it != controls.end();
            ++it) {
            Control::Control* c = static_cast<Control*>(*it);
            const QVariant& val = l[c->internalName];
            if(val.isValid()) {
                c->setValue(val);
            }
        }
    }
}

const QStringList& Kdetv::fileFormatsRead() const
{
    return _fileFormatsRead;
}

const QStringList& Kdetv::fileFormatsWrite() const
{
    return _fileFormatsWrite;
}

void Kdetv::importChannelFile(const QString& fmt)
{
    QString p = KGlobal::dirs()->saveLocation("kdetv");
    QString fn = KFileDialog::getOpenFileName(p,
                                              QString("*"),
                                              _view,
                                              i18n("Select Channel File for Import"));

    if (fn.isEmpty())
        return;

    ChannelStore tcs( this, 0 );
    if (tcs.load(fn, _cs->fileFormatsRead()[fmt]) && !tcs.isEmpty()) {
        _cs->addChannels(tcs);
        saveChannels();
    } else {
        KMessageBox::error(0L,
                           i18n("Unable to import channels\n(insufficient file access rights or wrong format chosen?)"),
                           i18n("Error Importing Channels"));
    }
}

void Kdetv::exportChannelFile(const QString& fmt)
{
    QString p = KGlobal::dirs()->saveLocation("kdetv");
    QString fn = KFileDialog::getOpenFileName(p,
                                              QString("*"),
                                              _view,
                                              i18n("Select Channel File for Export"));

    if (fn.isEmpty())
        return;

    if (!_cs->save(fn, _cs->fileFormatsWrite()[fmt])) {
        KMessageBox::error(0L,
                           i18n("Unable to export channels\n(insufficient file access rights?)"),
                           i18n("Error Exporting Channels"));
    }
}

bool Kdetv::doMigration()
{
    KGlobal::dirs()->addResourceType("kwintv", "share/apps/kwintv");
    QString p = KGlobal::dirs()->saveLocation("kwintv");

    if (p.isEmpty())
        return false;

    p += "/default.ch";

    if (!QFile::exists(p))
        return false;

    if (KMessageBox::Yes !=
        KMessageBox::questionYesNo(NULL,
                                   i18n("Migrate old KWinTV configuration?"),
                                   i18n("Kdetv"))) {
        return false;
    }

    return importLegacyChannels(false);
}

bool Kdetv::importLegacyChannels(bool warn)
{
    KGlobal::dirs()->addResourceType("kwintv", "share/apps/kwintv");

    QString p = KGlobal::dirs()->saveLocation("kwintv");

    if (p.isEmpty())
        return false;

    p += "/default.ch";

    if (!QFile::exists(p))
        return false;

    if (warn) {
        int rc = KMessageBox::warningContinueCancel(0L,
                                                    i18n("Are you sure you want to import?\nThis will overwrite your current channel file."),
                                                    i18n("Continue Import?"));
        if (rc == KMessageBox::Cancel) {
            return false;
        }
    }

    ChannelStore tcs( this, 0 );
    if (tcs.load(p, "ch")) {
        _cs->clear();
        _cs->addChannels(tcs);
        setLastChannel();
        saveChannels();
        return true;
    }

    return false;
}

QString Kdetv::channelName() const
{
    if (_chan) {
        return _chan->name();
    } else {
        return QString::null;
    }
}

int Kdetv::channelNumber() const
{
    if (_chan) {
        return _chan->number();
    } else {
        return 0;
    }
}

void Kdetv::setChannel( Channel *channel )
{
    if(!channel) {
        return;
    }

    if (_chan) {
        _prevChannel = _chan->number();
    }

    saveControls();
    _chan = channel;
    _cfg->lastChannel = _chan->number();

    _vc->prepareChannelChange();
}

void Kdetv::doSetChannel()
{
    if(!_chan) {
        return;
    }

    emit channelChanged( _chan->number() );
    emit channelChanged( _chan->name()   );
    emit channelChanged( _chan );

    kdDebug() << "Kdetv::setChannel(): " << _chan->name() << endl;
    _osd->displayChannel(_chan->number(), _chan->name());

    _srcm->setChannel(*_chan);
    applyControls();
}

void Kdetv::setChannel(int channel)
{
    // Ignore request if the channel number matches
    if (_chan && _chan->number () == channel) {
        _prevChannel = channel;
        return;
    }

    forceSetChannel(channel);
} // setChannel

void Kdetv::forceSetChannel(int channel)
{
    if (_cs->channelNumber(channel)) {
        setChannel(_cs->channelNumber(channel));
    } else {
        if (_chan) {
            // if the new channel is not valid, just repeat the original number
            // so that the LCD display knows what to show
            emit channelChanged(_chan->number());
        }
    }
}

void Kdetv::channelUp()
{
    Channel *c = _cs->channelAfter(_chan);

    if (!c)
        return;

    // skip channels that the user has disabled
    while (!c->enabled() && c != _chan)
        c = _cs->channelAfter(c);
    setChannel( c );
}

void Kdetv::channelDown()
{
    Channel *c = _cs->channelBefore(_chan);

    if (!c)
        return;

    // skip channels that the user has disabled
    while (!c->enabled() && c != _chan)
        c = _cs->channelBefore(c);
    setChannel( c );
}

void Kdetv::previousChannel()
{
    if (_prevChannel != -1)
        setChannel(_prevChannel);
}

void Kdetv::setLastChannel()
{
    Channel *x = _cs->channelNumber(_cfg->lastChannel);
    Channel *y = x;

    if (!x)
        x = _cs->channelAt(0);

    while (x && !x->enabled() && x != y)
        x = _cs->channelAfter(x);

    _prevChannel = -1;
    setChannel(x);
}

void Kdetv::toggleMute()
{
    _vc->toggleMute();
}

void Kdetv::volumeUp()
{
    _vc->volumeUp(_cfg->volumeIncrement);
}

void Kdetv::volumeDown()
{
    _vc->volumeDown(_cfg->volumeIncrement);
}

void Kdetv::setVolume(int left, int right)
{
    _vc->setVolume(left, right);
}

void Kdetv::setVolume(int vol)
{
    _vc->setVolume(vol, vol);
}

void Kdetv::setAudioMode(const QString& mode)
{
    _srcm->setAudioMode(mode);
}

KdetvView *Kdetv::createScreen( QWidget *parent, const char *name )
{
    _view = new KdetvView( parent, name ? name : "kdetv_screen" );

    // Make sure the TV background is black (looks best)
    _view->setPaletteBackgroundColor( QColor( 0, 0, 0 ) );

    // Check the setting of "Fix Aspect Ratio" and apply it.
    // The "Aspect Ratio Mode" determines how the algorithm works.
    if ( _cfg->fixAR ) {
        _view->setFixedAspectRatio( ASPECT_RATIO_NORMAL, _cfg->ARmode );
    } else {
        _view->setFixedAspectRatio( ASPECT_RATIO_NONE,   _cfg->ARmode );
    }

    connect(_view, SIGNAL(mouseWheelUp()),
            this, SLOT(mouseWheelUp()));
    connect(_view, SIGNAL(mouseWheelDown()),
            this, SLOT(mouseWheelDown()));
    connect(_view, SIGNAL(middleButtonPressed()),
            this, SLOT(toggleMute()));
    connect(_view, SIGNAL(numberKeyPressed(int)),
            this, SLOT(processNumberKeyEvent(int)));

    connect(_vc,  SIGNAL(volumeChanged(int,int)),
            _osd, SLOT(displayVolume(int,int)));
    connect(_vc,  SIGNAL(muted(bool)),
            _osd, SLOT(displayMuted(bool)));
    connect(this, SIGNAL(channelText(const QString &)),
            _osd, SLOT(displayMisc(const QString &)));

    connect(_srcm, SIGNAL(colourKeyChanged(QColor)),
            _osd, SLOT(setColourKey(QColor)));
    connect(_srcm, SIGNAL(colourKeyChanged(QColor)),
            _view, SLOT(update()));

    // Connect the viewmanager's signals to the slots to propagate configuration changes
    connect(_viewmng, SIGNAL(setFixedAspectRatio(bool, int)),
            _view, SLOT(setFixedAspectRatio(bool, int)));

    // Pass screen to view dependent managers
    _osd->setScreen(_view);
    _srcm->setScreen(_view);
    _mm->setScreen(_view);

    return _view;
}

void Kdetv::mouseWheelUp()
{
    if (_cfg->mouseWheelUpIsChUp) {
        channelUp();
    } else {
        channelDown();
    }
}

void Kdetv::mouseWheelDown()
{
    if (_cfg->mouseWheelUpIsChUp) {
        channelDown();
    } else {
        channelUp();
    }
}

void Kdetv::processNumberKeyEvent(int key)
{
    if (_mm->filterNumberKey(key))
        return;

    if (_keypresstimer->isActive())
        _keypresstimer->stop();

    // key == -1 means ENTER was pressed. Simply force a
    // channel change and return...
    if (key == -1) {
        slotKeyPressTimeout ();
        return;
    }

    // Get the highest channel number...
    Channel * ch = _cs->channelAt(_cs->count()-1);
    int maxChNum = ch->number();

    // For every '0' entered before the number lower the max channel by
    // 10 times
    int len = _number.length();

    for (int i = 0; i < len && _number[i] == '0'; i++)
        maxChNum /= 10;

    _number += QString::number(key);

    // If by entering another digit you can get a number lesser than
    // the highest channel number configured/detected, start a timer
    // (wait for that next keypress).
    // Otherwise, simply tune to the channel.
    if (_number.toInt() * 10 < maxChNum) {
        kdDebug() << "channelText(" << _number.rightJustify(3,'-') << ")" << endl;
        emit channelText(_number.rightJustify(3,'-'));
        _keypresstimer->start(_cfg->maxKeypressInterval, true);  // start the count-down timer
    } else {
        if (_number != "0")
            _keypresstimer->singleShot(0, this, SLOT(slotKeyPressTimeout()));
    } // else
}// processNumberKeyEvent

void Kdetv::slotKeyPressTimeout()
{
    if (_number != "0") {
        kdDebug() << "Kdetv: calling setChannel(" << _number << ")" << endl;
        setChannel(_number.toInt());
    }

    _number = "";
} // slotKeyPressTimeout

void Kdetv::reloadChannels()
{
    _cs->clear();
    _cs->load(_cfg->channelFile, _cs->defaultFormat());
    setLastChannel();
}

void Kdetv::saveChannels()
{
    _cs->save(_cfg->channelFile);
}

KXMLGUIFactory *Kdetv::guiFactory() const
{
    return _guiFactory;
}

KActionCollection *Kdetv::actionCollection() const
{
    return _actionCollection;
}

void Kdetv::setGuiFactory(KXMLGUIFactory *guiFactory, KActionCollection *actionCollection)
{
    _pf->setGUIFactory(guiFactory, actionCollection);

    _guiFactory       = guiFactory;
    _actionCollection = actionCollection;
}

void Kdetv::snapshot(const QString& filename)
{
    bool rc = false;

    int w = grab_resolutions[_cfg->snapshotRes].w;
    int h = grab_resolutions[_cfg->snapshotRes].h;

    kdDebug() << "Kdetv: Creating snapshot of size " << w << "x" << h << endl;
    QImage grabPix(w, h, 32);
    rc = _srcm->snapshot( grabPix );

    if (rc) {
        QString mf;
        QString channel = channelName();
        QDateTime currentDateTime = QDateTime::currentDateTime();

        if(filename.isEmpty()) {
            do {
                mf = _cfg->snapshotPath
                    + QString("tv-%1-%2-%3.").arg(channel.lower(), currentDateTime.toString("yyyyMMdd")).arg(_grabNumber++)
                    + _cfg->snapshotFormat.lower();
            } while (QFileInfo(mf).exists());
        } else {
            mf = _cfg->snapshotPath
                + filename
                + "."
                + _cfg->snapshotFormat.lower();
        }

        if ( grabPix.save(mf, _cfg->snapshotFormat.local8Bit(), _cfg->snapshotQuality) ) {
            _sm->message(i18n("Snapshot saved to %1").arg(mf));
        } else {
            KMessageBox::sorry(_view,
                               i18n("Error saving snapshot. Configure a working directory in the settings dialog."));
        }
    } else {
        KMessageBox::sorry(_view,
                           i18n("Error grabbing image. Snapshots may not be supported with this plugin."));
    }
}

void Kdetv::settings()
{
    _viewmng->launchSettings(_view);
}

void Kdetv::launchWizard()
{
    _viewmng->launchWizard(_view);
}

void Kdetv::importDefault()
{
    _viewmng->launchImportDefault(_view);
}

void Kdetv::controls()
{
    _viewmng->launchControls(_view);
}

void Kdetv::editChannels()
{
    _viewmng->launchChannelEditor(_view);
}

void Kdetv::filterChainStatus()
{
    _viewmng->filterChainStatus(_view);
}

#include "kdetv.moc"
