/***************************************************************************
                           vbidecoder.cpp
                           --------------
    begin                : Sun Oct 26 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.
 */

#ifdef __STRICT_ANSI__
#define FOO__STRICT_ANSI__
#undef __STRICT_ANSI__
#endif
#include <asm/types.h>
#ifdef FOO__STRICT_ANSI__
#define __STRICT_ANSI__
#undef FOO__STRICT_ANSI__
#endif
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
// the following #defines are necessary fo people who symlink their kernel
// include dir instead of installing clean userspace-only headers
#define _LINUX_TIME_H
#define _DEVICE_H_
#include <linux/videodev.h>
#include <libzvbi.h>

#include <qapplication.h>
#include <qevent.h>
#include <qcombobox.h>
#include <qframe.h>
#include <qthread.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qstringlist.h>
#include <qfile.h>
#include <qfileinfo.h>

#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>

#include "kdetv.h"
#include "vbidecoder.h"
#include "vbimanager.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// ---------------------------------------------- VBI capture configuration

// FIXME: Dirk: Do we need this? My libzvbi 0.2.4 seems to always use autodetection...
//              I added it to be sure of surprises, though.

struct vbi_config_s {
    const char* name;
    uint        services;
};

static struct vbi_config_s vbi_config[] = {
    {
        "Autodetect",
        VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625 |
        VBI_SLICED_TELETEXT_B |
        VBI_SLICED_VPS |
        VBI_SLICED_CAPTION_525 | VBI_SLICED_CAPTION_625 |
        VBI_SLICED_WSS_625 | VBI_SLICED_WSS_CPR1204
    },

    {
        "PAL / SECAM",
        VBI_SLICED_VBI_625 | VBI_SLICED_TELETEXT_B | VBI_SLICED_CAPTION_625 | VBI_SLICED_WSS_625 | VBI_SLICED_VPS
    },

    {
        "NTSC",
        VBI_SLICED_VBI_525 | VBI_SLICED_CAPTION_525 | VBI_SLICED_WSS_CPR1204
    }
};

#define VBI_CONFIGS ( sizeof(vbi_config) / sizeof(struct vbi_config_s) )

// add vbi devices here
static const char* vbi_devices[] = {
    "/dev/vbi", // this is usually a link to a default device, so try it first
    "/dev/v4l/vbi0",
    "/dev/v4l/vbi1",
    "/dev/v4l/vbi2",
    "/dev/v4l/vbi3",
    "/dev/v4l/vbi4",
    "/dev/vbi0",
    "/dev/vbi1",
    "/dev/vbi2",
    "/dev/vbi3",
    "/dev/vbi4",
    0
};

// ---------------------------------------------- Decoder Thread

class VbiDecoderPrivate : public QThread
{
public:
    VbiDecoderPrivate(QObject* eventTarget);
    virtual ~VbiDecoderPrivate();

    bool init(const QString& dev, uint services);
    void stop();
    int  vbiHandle();

    vbi_decoder* _dec;
    bool	     _suspended;


protected:
    virtual void run();


private:
    volatile bool     _terminate;
    vbi_capture*      _cap;
#ifdef HAVE_VBI_PROXY
    vbi_proxy_client* _proxy;
#endif
    vbi_raw_decoder*  _par;
    vbi_sliced*       _sliced;
    QObject*          _eventTarget;

    static void vbi_decoder_vbi_event(vbi_event* event, void* data);
    void vbiEvent(vbi_event* event);
};

VbiDecoderPrivate::VbiDecoderPrivate(QObject* eventTarget)
    : QThread(),
      _suspended(false),
      _terminate(false),
      _cap(0L),
#ifdef HAVE_VBI_PROXY
      _proxy(0L),
#endif
      _sliced(0L),
      _eventTarget(eventTarget)
{
    _dec = vbi_decoder_new();
}

VbiDecoderPrivate::~VbiDecoderPrivate()
{
    stop();
}

void VbiDecoderPrivate::stop()
{
    _terminate = true;
    wait();

    if (_sliced) delete[](_sliced);
    _sliced = 0L;
    if (_cap) vbi_capture_delete(_cap);
    _cap = 0L;
#ifdef HAVE_VBI_PROXY
    if(_proxy) vbi_proxy_client_destroy(_proxy);
    _proxy = 0L;
#endif
}

bool VbiDecoderPrivate::init(const QString& dev, uint services)
{
    char* errorstr = NULL;

    if ( running() ) {
        stop();
    }

    _cap = NULL;

#ifdef HAVE_VBI_PROXY
    _proxy = vbi_proxy_client_create(QFile::encodeName(dev), "kdetv",
                                     VBI_PROXY_CLIENT_NO_TIMEOUTS, &errorstr, true);
    if(errorstr) {
        kdWarning() << "VBIDecoder: vbi_proxy_client_create error: " << errorstr << endl;
        delete(errorstr);
        errorstr = NULL;
    }
    if (NULL != _proxy) {
        _cap = vbi_capture_proxy_new(_proxy, 16, 0, &services, -1, &errorstr);
        if(errorstr) {
            kdWarning() << "VBIDecoder: vbi_capture_proxy_new error: " << errorstr << endl;
            delete(errorstr);
            errorstr = NULL;
        }
    }
#endif

    if(NULL == _cap) {
        _cap = vbi_capture_v4l2_new(QFile::encodeName(dev), 16, &services, -1, &errorstr, true);
        if(errorstr) {
            kdWarning() << "VBIDecoder: vbi_capture_v4l2_new error: " << errorstr << endl;
            delete(errorstr);
            errorstr = NULL;
        }
        if (!_cap) {
            _cap = vbi_capture_v4l_new(QFile::encodeName(dev), 16, &services, -1, &errorstr, true);
            if(errorstr) {
                kdWarning() << "VBIDecoder: vbi_capture_v4l_new error: " << errorstr << endl;
                delete(errorstr);
            }
            if (!_cap) {
                return false;
            }
        }
        kdDebug() << "VBIDecoder: Using V4L(2) interface." << endl;
    } else {
        kdDebug() << "VBIDecoder: Using VBI proxy." << endl;
    }
    _par       = vbi_capture_parameters(_cap);
    _sliced    = new vbi_sliced[_par->count[0] + _par->count[1]];
    _terminate = false;
    start();
    return true;
}

void VbiDecoderPrivate::run()
{
    if (!_cap || !_sliced) return;

    vbi_event_handler_register(_dec,
                               VBI_EVENT_NETWORK  | VBI_EVENT_CAPTION |
                               VBI_EVENT_TTX_PAGE | VBI_EVENT_ASPECT  | VBI_EVENT_PROG_INFO,
                               &vbi_decoder_vbi_event,
                               this);
    qApp->postEvent(_eventTarget, new EventRunning(true));
    kdDebug() << "VbiDecoder: Running." << endl;

    double ts = 0.0;
    int lines;
    timeval tv = { 1, 0 };

    // No need for semaphore here.
    // Only one reader thread for a race-free NULL or NON-NULL value.
    while ( !_terminate ) {
        switch ( vbi_capture_read_sliced(_cap, _sliced, &lines, &ts, &tv) ) {
        case -1:
            kdWarning() << "VbiDecoder: VBI capture error: " << strerror(errno) << endl;
            _terminate = true;
            break;
        case 1:
            vbi_decode(_dec, _sliced, lines, ts);
            break;
        default:
            break;
        }
    }

    vbi_event_handler_unregister(_dec, &vbi_decoder_vbi_event, this);
    qApp->postEvent(_eventTarget, new EventRunning(false));
    kdDebug() << "VbiDecoder: Stopped." << endl;
}

void VbiDecoderPrivate::vbi_decoder_vbi_event(vbi_event* event, void* data)
{
    reinterpret_cast<VbiDecoderPrivate*>(data)->vbiEvent(event);
}

void VbiDecoderPrivate::vbiEvent(vbi_event* event)
{
    if (_suspended) return;	
    
    switch(event->type) {
    case VBI_EVENT_NETWORK:
        qApp->postEvent(_eventTarget,
                        new EventStationName(QString::fromLatin1((const char*)event->ev.network.name),
                                             event->ev.network.nuid,
                                             QString::fromLatin1((const char*)event->ev.network.call)));
        break;
    case VBI_EVENT_CAPTION:
        qApp->postEvent(_eventTarget, new EventCaption(event->ev.caption.pgno));
        break;
    case VBI_EVENT_TTX_PAGE:
        qApp->postEvent(_eventTarget,
                        new EventTtx(event->ev.ttx_page.pgno, event->ev.ttx_page.subno, event->ev.ttx_page.pn_offset,
                                     event->ev.ttx_page.roll_header, event->ev.ttx_page.header_update, event->ev.ttx_page.clock_update));
        break;
    case VBI_EVENT_ASPECT:
        qApp->postEvent(_eventTarget,
                        new EventAspect(event->ev.aspect.first_line, event->ev.aspect.last_line, event->ev.aspect.ratio,
                                        event->ev.aspect.film_mode, event->ev.aspect.open_subtitles));
        break;
    case VBI_EVENT_PROG_INFO:
        qApp->postEvent(_eventTarget,
                        new EventProgTitle(QString::fromLatin1((const char*)event->ev.prog_info->title)));
        qApp->postEvent(_eventTarget,
                        new EventRating(QString::fromLatin1(vbi_rating_string(event->ev.prog_info->rating_auth,
                                                                              event->ev.prog_info->rating_id))));
        break;
    default:
        break;
    }
}

int VbiDecoderPrivate::vbiHandle()
{
    if (_cap) {
        return vbi_capture_fd(_cap);
    } else {
        return -1;
    }
}

// ---------------------------------------------- Plugin

VbiDecoderPlugin::VbiDecoderPlugin(Kdetv *ktv, const QString& cfgkey, QObject *parent, const char* name)
    : KdetvVbiPlugin(ktv, cfgkey, parent, name)
{
    _t = new VbiDecoderPrivate(parent);
    _dec = static_cast<void*>(_t->_dec);

    _cfg->setGroup("Device");
    _cfg_dev  = _cfg->readEntry("Device", QString::null);
    _cfg_norm = _cfg->readNumEntry("Norm", 0);

    VbiDecoderPlugin::restart();
}

VbiDecoderPlugin::~VbiDecoderPlugin()
{
    delete _t;
}

bool VbiDecoderPlugin::restart()
{
    bool init_result;

    kdDebug() << "[VBIDecoder::restart()] last configured device was: "
              << _cfg_dev << endl;

    if (_cfg_dev.isEmpty() ||
        !QFileInfo(_cfg_dev).isReadable()) {
        kdDebug() << "[VBIDecoder::restart()] no (valid) previous config found. Trying to autodetect..."
                  << endl;

        // find first readable vbi device
        int i=0;
        while (vbi_devices[i] != 0) {
            QString dev = QString::fromLatin1(vbi_devices[i]);
            kdDebug() << "[VBIDecoder::restart()] Trying device: " << dev << endl;
            if(QFileInfo(dev).isReadable() && _t->init(dev, vbi_config[_cfg_norm].services)) {
                _cfg_dev = dev;
                return true;
            }
            i++;
        }
        // don't save config - user should call config dialog if he wants something special
    }

    if(!QFileInfo(_cfg_dev).isReadable()) {
        kdWarning() << "[VBIDecoder::restart()] no permission to access device "
                  << _cfg_dev << endl;
        return false;
    }

    init_result = _t->init(_cfg_dev, vbi_config[_cfg_norm].services);

    if(!init_result)
        kdWarning() << "[VBIDecoder::restart()] failed to initialize device"
                    << endl;
    else
        kdDebug() << "[VBIDecoder::restart()] Initialised VBI device \""
                  << _cfg_dev << "\" with norm \"" << vbi_config[_cfg_norm].name << "\"" << endl;

    return init_result;
}

bool VbiDecoderPlugin::decoding() const
{
    return _t->running();
}

bool VbiDecoderPlugin::tuned() const
{
    int handle = _t->vbiHandle();
    if (handle == -1) return false;

    struct video_tuner vt;
    memset(&vt, 0, sizeof(vt));
    int rc = ioctl(handle, VIDIOCGTUNER, &vt);
    if (rc < 0) {
        kdWarning() << "VbiDecoderPlugin: IOCTL VIDIOCGTUNER error: " << rc << endl;
        return false;
    }
    return (vt.signal != 0);
}

QWidget* VbiDecoderPlugin::configWidget(QWidget* parent, const char* name)
{
    QString dev;

    QFrame *w = new QFrame(parent, name);
    w->setMargin(4);

    QGridLayout *g = new QGridLayout(w, 3, 3);

    g->addWidget(new QLabel(i18n("Device:"), w), 0, 0);
    _cb_dev = new QComboBox(w);

    int i=0;
    while (vbi_devices[i] != 0) {
        if(QFileInfo(QString::fromLatin1(vbi_devices[i])).isReadable()) {
            _cb_dev->insertItem(QString::fromLatin1(vbi_devices[i]));
        }
        i++;
    }

    if(!_cfg_dev.isEmpty())
        _cb_dev->setCurrentText(_cfg_dev);

    g->addMultiCellWidget(_cb_dev, 0, 0, 1, 2);

    g->addWidget(new QLabel(i18n("Norm:"), w), 1, 0);
    _cb_norm = new QComboBox(w);
    for (uint i=0; i<VBI_CONFIGS; i++) {
        _cb_norm->insertItem(i18n(vbi_config[i].name));
    }
    _cb_norm->setCurrentItem(_cfg_norm);
    g->addMultiCellWidget(_cb_norm, 1, 1, 1, 2);

    _lb_running = new QLabel("", w);
    _lb_running->setAlignment(Qt::AlignHCenter);
    g->addMultiCellWidget(_lb_running, 2, 2, 0, 2);

    connect(_cb_dev,  SIGNAL( activated(int) ),
            this, SLOT ( changed() ));
    connect(_cb_norm, SIGNAL( activated(int) ),
            this, SLOT ( changed() ));
    changed();

    return w;
}

void VbiDecoderPlugin::saveConfig()
{
    _cfg_dev  = _cb_dev->currentText();
    _cfg_norm = _cb_norm->currentItem();

    _cfg->setGroup("Device");
    _cfg->writeEntry("Device", _cfg_dev);
    _cfg->writeEntry("Norm",   _cfg_norm);
    _cfg->sync();
}

void VbiDecoderPlugin::changed()
{
    _cfg_dev  = _cb_dev->currentText();
    _cfg_norm = _cb_norm->currentItem();

    if (restart()) {
        _lb_running->setText(i18n("Status: VBI decoder is running."));
    } else {
        _lb_running->setText(i18n("Status: VBI decoder is NOT running."));
    }
}

void VbiDecoderPlugin::suspend()
{
    _t->_suspended = true;
}

void VbiDecoderPlugin::resume()
{
    _t->_suspended = false;
}

//------------------------------------------------------------------------------

extern "C" {
    VbiDecoderPlugin* create_libzvbidecoder(Kdetv* ktv, QObject* parent)
    {
        return new VbiDecoderPlugin(ktv, "libzvbi-decoder", parent, "libzvbi vbi decoder");
    }
}

#include "vbidecoder.moc"
