/***************************************************************************
                           channelscanner.cpp
                           ------------------
    begin                : Sat Oct 25 2003
    copyright            : (C) 2003 by Dirk Ziegelmeier
    email                : 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 <limits.h>

#include <kdebug.h>
#include <klocale.h>
#include <kled.h>
#include <knuminput.h>
#include <kmessagebox.h>

#include <qcombobox.h>
#include <qapplication.h>
#include <qprogressbar.h>
#include <qlistview.h>
#include <qlabel.h>
#include <qradiobutton.h>
#include <qpushbutton.h>
#include <qstringlist.h>
#include <qtimer.h>
#include <qslider.h>
#include <qcheckbox.h>

#include "kdetv.h"
#include "sourcemanager.h"
#include "kdetvchannelplugin.h"
#include "channel.h"
#include "channelstore.h"
#include "channelimporter.h"
#include "channelscanner.h"
#include "kdetvvbiplugin.h"
#include "vbimanager.h"
#include "pluginfactory.h"
#include "finetuningdlg.h"


// time to wait for tuner to finish tuning
#define TUNING_DELAY_MSEC       300

// maximum time to wait for network identifier to be broadcasted
#define NETWORKNAME_DELAY_MSEC 5000

// ------------------------------------------------------ Result channel list item

class ResultListItem : public QListViewItem
{
public:
    ResultListItem(QListView* parent, Channel* c);
    virtual ~ResultListItem();

    virtual int compare(QListViewItem* i, int col, bool ascending) const;

    Channel* _c;
};

ResultListItem::ResultListItem(QListView* parent, Channel* c)
    : QListViewItem(parent,
                    QString("%1").arg(c->number()),
                    c->name(),
                    i18n("%1 kHz").arg(c->getChannelProperty("frequency").toULongLong())),
      _c(c)
{
}

ResultListItem::~ResultListItem()
{
}

int ResultListItem::compare(QListViewItem* item, int col, bool ascending) const
{
    ResultListItem* i = static_cast<ResultListItem*>(item);

    if (col == 0) {
        if (_c->number() == i->_c->number()) return 0;

        return (_c->number() > i->_c->number() ? 1 : -1);
    }
    return QListViewItem::compare(item, col, ascending);
}

// --------------------------------------------------------- Frequency Factories

class ScannerFrequencyFactory
{
public:

    ScannerFrequencyFactory() {};
    virtual ~ScannerFrequencyFactory() {};

    // returns false if no more frequencies
    virtual bool getFrequency(QString& defaultName, Q_ULLONG& freq, int& percent) = 0;
    virtual bool needsFineTuning() { return false; };
    virtual bool forceAdding() { return false; };
    // contine scanning from here
    virtual void setCurrentFreq(int) {};
};

class ScannerFrequencyFactoryPredefined : public ScannerFrequencyFactory
{
private:
    ChannelStore* _store;
    uint          _pos;
    uint          _capacity;
    bool          _forceAdding;

public:
    // Class assumes to be owner of supplied store
    ScannerFrequencyFactoryPredefined(ChannelStore* store, bool force)
        : _store(store),
          _pos(0),
          _capacity(store->count()),
          _forceAdding(force)
    {
    }

    virtual ~ScannerFrequencyFactoryPredefined()
    {
        delete _store;
    }

    virtual bool getFrequency(QString& defaultName, Q_ULLONG& freq, int& percent)
    {
        if (_pos >= _capacity) {
            return false;
        }
        Channel* c   = _store->channelAt(_pos);
        freq         = c->getChannelProperty("frequency").toULongLong();
        defaultName  = c->name();
        percent      = (_pos * 100) / _capacity;
        _pos        += 1;
        return true;
    }

    virtual bool forceAdding()
    {
        return _forceAdding;
    }
};

class ScannerFrequencyFactoryFreqList : public ScannerFrequencyFactory
{
private:
    int _min, _max, _step;
    int _current;

public:
    ScannerFrequencyFactoryFreqList(int min, int max, int step)
        : _min(min),
          _max(max),
          _step(step),
          _current(min)
    {
    }

    virtual ~ScannerFrequencyFactoryFreqList() {}

    virtual bool getFrequency(QString& defaultName, Q_ULLONG& freq, int& percent)
    {
        if (_current > _max) {
            return false;
        }

        freq         = _current;
        defaultName  = QString::null;
        percent      = ((_current - _min) * 100) / (_max - _min);
        _current    += _step;
        return true;
    }

    virtual bool needsFineTuning()
    {
        return true;
    }

    virtual void setCurrentFreq(int freq)
    {
        _current = freq + _step;
    };
};

// ------------------------------------------------------------------ Channel Scanner Dialog

ChannelScanner::ChannelScanner( Kdetv* ktv, QWidget* parent, const char* name, bool modal, WFlags fl)
    : ChWizard( parent, name, modal, fl ),
      _networkName(QString::null),
      _ktv(ktv),
      _srcm(ktv->sourceManager()),
      _vbimgr(ktv->vbiManager())
{
    _vbimgr->addClient();
    _vbimgr->resume();

    connect(this, SIGNAL( selected(const QString&) ),
            this, SLOT( pageChanged() ));
    connect(_scanPredefined, SIGNAL( toggled(bool) ),
            this, SLOT( updatePageSelection() ));
    connect(_scanFrequencies, SIGNAL( toggled(bool) ),
            this, SLOT( updatePageSelection() ));
    connect(_source, SIGNAL( activated(int) ),
            this, SLOT( updateSource() ));
    connect(_encoding, SIGNAL( activated(int) ),
            this, SLOT( updateEncoding() ));
    connect(_channelList, SIGNAL( selectionChanged() ),
            this, SLOT( setChannel() ));
    connect(_preselectRange, SIGNAL( activated(int) ),
            this, SLOT( preselectRangeChanged() ));

    connect(this, SIGNAL( progress(int) ),
            _progress, SLOT( setProgress(int) ));
    connect(this, SIGNAL( progress(const QString&) ),
            _textualScanFeedback, SLOT( setText(const QString&) ));

    _stationTimer = new QTimer(this, "StationTimer");
    connect(_stationTimer, SIGNAL( timeout() ),
            this, SLOT( checkFrequency() ));

    _nameTimer = new QTimer(this, "NameTimer");
    connect(_nameTimer, SIGNAL( timeout() ),
            this, SLOT( checkName() ));

    _source->insertStringList(_srcm->sourcesFor(_srcm->device()));
    _source->setCurrentText(_srcm->source());

    _encoding->insertStringList(_srcm->encodingsFor(_srcm->device()));
    _encoding->setCurrentText(_srcm->encoding());

    _channelImporter = new ChannelImporter();
    _frequencyTable->insertStringList(_channelImporter->lists());
    _preselectRange->insertStringList(_channelImporter->lists());
    preselectRangeChanged();

    connect(_vbimgr, SIGNAL( networkId(const QString&, int&, const QString&) ),
            this, SLOT( setNetworkId(const QString&) ));
    _vbimgr->restartPlugin();

    _store = new ChannelStore(_ktv, this, "ResultStore");

    // prevent infinite loop when user presses cancel and there are no channels
    if ( _ktv->channels()->isEmpty() ) {
        _prevDev = QString::null;
    } else {
        _prevDev = _srcm->device();
    }

    updatePageSelection();
}

ChannelScanner::~ChannelScanner()
{
    _vbimgr->suspend();
    _vbimgr->removeClient();

    delete _stationTimer;
    delete _nameTimer;
    delete _channelImporter;
    delete _store;
}

void ChannelScanner::updateSource()
{
    _srcm->setSource(_source->currentText());
}

void ChannelScanner::updateEncoding()
{
    _srcm->setEncoding(_encoding->currentText());
    _vbimgr->restartPlugin();
}

void ChannelScanner::preselectRangeChanged()
{
    ChannelStore store(_ktv, this, "FactoryStore");

    if (!_channelImporter->import(&store, _preselectRange->currentText())) {
        KMessageBox::error(0L,
                           i18n("Error importing frequency list file. Check your installation."),
                           i18n("Error Reading Frequency File"));
        return;
    }

    ulong minFreq = ULONG_MAX;
    ulong maxFreq = 0L;
    for (uint i=0; i<store.count(); i++) {
        Q_ULLONG f = store.channelAt(i)->getChannelProperty("frequency").toULongLong();

        if (f < minFreq) {
            minFreq = f;
        }
        if (f > maxFreq) {
            maxFreq = f;
        }
    }

    _minFreq->setValue(minFreq / 1000);
    _maxFreq->setValue(maxFreq / 1000);
}

void ChannelScanner::updatePageSelection()
{
    if (_scanPredefined->isChecked()) {
        setAppropriate(((QWizard*)this)->page(preparePredefined), true);
        setAppropriate(((QWizard*)this)->page(prepareFreqRange),  false);
    } else {
        setAppropriate(((QWizard*)this)->page(preparePredefined), false);
        setAppropriate(((QWizard*)this)->page(prepareFreqRange),  true);
    }
}

void ChannelScanner::pageChanged()
{
    helpButton()->setEnabled(false);

    switch (indexOf(currentPage())) {
    case Setup:
        checkSignalStrengthReadback();
        break;
    case Scanning:
        backButton()->setEnabled(false);
        nextButton()->setEnabled(false);
        scan();
        break;
    case Finished:
        finishButton()->setEnabled(true);
        setupFinishedPage();
        break;
    default:
        break;
    }
}

void ChannelScanner::accept()
{
    ChannelStore* ktvStore = _ktv->channels();

    if (_replaceChannels->isChecked()) {
        ktvStore->clear();
    }
    ktvStore->addChannels(*_store);

    QDialog::accept();
}

void ChannelScanner::reject()
{
    // restore old state
    if (!_prevDev.isEmpty()) {
        _ktv->playDevice(_prevDev);
    }

    QDialog::reject();
}

void ChannelScanner::scanningDone(bool success)
{
    if (success) {
        showPage( ((QWizard*)this)->page(Finished) );
        backButton()->setEnabled(false);
        nextButton()->setEnabled(true);
    } else {
        showPage( ((QWizard*)this)->page(Setup) );
        backButton()->setEnabled(true);
        nextButton()->setEnabled(true);
    }

    if (_freqFactory) delete _freqFactory;
}

void ChannelScanner::setupFinishedPage()
{
    _store->renumber();
    _channelList->clear();
    for (uint i=0; i< _store->count(); i++) {
        new ResultListItem(_channelList, _store->channelAt(i));
    }
    _channelList->setSelected(_channelList->firstChild(), true);
    setChannel();
}

void ChannelScanner::checkSignalStrengthReadback()
{
    bool vbi = _vbimgr->decoding();

    if (vbi) {
        _vbiAccess->setState(KLed::On);
        _vbiAccess->setColor(QColor(0, 255, 0));
        _getNamesFromVBI->setChecked(true);
        _getNamesFromVBI->setEnabled(true);
    } else {
        _vbiAccess->setState(KLed::Off);
        _vbiAccess->setColor(QColor(255, 0, 0));
        _getNamesFromVBI->setChecked(false);
        _getNamesFromVBI->setEnabled(false);
    }

    if (_srcm->signal() == -1) {
        _signalReadback->setState(KLed::Off);
        _signalReadback->setColor(QColor(255, 0, 0));
    } else {
        _signalReadback->setState(KLed::On);
        _signalReadback->setColor(QColor(0, 255, 0));
    }

    if ( (_srcm->signal() != -1) || vbi) {
        _okToScan->setState(KLed::On);
        _okToScan->setColor(QColor(0, 255, 0));
        nextButton()->setEnabled(true);
    } else {
        _okToScan->setState(KLed::Off);
        _okToScan->setColor(QColor(255, 0, 0));
        nextButton()->setEnabled(false);
    }
}

void ChannelScanner::setChannel()
{
    ResultListItem* i = static_cast<ResultListItem*>(_channelList->selectedItem());

    if (!i) {
        return;
    } else {
        _ktv->setChannel(i->_c);
    }
}

// ------------------------------------------------------------------------ Scanning backend

void ChannelScanner::scan()
{
    if (_scanPredefined->isChecked()) {
        ChannelStore* fStore = new ChannelStore(_ktv, this, "FactoryStore");
        if (!_channelImporter->import(fStore, _frequencyTable->currentText())) {
            KMessageBox::error(0L,
                               i18n("Error importing frequency list file. Check your installation."),
                               i18n("Error Reading Frequency File"));
            delete fStore;
            scanningDone(false);
            return;
        }
        _freqFactory = new ScannerFrequencyFactoryPredefined(fStore, _disableUnused->isChecked());
    } else {
        _freqFactory = new ScannerFrequencyFactoryFreqList((int)(_minFreq->value()*1000),
                                                           (int)(_maxFreq->value()*1000),
                                                           (int)(_freqIncrement->value()*1000));
    }

    _store->clear();
    scanFrequency();
}

void ChannelScanner::scanFrequency()
{
    int percent;
    Q_ULLONG freq;

    if (!_freqFactory->getFrequency(_name, freq, percent)) {
        scanningDone(true);
        return;
    }

    _networkName = QString::null;
    setFrequency(freq);

    emit progress(percent);
    QString text;
    if (_name.isEmpty()) {
        text = i18n("Scanning... (%1 kHz)").arg(freq);
    } else {
        text = i18n("Scanning... (%1)").arg(_name);
    }
    emit progress(text);

    // wait for tuner
    _stationTimer->start(TUNING_DELAY_MSEC, true);
}

void ChannelScanner::checkFrequency()
{
    bool isStation;

    if (_vbimgr->decoding()) {
        isStation = _vbimgr->tuned();
    } else {
        isStation = (_srcm->signal() != 0);
    }

    if (isStation) {
        if (_freqFactory->needsFineTuning()) {
            ftd = new FineTuningDlg(this, "FinetuningDialog", true);

            ftd->_slider->setMinValue(_freq - 3000);
            ftd->_slider->setMaxValue(_freq + 3000);
            ftd->_slider->setValue   (_freq        );

            connect(ftd->_slider, SIGNAL( valueChanged(int) ),
                    this, SLOT( setFrequency(int) ));
            connect(ftd, SIGNAL( okClicked() ),
                    this, SLOT( fineTuneOK() ));
            connect(ftd, SIGNAL( cancelClicked() ),
                    this, SLOT( fineTuneCancel() ));

            ftd->show();
        } else {
            fineTuningDone();
        }
    } else {
        if (_freqFactory->forceAdding()) {
            stationFound(false);
        } else {
            scanFrequency();
        }
    }
}

void ChannelScanner::checkName()
{
    if (!_networkName.isEmpty()) {
        _name = _networkName;
    }
    stationFound(true);
}

void ChannelScanner::stationFound(bool enable)
{
    if (_name.isEmpty()) {
        _name = QString("%1 kHz").arg(_freq);
    }
    kdDebug() << "ChannelScanner: Station found: " << _name << ", frequency: " << (unsigned int)_freq << " kHz" << endl;
    Channel* c = new Channel(_store);
    c->updateValues(_name, 0, true);
    c->setChannelProperty("frequency", _freq);
    c->setChannelProperty("source", _source->currentText());
    c->setChannelProperty("encoding", _encoding->currentText());
    _store->addChannel(c);
    c->setEnabled(enable);
    _freqFactory->setCurrentFreq(_freq);
    scanFrequency();
}

void ChannelScanner::setNetworkId(const QString& name)
{
    _networkName = name;

    // speedup scanning if we received a name
    if (_stationTimer->isActive() && !_networkName.isEmpty()) {
        // no need to check for station reception -
        // if we have VBI events, we are tuned anyway
        _stationTimer->stop();
        checkName();
    }
    if (_nameTimer->isActive() && !_networkName.isEmpty()) {
        _nameTimer->stop();
        checkName();
    }
}

void ChannelScanner::fineTuneOK()
{
    ftd->deleteLater();
    fineTuningDone();
}

void ChannelScanner::fineTuneCancel()
{
    ftd->deleteLater();
    scanFrequency();
}

void ChannelScanner::fineTuningDone()
{
    if (_getNamesFromVBI->isChecked()) {
        emit progress(i18n("Station found. Waiting for network name..."));
        // wait for station name to be broadcasted
        _nameTimer->start(NETWORKNAME_DELAY_MSEC, true);
    } else {
        stationFound(true);
    }
}

void ChannelScanner::setFrequency(int freq)
{
    _freq = freq;
    _srcm->setFrequency(freq);
}

#include "channelscanner.moc"
