/*
   Copyright (c) 2002 Malte Starostik <malte@kde.org>

   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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include <config-kdetv.h>

#include <errno.h>
//Added by qt3to4:
#include <QCustomEvent>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QEvent>

typedef unsigned char uint8_t;
#define export export_defanged
#include <libzvbi.h>
#undef export

#include <qbitmap.h>
#include <qcursor.h>
#include <qimage.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qthread.h>
#include <qstring.h>

#include <kcursor.h>
#include <kdebug.h>
#include <krun.h>
#include <kaction.h>
#include <klocale.h>
#include <kxmlguifactory.h>
#include <kxmlguiclient.h>
#include <kmainwindow.h>
#include <ktoolbar.h>
#include <ktoggleaction.h>
#include <KActionCollection>
#include "telexadaptor.h"

#include <statusmanager.h>
#include <kdetv.h>
#include <vbimanager.h>

#include <unistd.h>

#include "telex.moc"

#ifdef HAVE_XRENDER_TRANSFORMS
#include <X11/extensions/Xrender.h>
#include <QX11Info>
extern bool qt_use_xrender;
#endif

namespace Telex
{
    bool Page::operator ==( const Page& rhs ) const
    {
        return m_number == rhs.m_number &&
            ( m_sub == rhs.m_sub || m_sub == -1 || rhs.m_sub == -1 );
    }

    class Event : public QCustomEvent
    {
    public:
        Event( const Page& page )
            : QCustomEvent( User ), m_page( page ) {}

        const Page& page() const { return m_page; }

    private:
        Page m_page;
    };

    bool Link::operator ==( const Link& rhs ) const
    {
        if ( rhs.m_type != m_type ) return false;
        if ( m_type == TTX ) return rhs.m_page == m_page;
        else return rhs.m_url == m_url;
    }

    Plugin::Plugin( Kdetv *ktv, QWidget* parent )
        : KXMLGUIClient(),
          KdetvMiscPlugin( ktv, "telex-misc", parent ),
          m_vbimgr( ktv->vbiManager() ),
          m_showAction( 0 ),
          m_transparentAction( 0 ),
          m_pageInput( 0 )
    {
        if ( !parent ) return;
        new TetexAdaptor(this);
        QDBusConnection::sessionBus().registerObject("/Kdetv/Telex", this);


        m_vbimgr->addClient();
        m_display = new Display( parent, this );

        setXMLFile("telexui.rc");
        m_showAction = actionCollection()->add<KToggleAction>("toggle_teletext" );
        m_showAction->setIcon( KIcon("toggle_teletext") );
        m_showAction->setText( i18n( "Show Teletext" ) );
        m_showAction->setChecked(false);
        if (!m_vbimgr->decoding()) {
            m_showAction->setEnabled(false);
        }
        connect( m_showAction, SIGNAL( toggled( bool ) ),
                 this, SLOT( showDisplay( bool ) ) );

        m_transparentAction = actionCollection()->add<KToggleAction>("toggle_teletext_transparent" );
        m_transparentAction->setText(i18n( "Transparent Teletext" ) );
        m_transparentAction->setIcon( KIcon("toggle_teletext_transparent") );
        m_transparentAction->setChecked( false );
        m_transparentAction->setEnabled( false );
        connect( m_transparentAction, SIGNAL( toggled( bool ) ),
                 m_display, SLOT( setTransparent( bool ) ) );

        m_revealAction =actionCollection()->add<KToggleAction>("reveal_hidden_teletext" );
        m_revealAction->setText( i18n( "Reveal Hidden Teletext" ) );
        m_revealAction->setIcon( KIcon("reveal_hidden_teletext") );
        m_revealAction->setChecked( false );
        m_revealAction->setEnabled( false );
        connect( m_revealAction, SIGNAL( toggled( bool ) ),
                 m_display, SLOT( setReveal( bool ) ) );

        connect(m_vbimgr, SIGNAL( ttxPage(int, int, int, bool, bool, bool) ),
                this, SLOT( ttxPageEvent(int, int, int, bool, bool, bool) ));
        connect(m_vbimgr, SIGNAL( running(bool) ),
                this, SLOT( vbiDecoderRunning(bool) ));

        // disable display it will be enabled by the user
        m_display->hide();

        connect( m_display, SIGNAL( navigate( const Link& ) ),
                 SLOT( navigate( const Link& ) ) );

        connect( driver()->statusManager(), SIGNAL( channelChanged() ),
                 SLOT( channelChanged() ) );

        navigate( Page( 100 ) );
    }

    Plugin::~Plugin()
    {
        m_vbimgr->removeClient();
        delete m_display;
    }

    void Plugin::navigate( int page )
    {
        if ( page > 100 )
            navigate( Page( page ) );
    }

    void Plugin::nextPage()
    {
        navigate( m_page.number() + 1 );
    }

    void Plugin::previousPage()
    {
        navigate( m_page.number() - 1 );
    }

    void Plugin::installGUIElements(KXMLGUIFactory *guiFactory, KActionCollection *)
    {
        if ( guiFactory ) {
            guiFactory->addClient(static_cast<KXMLGUIClient*>(this));
        }
    }

    void Plugin::removeGUIElements(KXMLGUIFactory *guiFactory, KActionCollection *)
    {
        if ( guiFactory ) {
            guiFactory->removeClient(static_cast<KXMLGUIClient*>(this));
        }
    }

    bool Plugin::filterNumberKey( int key )
    {
        if (m_display->isHidden())
            return false;
        if ( key == -1 ) return false;
        if ( m_pageInput >= 100 ) m_pageInput = key;
        else m_pageInput = m_pageInput * 10 + key;
        if ( m_pageInput >= 100 ) navigate( Page( m_pageInput ) );
        return true;
    }

    void Plugin::channelChanged()
    {
        if( m_vbimgr->decoding() )
            m_display->setPage( m_page );
    }

    void Plugin::vbiDecoderRunning(bool running)
    {
        if (running) {
            m_showAction->setEnabled(true);
            navigate( Page(100) );
        } else {
            m_showAction->setEnabled(false);
            m_showAction->setChecked(false);
            m_transparentAction->setChecked(false);
            m_transparentAction->setEnabled(false);
            m_revealAction->setChecked( false );
            m_revealAction->setEnabled( false );
            m_display->hide();
        }
    }

    void Plugin::showDisplay(bool p_show)
    {

        if (p_show) {
            m_vbimgr->resume();
            if (!m_vbimgr->decoding()) return;
            m_display->show();
            m_display->raise();
        } else {
            m_display->hide();
            m_vbimgr->suspend();
        }
        m_transparentAction->setEnabled( p_show );
        m_revealAction->setEnabled( p_show );
    }

    void Plugin::toggleShow()
    {
        showDisplay(m_display->isHidden());
        m_showAction->setChecked( !m_display->isHidden() );
    }

    void Plugin::toggleTransparent()
    {
        m_display->setTransparent( !m_display->isTransparent() );
        m_transparentAction->setChecked( m_display->isTransparent() );
    }

    void Plugin::toggleReveal()
    {
        m_display->setReveal( !m_display->isRevealed() );
        m_revealAction->setChecked( m_display->isRevealed() );
    }

    void Plugin::navigate( const Link& link )
    {
        switch ( link.type() ) {
        case Link::TTX:
            if( m_vbimgr->decoding() )
                m_display->setPage( m_page = link.page() );
            break;
        case Link::URL: new KRun( link.url(), NULL );
        default: break;
        }
    }

    void Plugin::ttxPageEvent(int pgno, int subno, int pno, bool roll, bool header, bool clock)
    {
        if ( roll || header || clock ) {
            Page p(vbi_bcd2dec(pgno), vbi_bcd2dec(subno), pno);
            if ( p == m_page ) m_display->setPage( p );
            else m_display->setHeader( p );
        }
    }

    Display::Display( QWidget* parent, Plugin* plugin )
        : QWidget( parent ),
          m_plugin( plugin ),
          m_transparent( false ),
          m_reveal( false ),
          m_columns( 0 ),
          m_rows( 0 )
    {
        setBackgroundMode( Qt::NoBackground );
        parent->installEventFilter( this );
        parent->setMouseTracking( true );
        setMouseTracking( true );
        resize( parent->size() );
#warning "fix setAutoMask( true )"
        // setAutoMask( true );
    }

    Display::~Display()
    {
        parentWidget()->setMouseTracking( false );
        parent()->removeEventFilter( this );
    }

    void Display::setHeader( const Page& page )
    {
        if ( fetch( page, true ) )
            update( 0, 0, width(), height() / 25 );
    }

    void Display::setPage( const Page& page )
    {
        if ( fetch( page, false ) ) {
            m_page = page;
            updateMask();
            update();
        }
    }

    bool Display::fetch( const Page& page, bool headerOnly )
    {
        int rows[] = { 25, 1 };
        vbi_page data;
        if ( !vbi_fetch_vt_page( static_cast<vbi_decoder*>(m_plugin->vbiManager()->internalData()),
                                 &data,
                                 vbi_dec2bcd(page.number()),
                                 page.hasSub() ? vbi_dec2bcd(page.sub()) : VBI_ANY_SUBNO,
                                 VBI_WST_LEVEL_3p5,
                                 rows[ headerOnly ],
                                 !headerOnly ) )
            return false;

        if ( page.pno() != -1 ) {
            QString info = QString::number( m_plugin->page().number() );
            for ( int i = 0; i < 3; ++i ) {
                data.text[ page.pno() + i ].unicode = info[ i ].unicode();
                if ( m_plugin->page() != m_page )
                    // XXX: I want it red, and VBI_BLUE appears as red for me
                    // while VBI_RED appears as blue. Anyone else?
                    data.text[ page.pno() + i ].foreground = VBI_BLUE;
            }
        }

        QImage image( data.columns * 12, data.rows * 10,
                      32, 0, QImage::LittleEndian );

        vbi_draw_vt_page( &data, VBI_PIXFMT_RGBA32_LE,
                          image.bits(), m_reveal, true );

        if ( !headerOnly ) {
            image.setAlphaBuffer( true );
            quint32* p =
                reinterpret_cast< quint32* >( image.scanLine( 10 ) );
            quint32* end = reinterpret_cast< quint32* >(image.scanLine( image.height() - 1 ) );
            int x = 0, y = 10;
            vbi_opacity opacity;
            for ( ; p < end; ++p ) {
                opacity = vbi_opacity(data.text[ y / 10 * data.columns + x / 12 ].opacity );
                switch ( opacity ) {
                case VBI_TRANSPARENT_SPACE:
                    *p = 0;
                    break;
                case VBI_OPAQUE:
                    if ( !m_transparent ) break;
                case VBI_TRANSPARENT_FULL:
                case VBI_SEMI_TRANSPARENT:
                    if ( *p == qRgba( 0, 0, 0, 0xff ) ) *p = 0;
                    break;
                default: break;
                }
                if ( ++x >= image.width() ) {
                    x = 0;
                    ++y;
                }
            }

            m_columns = data.columns;
            m_rows = data.rows;
            vbi_link link;
            m_links.resize( m_rows * m_columns );
            for ( int row = 0; row < m_rows; ++row ) {
                for ( int col = 0; col < m_columns; ++col ) {
                    int index = row * m_columns + col;
                    vbi_resolve_link( &data, col, row, &link );
                    switch ( link.type ) {
                    case VBI_LINK_PAGE:
                    case VBI_LINK_SUBPAGE:
                        m_links[ index ] =
                            Page( vbi_bcd2dec( link.pgno ),
                                  ( link.subno == VBI_ANY_SUBNO ) ?
                                  -1 : link.subno );
                        break;
                    case VBI_LINK_HTTP:
                    case VBI_LINK_FTP:
                    case VBI_LINK_EMAIL:
                        m_links[ index ] = KUrl(reinterpret_cast<const char *>( link.url ) );
                        break;
                    default:
                        m_links[ index ] = Link();
                    }
                }
            }
        }

        QPixmap pixmap;
        pixmap.convertFromImage( image );
        if ( m_pixmap.width() == data.columns * 12 && headerOnly ) {
            bitBlt( &m_pixmap, 0, 0, &pixmap );
#ifdef HAVE_XRENDER_TRANSFORMS
            if ( !qt_use_xrender )
#endif
                {
                    QPixmap scaled;
                    int h = pixmap.height() > 10 ? height() : height() / 25;
                    scaled.convertFromImage( pixmap.convertToImage()
                                             .smoothScale( width(), h ) );
                    bitBlt( &m_scaled, 0, 0, &scaled );
                }
        } else {
            m_pixmap = pixmap;
            updateScale();
        }
        vbi_unref_page(&data);
        return true;
    }

    void Display::updateScale()
    {
#ifdef HAVE_XRENDER_TRANSFORMS
        if ( qt_use_xrender ) {
            int h = m_pixmap.height() > 10 ? height() : height() / 25;
            XTransform transform =
                {
                    {
                        { 1000 * m_pixmap.width() / width(), 0, 0 },
                        { 0, 1000 * m_pixmap.height() / h, 0 },
                        { 0, 0, 1000 }
                    }
                };
            XRenderSetPictureTransform( QX11Info::display(),
                                        m_pixmap.x11RenderHandle(),
                                        &transform );
            if ( m_pixmap.mask() )
                XRenderSetPictureTransform( QX11Info::display(),
                                            m_pixmap.mask()->x11RenderHandle(),
                                            &transform );
            /*            XRenderSetPictureFilter( QX11Info::display(),
                          m_pixmap.x11RenderHandle(),
                          "good", 0, 0 );*/
        } else
#endif
            {
                int h = m_pixmap.height() > 10 ? height() : height() / 25;
                m_scaled.convertFromImage( m_pixmap.convertToImage()
                                           .smoothScale( width(), h ) );
            }
    }

    void Display::setTransparent( bool transparent )
    {
        m_transparent = transparent;
        updateMask();
        setPage( m_page );
    }

    void Display::setReveal( bool reveal )
    {
        m_reveal = reveal;
        updateMask();
        setPage( m_page );
    }

    void Display::updateMask()
    {
        if ( m_pixmap.height() <= 10 )
            setMask( QRect( 0, 0, width(), height() / 25 ) );
        else if ( ! m_pixmap.mask().isNull() ) {
#ifdef HAVE_XRENDER_TRANSFORMS
            if ( qt_use_xrender ) {
                QBitmap mask( width(), height() );
                XRenderComposite( QX11Info::display(), PictOpSrc,
                                  m_pixmap.mask()->x11RenderHandle(), 0,
                                  mask.x11RenderHandle(),
                                  0, 0, 0, 0, 0, 0, width(), height() );
                setMask( mask );
            } else
#endif
                setMask( m_scaled.mask() );
        }
        else clearMask();
    }

    bool Display::eventFilter( QObject*, QEvent* e )
    {
        if ( e->type() == QEvent::Resize ) {
            resize( static_cast< QResizeEvent* >( e )->size() );
            return false;
        }
        if ( e->type() == QEvent::MouseButtonPress )
            mousePressEvent( static_cast< QMouseEvent* >( e ) );
        else if ( e->type() == QEvent::MouseButtonRelease )
            mouseReleaseEvent( static_cast< QMouseEvent* >( e ) );
        else if ( e->type() == QEvent::MouseMove )
            mouseMoveEvent( static_cast< QMouseEvent* >( e ) );

        // TV widget may want to process event too (cursor hiding)
        return false;
    }

    bool Display::event( QEvent* e )
    {
        // This enables RMB menu of parent widget when teletext is shown
#warning "fix this"
        /* if ( e->type() == QEvent::MouseButtonPress )
            qApp->sendEvent(parent(), e);*/
        return QWidget::event(e);
    }

    void Display::resizeEvent( QResizeEvent* e )
    {
        if ( !m_pixmap.isNull() ) updateScale();
        QWidget::resizeEvent( e );
    }

    void Display::paintEvent( QPaintEvent* e )
    {
        if ( !m_pixmap.isNull() ) {
#ifdef HAVE_XRENDER_TRANSFORMS
            if ( qt_use_xrender )
                XRenderComposite( QX11Info::display(), PictOpSrc,
                                  m_pixmap.x11RenderHandle(), 0,
                                  x11RenderHandle(),
                                  e->rect().left(), e->rect().top(),
                                  0, 0,
                                  e->rect().left(), e->rect().top(),
                                  e->rect().width(), e->rect().height() );
            else
#endif
                bitBlt( this, e->rect().topLeft(), &m_scaled, e->rect() );
        }
    }

    void Display::mouseMoveEvent( QMouseEvent* e )
    {
        if (isHidden()) return;

        if ( !m_rows || !m_columns ) return;
        int col = m_columns * e->x() / width(),
            row = m_rows * e->y() / height();
        if ( m_links[ row * m_columns + col ] )
            parentWidget()->setCursor( Qt::PointingHandCursor );
        else parentWidget()->setCursor( Qt::ArrowCursor );
    }

    void Display::mousePressEvent( QMouseEvent* e )
    {
        if (isHidden()) return;

        if ( !m_rows || !m_columns ) return;
        int col = m_columns * e->x() / width(),
            row = m_rows * e->y() / height();
        emit navigate( m_links[ row * m_columns + col ] );
    }
}

extern "C"
{
    KdetvMiscPlugin* create_telex( Kdetv *ktv, QWidget* parent )
    {
        return new Telex::Plugin( ktv, parent );
    }
}

// vim: ts=4 sw=4 et
