/* This file is part of the KMPlayer application
   Copyright (C) 2003 Koos Vriezen <koos.vriezen@xs4all.nl>

   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.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <libgen.h>
#include <dcopclient.h>
#include <tqcstring.h>
#include <tqtimer.h>
#include <tqfile.h>
#include <tqurl.h>
#include <tqthread.h>
#include <tqmutex.h>
#include <tqdom.h>
#include "kmplayer_backend.h"
#include "kmplayer_callback_stub.h"
#include "kmplayer_callback.h"
#include "xineplayer.h"
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>

#include <xine.h>
#include <xine/xineutils.h>

#ifndef XShmGetEventBase
extern int XShmGetEventBase(Display *);
#endif

#define MWM_HINTS_DECORATIONS   (1L << 1)
#define PROP_MWM_HINTS_ELEMENTS 5
typedef struct {
    uint32_t  flags;
    uint32_t  functions;
    uint32_t  decorations;
    int32_t   input_mode;
    uint32_t  status;
} MWMHints;


static KXinePlayer * xineapp;
static KMPlayer::Callback_stub * callback;
static TQMutex mutex (true);

static xine_t              *xine;
static xine_stream_t       *stream;
static xine_stream_t       *sub_stream;
static xine_video_port_t   *vo_port;
static xine_audio_port_t   *ao_port;
static xine_post_t         *post_plugin;
static xine_event_queue_t  *event_queue;
static xine_cfg_entry_t     audio_vis_cfg_entry;
static x11_visual_t         vis;
static char                 configfile[2048];
static Atom                 quit_atom;

static Display             *display;
static Window               wid;
static bool                 window_created;
static bool                 xine_verbose;
static bool                 xine_vverbose;
static bool                 wants_config;
static bool                 audio_vis;
static int                  screen;
static int                  completion_event;
static int                  repeat_count;
static int                  xpos, ypos, width, height;
static int                  movie_width, movie_height, movie_length, movie_pos;
static int                  movie_brightness = 32767;
static int                  movie_contrast = 32767;
static int                  movie_hue = 32767;
static int                  movie_saturation = 32767;
static int                  movie_volume = 32767;
static double               pixel_aspect;

static int                  running = 0;
static volatile int         firstframe = 0;
static const int            event_finished = TQEvent::User;
static const int            event_progress = TQEvent::User + 2;
static const int            event_url = TQEvent::User + 3;
static const int            event_size = TQEvent::User + 4;
static const int            event_title = TQEvent::User + 5;
static const int            event_video = TQEvent::User + 6;
static TQString mrl;
static TQString sub_mrl;
static TQString rec_mrl;
static TQString alang, slang;
static TQStringList alanglist, slanglist;

static TQString elmentry ("entry");
static TQString elmitem ("item");
static TQString attname ("name");
static TQString atttype ("type");
static TQString attdefault ("DEFAULT");
static TQString attvalue ("value");
static TQString attstart ("START");
static TQString attend ("end");
static TQString valrange ("range");
static TQString valnum ("num");
static TQString valbool ("bool");
static TQString valenum ("enum");
static TQString valstring ("string");

extern "C" {

static void dest_size_cb(void * /*data*/, int /*video_width*/, int /*video_height*/, double /*video_pixel_aspect*/,
        int *dest_width, int *dest_height, double *dest_pixel_aspect)  {

    *dest_width        = width;
    *dest_height       = height;
    *dest_pixel_aspect = pixel_aspect;
}

static void frame_output_cb(void * /*data*/, int /*video_width*/, int /*video_height*/,
        double /*video_pixel_aspect*/, int *dest_x, int *dest_y,
        int *dest_width, int *dest_height, 
        double *dest_pixel_aspect, int *win_x, int *win_y) {
    if (running && firstframe) {
        firstframe = 0;
        int pos;
        fprintf(stderr, "first frame\n");
        mutex.lock ();
        xine_get_pos_length (stream, 0, &pos, &movie_length);
        movie_width = xine_get_stream_info(stream, XINE_STREAM_INFO_VIDEO_WIDTH);
        movie_height = xine_get_stream_info(stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
        mutex.unlock ();
        TQApplication::postEvent (xineapp, new XineMovieParamEvent (movie_length, movie_width, movie_height, alanglist, slanglist, true));
        
    }

    *dest_x            = 0;
    *dest_y            = 0;
    *win_x             = xpos;
    *win_y             = ypos;
    *dest_width        = width;
    *dest_height       = height;
    *dest_pixel_aspect = pixel_aspect;
}

static void xine_config_cb (void * /*user_data*/, xine_cfg_entry_t * entry) {
    fprintf (stderr, "xine_config_cb %s\n", entry->enum_values[entry->num_value]);
    if (!stream)
        return;
    mutex.lock ();
    if (post_plugin) {
        xine_post_wire_audio_port (xine_get_audio_source (stream), ao_port);
        xine_post_dispose (xine, post_plugin);
        post_plugin = 0L;
    }
    if (audio_vis && strcmp (entry->enum_values[entry->num_value], "none")) {
        post_plugin = xine_post_init (xine, entry->enum_values[entry->num_value], 0, &ao_port, &vo_port);
        xine_post_wire (xine_get_audio_source (stream), (xine_post_in_t *) xine_post_input (post_plugin, (char *) "audio in"));
    }
    mutex.unlock ();
}

static void event_listener(void * /*user_data*/, const xine_event_t *event) {
    if (event->stream != stream)
        return; // not interested in sub_stream events
    switch(event->type) { 
        case XINE_EVENT_UI_PLAYBACK_FINISHED:
            fprintf (stderr, "XINE_EVENT_UI_PLAYBACK_FINISHED\n");
            if (repeat_count-- > 0)
                xine_play (stream, 0, 0);
            else
                TQApplication::postEvent (xineapp, new TQEvent ((TQEvent::Type) event_finished));
            break;
        case XINE_EVENT_PROGRESS:
            TQApplication::postEvent (xineapp, new XineProgressEvent (((xine_progress_data_t *) event->data)->percent));
            break;
        case XINE_EVENT_MRL_REFERENCE:
            fprintf(stderr, "XINE_EVENT_MRL_REFERENCE %s\n", 
            ((xine_mrl_reference_data_ext_t*)event->data)->mrl);
            TQApplication::postEvent (xineapp, new XineURLEvent (TQString::fromLocal8Bit (((xine_mrl_reference_data_ext_t*)event->data)->mrl)));
            break;
        case XINE_EVENT_FRAME_FORMAT_CHANGE:
            fprintf (stderr, "XINE_EVENT_FRAME_FORMAT_CHANGE\n");
            break;
        case XINE_EVENT_UI_SET_TITLE:
            {
                xine_ui_data_t * data = (xine_ui_data_t *) event->data;
                TQApplication::postEvent(xineapp, new XineTitleEvent(data->str));
                fprintf (stderr, "Set title event %s\n", data->str);
            }
            break;
        case XINE_EVENT_UI_CHANNELS_CHANGED: {
            fprintf (stderr, "Channel changed event %d\n", firstframe);
            mutex.lock ();
            int w = xine_get_stream_info(stream, XINE_STREAM_INFO_VIDEO_WIDTH);
            int h = xine_get_stream_info(stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
            int pos, l, nr;
            xine_get_pos_length (stream, 0, &pos, &l);
            char * langstr = new char [66];
            alanglist.clear ();
            slanglist.clear ();

            nr =xine_get_stream_info(stream,XINE_STREAM_INFO_MAX_AUDIO_CHANNEL);
            // if nrch > 25) nrch = 25
            for (int i = 0; i < nr; ++i) {
                if (!xine_get_audio_lang (stream, i, langstr))
                    continue;
                TQString ls = TQString(TQString::fromLocal8Bit (langstr)).stripWhiteSpace();
                if (ls.isEmpty ())
                    continue;
                if (!slang.isEmpty () && alang == ls)
                    xine_set_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, i);
                alanglist.push_back (ls);
                fprintf (stderr, "alang %s\n", langstr);
            }
            nr = xine_get_stream_info(stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL);
            // if nrch > 25) nrch = 25
            for (int i = 0; i < nr; ++i) {
                if (!xine_get_spu_lang (stream, i, langstr))
                    continue;
                TQString ls = TQString(TQString::fromLocal8Bit (langstr)).stripWhiteSpace();
                if (ls.isEmpty ())
                    continue;
                if (!slang.isEmpty () && slang == ls)
                    xine_set_param (stream, XINE_PARAM_SPU_CHANNEL, i);
                slanglist.push_back (ls);
                fprintf (stderr, "slang %s\n", langstr);
            }
            delete langstr;
            mutex.unlock ();
            movie_width = w;
            movie_height = h;
            movie_length = l;
            TQApplication::postEvent (xineapp, new XineMovieParamEvent (l, w, h, alanglist, slanglist, firstframe));
            if (running && firstframe)
                firstframe = 0;
            if (window_created && w > 0 && h > 0) {
                XLockDisplay (display);
                XResizeWindow (display, wid, movie_width, movie_height);
                XFlush (display);
                XUnlockDisplay (display);
            }
            break;
        }
        case XINE_EVENT_INPUT_MOUSE_MOVE:
            break;
        default:
            fprintf (stderr, "event_listener %d\n", event->type);

    }
}

} // extern "C"

using namespace KMPlayer;

Backend::Backend ()
    : DCOPObject (TQCString ("Backend")) {
}

Backend::~Backend () {}

void Backend::setURL (TQString url) {
    mrl = url;
}

void Backend::setSubTitleURL (TQString url) {
    sub_mrl = url;
}

void Backend::play (int repeat_count) {
    xineapp->play (repeat_count);
}

void Backend::stop () {
    TQTimer::singleShot (0, xineapp, TQT_SLOT (stop ()));
}

void Backend::pause () {
    xineapp->pause ();
}

void Backend::seek (int pos, bool /*absolute*/) {
    xineapp->seek (pos);
}

void Backend::hue (int h, bool) {
    xineapp->hue (65535 * (h + 100) / 200);
}

void Backend::saturation (int s, bool) {
    xineapp->saturation (65535 * (s + 100) / 200);
}

void Backend::contrast (int c, bool) {
    xineapp->contrast (65535 * (c + 100) / 200);
}

void Backend::brightness (int b, bool) {
    xineapp->brightness (65535 * (b + 100) / 200);
}

void Backend::volume (int v, bool) {
    xineapp->volume (v);
}

void Backend::frequency (int) {
}

void Backend::setAudioLang (int id, TQString al) {
    xineapp->setAudioLang (id, al);
}

void Backend::setSubtitle (int id, TQString sl) {
    xineapp->setSubtitle (id, sl);
}

void Backend::quit () {
    delete callback;
    callback = 0L;
    if (running)
        stop ();
    else
        TQTimer::singleShot (0, tqApp, TQT_SLOT (quit ()));
}

bool updateConfigEntry (const TQString & name, const TQString & value) {
    fprintf (stderr, "%s=%s\n", name.ascii (), (const char *) value.local8Bit ());
    bool changed = false;
    xine_cfg_entry_t cfg_entry;
    if (!xine_config_lookup_entry (xine, name.ascii (), &cfg_entry))
        return false;
    if (cfg_entry.type == XINE_CONFIG_TYPE_STRING ||
            cfg_entry.type == XINE_CONFIG_TYPE_UNKNOWN) {
        changed = strcmp (cfg_entry.str_value, value.ascii ());
        cfg_entry.str_value = (char *) value.ascii ();
    } else {
        changed = cfg_entry.num_value != value.toInt ();
        cfg_entry.num_value = value.toInt ();
    }
    xine_config_update_entry (xine,  &cfg_entry);
    return changed;
}

void Backend::setConfig (TQByteArray data) {
    TQString err;
    int line, column;
    TQDomDocument dom;
    if (dom.setContent (data, false, &err, &line, &column)) {
        if (dom.childNodes().length() == 1) {
            for (TQDomNode node = dom.firstChild().firstChild();
                    !node.isNull ();
                    node = node.nextSibling ()) {
                TQDomNamedNodeMap attr = node.attributes ();
                updateConfigEntry (attr.namedItem (attname).nodeValue (),
                                   attr.namedItem (attvalue).nodeValue ());
            }
            xine_config_save (xine, configfile);
        } else
            err = TQString ("invalid data");
    }
    if (callback)
        callback->errorMessage (0, err);
}

bool Backend::isPlaying () {
    mutex.lock ();
    bool b = running &&
        (xine_get_status (stream) == XINE_STATUS_PLAY) &&
        (xine_get_param (stream, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE);
    mutex.unlock ();
    return b;
}

KXinePlayer::KXinePlayer (int _argc, char ** _argv)
  : TQApplication (_argc, _argv, false) {
}

void KXinePlayer::init () {
    xpos    = 0;
    ypos    = 0;
    width   = 320;
    height  = 200;

    XLockDisplay(display);
    if (window_created)
        wid = XCreateSimpleWindow(display, XDefaultRootWindow(display),
                xpos, ypos, width, height, 1, 0, 0);
    XSelectInput (display, wid,
                  (PointerMotionMask | ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask)); // | SubstructureNotifyMask));
    XWindowAttributes attr;
    XGetWindowAttributes(display, wid, &attr);
    width = attr.width;
    height = attr.height;
    if (XShmQueryExtension(display) == True)
        completion_event = XShmGetEventBase(display) + ShmCompletion;
    else
        completion_event = -1;
    if (window_created) {
        fprintf (stderr, "map %lu\n", wid);
        XMapRaised(display, wid);
        XSync(display, False);
    }
    //double d->res_h = 1.0 * DisplayWidth(display, screen) / DisplayWidthMM(display, screen);
    //double d->res_v = 1.0 * DisplayHeight(display, screen) / DisplayHeightMM(display, screen);
    XUnlockDisplay(display);
    vis.display           = display;
    vis.screen            = screen;
    vis.d                 = wid;
    vis.dest_size_cb      = dest_size_cb;
    vis.frame_output_cb   = frame_output_cb;
    vis.user_data         = NULL;
    //pixel_aspect          = d->res_v / d->res_h;

    //if(fabs(pixel_aspect - 1.0) < 0.01)
        pixel_aspect = 1.0;

    const char *const * pp = xine_list_post_plugins_typed (xine, XINE_POST_TYPE_AUDIO_VISUALIZATION);
    int i;
    for (i = 0; pp[i]; i++);
    const char ** options = new const char * [i+2];
    options[0] = "none";
    for (i = 0; pp[i]; i++)
        options[i+1] = pp[i];
    options[i+1] = 0L;
    xine_config_register_enum (xine, "audio.visualization", 0, (char ** ) options, 0L, 0L, 0, xine_config_cb, 0L);
    if (!callback)
        TQTimer::singleShot (10, this, TQT_SLOT (play ()));
}

KXinePlayer::~KXinePlayer () {
    if (window_created) {
        XLockDisplay (display);
        fprintf (stderr, "unmap %lu\n", wid);
        XUnmapWindow (display,  wid);
        XDestroyWindow(display,  wid);
        XSync (display, False);
        XUnlockDisplay (display);
    }
    xineapp = 0L;
}

void getConfigEntries (TQByteArray & buf) {
    xine_cfg_entry_t entry;
    TQDomDocument doc;
    TQDomElement root = doc.createElement (TQString ("document"));
    for (int i = xine_config_get_first_entry (xine, &entry);
            i;
            i = xine_config_get_next_entry (xine, &entry)) {
        TQDomElement elm = doc.createElement (elmentry);
        elm.setAttribute (attname, TQString (entry.key));
        if (entry.type == XINE_CONFIG_TYPE_STRING || entry.type == XINE_CONFIG_TYPE_UNKNOWN) {
            elm.setAttribute (atttype, valstring);
            elm.setAttribute (attvalue, TQString (entry.str_value));
        } else {
            elm.setAttribute (attdefault, TQString::number (entry.num_default));
            elm.setAttribute (attvalue, TQString::number (entry.num_value));
            switch (entry.type) {
                case XINE_CONFIG_TYPE_RANGE:
                    elm.setAttribute (atttype, valrange);
                    elm.setAttribute (attstart, TQString::number (entry.range_min));
                    elm.setAttribute (attend, TQString::number (entry.range_max));
                    break;
                case XINE_CONFIG_TYPE_ENUM:
                    elm.setAttribute (atttype, valenum);
                    for (int i = 0; entry.enum_values[i]; i++) {
                        TQDomElement item = doc.createElement (elmitem);
                        item.setAttribute (attvalue, TQString (entry.enum_values[i]));
                        elm.appendChild (item);
                    }
                    break;
                case XINE_CONFIG_TYPE_NUM:
                    elm.setAttribute (atttype, valnum);
                    break;
                case XINE_CONFIG_TYPE_BOOL:
                    elm.setAttribute (atttype, valbool);
                    break;
                default:
                    fprintf (stderr, "unhandled config type: %d\n", entry.type);
            }
        }
        if (entry.help)
            elm.appendChild (doc.createTextNode (TQString::fromUtf8 (entry.help)));
        root.appendChild (elm);
    }
    doc.appendChild (root);
    TQString exp = doc.toString ();
    TQCString cexp = exp.utf8 ();
    buf.duplicate (cexp);
    buf.resize (cexp.length ()); // strip terminating \0
}

void KXinePlayer::play (int repeat) {
    fprintf (stderr, "play mrl: '%s'\n", (const char *) mrl.local8Bit ());
    mutex.lock ();
    repeat_count = repeat;
    if (running) {
        if (xine_get_status (stream) == XINE_STATUS_PLAY &&
            xine_get_param (stream, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE)
            xine_set_param( stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
        mutex.unlock ();
        return;
    }
    movie_pos = 0;
    movie_width = 0;
    movie_height = 0;

    if (mrl.startsWith ("cdda://"))
        mrl = TQString ("cdda:/") + mrl.mid (7);
    stream = xine_stream_new (xine, ao_port, vo_port);
    event_queue = xine_event_new_queue (stream);
    xine_event_create_listener_thread (event_queue, event_listener, NULL);
    if (mrl == "cdda:/") {
        int nr;
        const char * const* mrls = xine_get_autoplay_mrls (xine, "CD", &nr);
        running = 1;
        for (int i = 0; i < nr; i++) {
            TQString m (mrls[i]);
            TQString title;
            if (xine_open (stream, mrls[i])) {
                const char * t = xine_get_meta_info (stream, XINE_META_INFO_TITLE);
                if (t && t[0])
                    title = TQString::fromUtf8 (t);
                xine_close (stream);
            }
            if (callback)
                callback->subMrl (m, title);
            else
                printf ("track %s\n", m.utf8 ().data ());
        }
        mutex.unlock ();
        finished ();
        return;
    }

    xine_port_send_gui_data(vo_port, XINE_GUI_SEND_VIDEOWIN_VISIBLE, (void *) 1);

    running = 1;
    TQString mrlsetup = mrl;
    if (!rec_mrl.isEmpty ()) {
        char * rm = strdup (rec_mrl.local8Bit ());
        char *bn = basename (rm);
        char *dn = dirname (rm);
        if (bn)
            updateConfigEntry (TQString ("media.capture.save_dir"), TQString::fromLocal8Bit (dn));
        mrlsetup += TQString ("#save:") + TQString::fromLocal8Bit (bn);
        free (rm);
    }
    if (!xine_open (stream, (const char *) mrlsetup.local8Bit ())) {
        fprintf(stderr, "Unable to open mrl '%s'\n", (const char *) mrl.local8Bit ());
        mutex.unlock ();
        finished ();
        return;
    }
    xine_set_param (stream, XINE_PARAM_VO_SATURATION, movie_saturation);
    xine_set_param (stream, XINE_PARAM_VO_BRIGHTNESS, movie_brightness);
    xine_set_param (stream, XINE_PARAM_VO_CONTRAST, movie_contrast);
    xine_set_param (stream, XINE_PARAM_VO_HUE, movie_hue);

    if (!sub_mrl.isEmpty ()) {
        fprintf(stderr, "Using subtitles from '%s'\n", (const char *) sub_mrl.local8Bit ());
        sub_stream = xine_stream_new (xine, NULL, vo_port);
        if (xine_open (sub_stream, (const char *) sub_mrl.local8Bit ())) {
            xine_stream_master_slave (stream, sub_stream,
                    XINE_MASTER_SLAVE_PLAY | XINE_MASTER_SLAVE_STOP);
        } else {
            fprintf(stderr, "Unable to open subtitles from '%s'\n", (const char *) sub_mrl.local8Bit ());
            xine_dispose (sub_stream);
            sub_stream = 0L;
        }
    } 
    if (!xine_play (stream, 0, 0)) {
        fprintf(stderr, "Unable to play mrl '%s'\n", (const char *) mrl.local8Bit ());
        mutex.unlock ();
        finished ();
        return;
    }
    audio_vis = false;
    if (xine_get_stream_info (stream, XINE_STREAM_INFO_HAS_VIDEO))
        TQApplication::postEvent(xineapp, new TQEvent((TQEvent::Type)event_video));
    else
        audio_vis = xine_config_lookup_entry
            (xine, "audio.visualization", &audio_vis_cfg_entry);
    mutex.unlock ();
    if (audio_vis)
        xine_config_cb (0L, &audio_vis_cfg_entry);
    if (callback)
        firstframe = 1;
}

void KXinePlayer::stop () {
    if (!running) return;
    fprintf(stderr, "stop\n");
    mutex.lock ();
    repeat_count = 0;
    if (sub_stream)
        xine_stop (sub_stream);
    xine_stop (stream);
    mutex.unlock ();
    TQTimer::singleShot (10, this, TQT_SLOT (postFinished ()));
}

void KXinePlayer::postFinished () {
    TQApplication::postEvent (xineapp, new TQEvent ((TQEvent::Type) event_finished));
}

void KXinePlayer::pause () {
    if (!running) return;
    mutex.lock ();
    if (xine_get_status (stream) == XINE_STATUS_PLAY) {
        if (xine_get_param (stream, XINE_PARAM_SPEED) == XINE_SPEED_PAUSE)
            xine_set_param (stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
        else
            xine_set_param (stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
    }
    mutex.unlock ();
}

void KXinePlayer::finished () {
    TQTimer::singleShot (10, this, TQT_SLOT (stop ()));
}

void KXinePlayer::setAudioLang (int id, const TQString & al) {
    alang = al;
    mutex.lock ();
    if (xine_get_status (stream) == XINE_STATUS_PLAY)
        xine_set_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, id);
    mutex.unlock ();
}

void KXinePlayer::setSubtitle (int id, const TQString & sl) {
    slang = sl;
    mutex.lock ();
    if (xine_get_status (stream) == XINE_STATUS_PLAY)
        xine_set_param (stream, XINE_PARAM_SPU_CHANNEL, id);
    mutex.unlock ();
}

void KXinePlayer::updatePosition () {
    if (!running || !callback) return;
    int pos;
    mutex.lock ();
    xine_get_pos_length (stream, 0, &pos, &movie_length);
    mutex.unlock ();
    if (movie_pos != pos) {
        movie_pos = pos;
        callback->moviePosition (pos/100);
    }
    TQTimer::singleShot (500, this, TQT_SLOT (updatePosition ()));
}

void KXinePlayer::saturation (int val) {
    movie_saturation = val;
    if (running) {
        mutex.lock ();
        xine_set_param (stream, XINE_PARAM_VO_SATURATION, val);
        mutex.unlock ();
    }
}

void KXinePlayer::hue (int val) {
    movie_hue = val;
    if (running) {
        mutex.lock ();
        xine_set_param (stream, XINE_PARAM_VO_HUE, val);
        mutex.unlock ();
    }
}

void KXinePlayer::contrast (int val) {
    movie_contrast = val;
    if (running) {
        mutex.lock ();
        xine_set_param (stream, XINE_PARAM_VO_CONTRAST, val);
        mutex.unlock ();
    }
}

void KXinePlayer::brightness (int val) {
    movie_brightness = val;
    if (running) {
        mutex.lock ();
        xine_set_param (stream, XINE_PARAM_VO_BRIGHTNESS, val);
        mutex.unlock ();
    }
}

void KXinePlayer::volume (int val) {
    movie_volume = val;
    if (running) {
        mutex.lock ();
        xine_set_param( stream, XINE_PARAM_AUDIO_VOLUME, val);
        mutex.unlock ();
    }
}

void KXinePlayer::seek (int val) {
    if (running) {
        fprintf(stderr, "seek %d\n", val);
        mutex.lock ();
        if (!xine_play (stream, 0, val * 100)) {
            fprintf(stderr, "Unable to seek to %d :-(\n", val);
        }
        mutex.unlock ();
    }
}

bool KXinePlayer::event (TQEvent * e) {
    switch (e->type()) {
        case event_finished: {
            fprintf (stderr, "event_finished\n");
            if (audio_vis) {
                audio_vis_cfg_entry.num_value = 0;
                xine_config_cb (0L, &audio_vis_cfg_entry);
            }
            mutex.lock ();
            running = 0;
            firstframe = 0;
            if (sub_stream) {
                xine_dispose (sub_stream);
                sub_stream = 0L;
            }
            if (stream) {
                xine_event_dispose_queue (event_queue);
                xine_dispose (stream);
                stream = 0L;
            }
            mutex.unlock ();
            //XLockDisplay (display);
            //XClearWindow (display, wid);
            //XUnlockDisplay (display);
            if (callback)
                callback->finished ();
            else
                TQTimer::singleShot (0, this, TQT_SLOT (quit ()));
            break;
        }
        case event_size: {
            if (callback) {
                XineMovieParamEvent * se = static_cast <XineMovieParamEvent *> (e);
                if (se->length < 0) se->length = 0;
                callback->movieParams (se->length/100, se->width, se->height, se->height ? 1.0*se->width/se->height : 1.0, se->alang, se->slang);
                if (se->first_frame) {
                    callback->playing ();
                    TQTimer::singleShot (500, this, TQT_SLOT (updatePosition ()));
                }
            }
            break;
        }
        case event_progress: {
            XineProgressEvent * pe = static_cast <XineProgressEvent *> (e);                
            if (callback)
                callback->loadingProgress (pe->progress);
            break;
        }
        case event_url: {
            XineURLEvent * ue = static_cast <XineURLEvent *> (e);                
            if (callback)
                callback->subMrl (ue->url, TQString ());
            break;
        }
        case event_title: {
            XineTitleEvent * ue = static_cast <XineTitleEvent *> (e);                
            if (callback)
                callback->statusMessage ((int) KMPlayer::Callback::stat_newtitle, ue->title);
            break;
        }
        case event_video:
            if (callback)
                callback->statusMessage ((int) KMPlayer::Callback::stat_hasvideo, TQString ());
            break;
        default:
            return false;
    }
    return true;
}

void KXinePlayer::saveState (TQSessionManager & sm) {
    if (callback)
        sm.setRestartHint (TQSessionManager::RestartNever);
}

XineMovieParamEvent::XineMovieParamEvent(int l, int w, int h, const TQStringList & a, const TQStringList & s, bool ff)
  : TQEvent ((TQEvent::Type) event_size),
    length (l), width (w), height (h), alang (a), slang (s) , first_frame (ff)
{}

XineURLEvent::XineURLEvent (const TQString & u)
  : TQEvent ((TQEvent::Type) event_url), url (u) 
{}

XineTitleEvent::XineTitleEvent (const char * t)
  : TQEvent ((TQEvent::Type) event_title), title (TQString::fromUtf8 (t)) 
{
    TQUrl::decode (title);
}

XineProgressEvent::XineProgressEvent (const int p)
  : TQEvent ((TQEvent::Type) event_progress), progress (p) 
{}

//static bool translateCoordinates (int wx, int wy, int mx, int my) {
//    movie_width
class XEventThread : public TQThread {
protected:
    void run () {
        Time prev_click_time = 0;
        int prev_click_x = 0;
        int prev_click_y = 0;
        while (true) {
            XEvent   xevent;
            XNextEvent(display, &xevent);
            switch(xevent.type) {
                case ClientMessage:
                    if (xevent.xclient.message_type == quit_atom) {
                        fprintf(stderr, "request quit\n");
                        return;
                    }
                    break;
                case KeyPress:
                    {
                        XKeyEvent  kevent;
                        KeySym     ksym;
                        char       kbuf[256];
                        int        len;

                        kevent = xevent.xkey;

                        XLockDisplay(display);
                        len = XLookupString(&kevent, kbuf, sizeof(kbuf), &ksym, NULL);
                        XUnlockDisplay(display);
                        fprintf(stderr, "keypressed 0x%x 0x%x\n", kevent.keycode, ksym);

                        switch (ksym) {

                            case XK_q:
                            case XK_Q:
                                xineapp->lock ();
                                xineapp->stop ();
                                xineapp->unlock ();
                                break;

                            case XK_p: // previous
                                mutex.lock ();
                                if (stream) {
                                    xine_event_t xine_event;
                                    memset(&xine_event, 0, sizeof(xine_event));
                                    xine_event.type = XINE_EVENT_INPUT_PREVIOUS;
                                    xine_event.stream = stream;
                                    xine_event_send (stream, &xine_event);
                                } 
                                mutex.unlock ();
                                break;

                            case XK_n: // next
                                mutex.lock ();
                                if (stream) {
                                    xine_event_t xine_event;
                                    memset(&xine_event, 0, sizeof(xine_event));
                                    xine_event.type = XINE_EVENT_INPUT_NEXT;
                                    xine_event.stream = stream;
                                    xine_event_send (stream, &xine_event);
                                } 
                                mutex.unlock ();
                                break;

                            case XK_u: // up menu
                                mutex.lock ();
                                if (stream) {
                                    xine_event_t xine_event;
                                    memset(&xine_event, 0, sizeof(xine_event));
                                    xine_event.type = XINE_EVENT_INPUT_MENU1;
                                    xine_event.stream = stream;
                                    xine_event_send (stream, &xine_event);
                                } 
                                mutex.unlock ();
                                break;

                            case XK_r: // root menu
                                mutex.lock ();
                                if (stream) {
                                    xine_event_t xine_event;
                                    memset(&xine_event, 0, sizeof(xine_event));
                                    xine_event.type = XINE_EVENT_INPUT_MENU3;
                                    xine_event.stream = stream;
                                    xine_event_send (stream, &xine_event);
                                } 
                                mutex.unlock ();
                                break;

                            case XK_Up:
                                xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME,
                                        (xine_get_param(stream, XINE_PARAM_AUDIO_VOLUME) + 1));
                                break;

                            case XK_Down:
                                xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME,
                                        (xine_get_param(stream, XINE_PARAM_AUDIO_VOLUME) - 1));
                                break;

                            case XK_plus:
                                xine_set_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, 
                                        (xine_get_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL) + 1));
                                break;

                            case XK_minus:
                                xine_set_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, 
                                        (xine_get_param(stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL) - 1));
                                break;

                            case XK_space:
                                if(xine_get_param(stream, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE)
                                    xine_set_param(stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
                                else
                                    xine_set_param(stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
                                break;

                        }
                    }
                    break;

                case Expose:
                    if(xevent.xexpose.count != 0 || !stream || xevent.xexpose.window != wid)
                        break;
                    mutex.lock ();
                    xine_port_send_gui_data(vo_port, XINE_GUI_SEND_EXPOSE_EVENT, &xevent);
                    mutex.unlock ();
                    break;

                case ConfigureNotify:
                    {
                        Window           tmp_win;

                        width  = xevent.xconfigure.width;
                        height = xevent.xconfigure.height;
                        if((xevent.xconfigure.x == 0) && (xevent.xconfigure.y == 0)) {
                            XLockDisplay(display);
                            XTranslateCoordinates(display, xevent.xconfigure.window,
                                    DefaultRootWindow(xevent.xconfigure.display),
                                    0, 0, &xpos, &ypos, &tmp_win);
                            XUnlockDisplay(display);
                        }
                        else {
                            xpos = xevent.xconfigure.x;
                            ypos = xevent.xconfigure.y;
                        }
                    }

                    break;
                case MotionNotify:
                    if (stream) {
                        XMotionEvent *mev = (XMotionEvent *) &xevent;
                        x11_rectangle_t rect = { mev->x, mev->y, 0, 0 };
                        if (xine_port_send_gui_data(vo_port, XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO, (void*) &rect) == -1)
                            break;
                        xine_input_data_t data;
                        data.x = rect.x;
                        data.y = rect.y;
                        data.button = 0;
                        xine_event_t xine_event;
                        memset(&xine_event, 0, sizeof(xine_event));
                        xine_event.type = XINE_EVENT_INPUT_MOUSE_MOVE;
                        xine_event.stream = stream;
                        xine_event.data = &data;
                        xine_event.data_length = sizeof (xine_input_data_t);
                        mutex.lock ();
                        xine_event_send (stream, &xine_event);
                        mutex.unlock ();
                    }
                    break;
                case ButtonPress: {
                    XButtonEvent *bev = (XButtonEvent *) &xevent;
                    int dx = prev_click_x - bev->x;
                    int dy = prev_click_y - bev->y;
                    if (bev->time - prev_click_time < 400 &&
                            (dx * dx + dy * dy) < 25) {
                        xineapp->lock ();
                        if (callback)
                            callback->toggleFullScreen ();
                        xineapp->unlock ();
                    }
                    prev_click_time = bev->time;
                    prev_click_x = bev->x;
                    prev_click_y = bev->y;
                    if (stream) {
                        fprintf(stderr, "ButtonPress\n");
                        XButtonEvent *bev = (XButtonEvent *) &xevent;
                        x11_rectangle_t rect = { bev->x, bev->y, 0, 0 };
                        if (xine_port_send_gui_data(vo_port, XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO, (void*) &rect) == -1)
                            break;
                        xine_input_data_t data;
                        data.x = rect.x;
                        data.y = rect.y;
                        data.button = 1;
                        xine_event_t xine_event;
                        memset(&xine_event, 0, sizeof(xine_event));
                        xine_event.type = XINE_EVENT_INPUT_MOUSE_BUTTON;
                        xine_event.stream = stream;
                        xine_event.data = &data;
                        xine_event.data_length = sizeof (xine_input_data_t);
                        mutex.lock ();
                        xine_event_send (stream, &xine_event);
                        mutex.unlock ();
                    }
                    break;
                }
                case NoExpose:
                    //fprintf (stderr, "NoExpose %lu\n", xevent.xnoexpose.drawable);
                    break;
                case CreateNotify:
                    fprintf (stderr, "CreateNotify: %lu %lu %d,%d %dx%d\n",
                            xevent.xcreatewindow.window, xevent.xcreatewindow.parent,
                            xevent.xcreatewindow.x, xevent.xcreatewindow.y,
                            xevent.xcreatewindow.width, xevent.xcreatewindow.height);
                    break;
                case DestroyNotify:
                    fprintf (stderr, "DestroyNotify: %lu\n", xevent.xdestroywindow.window);
                    break;
                default:
                    if (xevent.type < LASTEvent)
                        fprintf (stderr, "event %d\n", xevent.type);
            }

            if(xevent.type == completion_event && stream)
                xine_port_send_gui_data(vo_port, XINE_GUI_SEND_COMPLETION_EVENT, &xevent);
        }
    }
};

int main(int argc, char **argv) {
    const char *dvd_device = 0L;
    const char *vcd_device = 0L;
    const char *grab_device = 0L;
    if (!XInitThreads ()) {
        fprintf (stderr, "XInitThreads () failed\n");
        return 1;
    }
    display = XOpenDisplay(NULL);
    screen  = XDefaultScreen(display);
    quit_atom = XInternAtom (display, "kxineplayer_quit", false);

    snprintf(configfile, sizeof (configfile), "%s%s", xine_get_homedir(), "/.xine/config2");
    xineapp = new KXinePlayer (argc, argv);
    window_created = true;
    TQString vo_driver ("auto");
    TQString ao_driver ("auto");
    for (int i = 1; i < argc; i++) {
        if (!strcmp (argv [i], "-vo") && ++i < argc) {
            vo_driver = argv [i];
        } else if (!strcmp (argv [i], "-ao") && ++i < argc) {
            ao_driver = argv [i];
        } else if (!strcmp (argv [i], "-dvd-device") && ++i < argc) {
            dvd_device = argv [i];
        } else if (!strcmp (argv [i], "-vcd-device") && ++i < argc) {
            vcd_device = argv [i];
        } else if (!strcmp (argv [i], "-vd") && ++i < argc) {
            grab_device = argv [i];
        } else if ((!strcmp (argv [i], "-wid") ||
                    !strcmp (argv [i], "-window-id")) && ++i < argc) {
            wid = atol (argv [i]);
            window_created = false;
        } else if (!strcmp (argv [i], "-root")) {
            wid =  XDefaultRootWindow (display);
            window_created = false;
        } else if (!strcmp (argv [i], "-window")) {
            ;
        } else if (!strcmp (argv [i], "-sub") && ++i < argc) {
            sub_mrl = TQString (argv [i]);
        } else if (!strcmp (argv [i], "-lang") && ++i < argc) {
            slang = alang = TQString (argv [i]);
        } else if (!strcmp (argv [i], "-v")) {
            xine_verbose = true;
        } else if (!strcmp (argv [i], "-vv")) {
            xine_verbose = xine_vverbose = true;
        } else if (!strcmp (argv [i], "-c")) {
            wants_config = true;
        } else if (!strcmp (argv [i], "-f") && ++i < argc) {
            strncpy (configfile, argv [i], sizeof (configfile));
            configfile[sizeof (configfile) - 1] = 0;
        } else if (!strcmp (argv [i], "-cb") && ++i < argc) {
            TQString str = argv [i];
            int pos = str.find ('/');
            if (pos > -1) {
                fprintf (stderr, "callback is %s %s\n", str.left (pos).ascii (), str.mid (pos + 1).ascii ());
                callback = new KMPlayer::Callback_stub 
                    (str.left (pos).ascii (), str.mid (pos + 1).ascii ());
            }
        } else if (!strcmp (argv [i], "-rec") && i < argc - 1) {
            rec_mrl = TQString::fromLocal8Bit (argv [++i]);
        } else if (!strcmp (argv [i], "-loop") && i < argc - 1) {
            repeat_count = atol (argv [++i]);
        } else {
            if (mrl.startsWith ("-session")) {
                delete xineapp;
                return 1;
            }
            mrl = TQString::fromLocal8Bit (argv [i]);
        }
    }
    bool config_changed = !TQFile (configfile).exists ();

    if (!callback && mrl.isEmpty ()) {
        fprintf (stderr, "usage: %s [-vo (xv|xshm)] [-ao (arts|esd|..)] "
                "[-f <xine config file>] [-dvd-device <device>] "
                "[-vcd-device <device>] [-vd <video device>] "
                "[-wid <X11 Window>|-window-id <X11 Window>|-root] "
                "[-sub <subtitle url>] [-lang <lang>] [(-v|-vv)] "
                "[-cb <DCOP callback name> [-c]] "
                "[-loop <repeat>] [<url>]\n", argv[0]);
        delete xineapp;
        return 1;
    }

    XEventThread * eventThread = new XEventThread;
    eventThread->start ();

    DCOPClient dcopclient;
    dcopclient.registerAs ("kxineplayer");
    Backend player;

    xine = xine_new();
    if (xine_verbose)
        xine_engine_set_param (xine, XINE_ENGINE_PARAM_VERBOSITY, xine_vverbose ? XINE_VERBOSITY_DEBUG : XINE_VERBOSITY_LOG);
    xine_config_load(xine, configfile);
    xine_init(xine);

    xineapp->init ();

    if (dvd_device)
        config_changed |= updateConfigEntry (TQString ("input.dvd_device"), TQString (dvd_device));
    if (vcd_device)
        config_changed |= updateConfigEntry (TQString ("input.vcd_device"), TQString (vcd_device));
    if (grab_device)
        config_changed |= updateConfigEntry (TQString ("media.video4linux.video_device"), TQString (grab_device));

    if (config_changed)
        xine_config_save (xine, configfile);

    TQStringList vos = TQStringList::split (',', vo_driver);
    for (int i = 0; i < vos.size (); i++) {
        if (vos[i] == "x11")
            vos[i] = "xshm";
        else if (vos[i] == "gl")
            vos[i] = "opengl";
        fprintf (stderr, "trying video driver %s ..\n", vos[i].ascii ());
        vo_port = xine_open_video_driver(xine, vos[i].ascii (),
                XINE_VISUAL_TYPE_X11, (void *) &vis);
        if (vo_port)
            break;
    }
    if (!vo_port)
        fprintf (stderr, "no video driver found\n");
    TQStringList aos = TQStringList::split (',', ao_driver);
    for (int i = 0; i < aos.size (); i++) {
        fprintf (stderr, "trying audio driver %s ..\n", aos[i].ascii ());
        ao_port = xine_open_audio_driver (xine, aos[i].ascii (), NULL);
        if (ao_port)
            break;
    }
    if (!ao_port)
        fprintf (stderr, "audio driver initialisation failed\n");
    stream = xine_stream_new (xine, ao_port, vo_port);

    TQByteArray buf;
    if (wants_config) {
        /* TODO? Opening the output drivers in front, will add more config
                 settings. Unfortunately, that also adds a second in startup..
        const char *const * aops = xine_list_audio_output_plugins (xine);
        for (const char *const* aop = aops; *aop; aop++) {
            xine_audio_port_t * ap = xine_open_audio_driver (xine, *aop, 0L);
            xine_close_audio_driver (xine, ap);
            fprintf (stderr, "audio output: %s\n", *aop);
        }
        const char *const * vops = xine_list_video_output_plugins (xine);
        for (const char *const* vop = vops; *vop; vop++) {
            xine_video_port_t * vp = xine_open_video_driver (xine, *vop, XINE_VISUAL_TYPE_NONE, 0L);
            xine_close_video_driver (xine, vp);
            fprintf (stderr, "vidio output: %s\n", *vop);
        }*/
        getConfigEntries (buf);
    }
    if (callback)
        callback->started (dcopclient.appId (), buf);
    else
        ;//printf ("%s\n", TQString (buf).ascii ());
    xineapp->exec ();

    if (sub_stream)
        xine_dispose (sub_stream);
    if (stream) {
        xine_event_dispose_queue (event_queue);
        xine_dispose (stream);
    }
    if (ao_port)
        xine_close_audio_driver (xine, ao_port);
    if (vo_port)
        xine_close_video_driver (xine, vo_port);
    XLockDisplay(display);
    XEvent ev;
    ev.xclient.type = ClientMessage;
    ev.xclient.serial = 0;
    ev.xclient.send_event = true;
    ev.xclient.display = display;
    ev.xclient.window = wid;
    ev.xclient.message_type = quit_atom;
    ev.xclient.format = 8;
    ev.xclient.data.b[0] = 0;
    XSendEvent (display, wid, false, StructureNotifyMask, &ev);
    XFlush (display);
    XUnlockDisplay(display);
    eventThread->wait (500);
    delete eventThread;

    xineapp->stop ();
    delete xineapp;

    xine_exit (xine);

    fprintf (stderr, "closing display\n");
    XCloseDisplay (display);
    fprintf (stderr, "done\n");
    return 0;
}

#include "xineplayer.moc"
