// (C) 2005 Max Howell (max.howell@methylblue.com)
// See COPYING file for licensing information

#define CODEINE_DEBUG_PREFIX "engine"

#include <cmath>          //the fade out
#include <limits>
#include <tdelocale.h>
#include <tqapplication.h> //::sendEvent()
#include <tqdatetime.h>    //record()
#include <tqdir.h>         //::exists()
#include <unistd.h>
#include <cstdlib>

#include "../debug.h"
#include "../mxcl.library.h"
#include "actions.h"      //::seek() FIXME unfortunate
#include "codeineConfig.h"
#include "slider.h"
#include "theStream.h"
#include "xineEngine.h"
#include "xineScope.h"


namespace Codeine {


VideoWindow *VideoWindow::s_instance = nullptr;
bool VideoWindow::s_logarithmicVolume = false;


VideoWindow::VideoWindow( TQWidget *parent )
      : TQWidget( parent, "VideoWindow" )
      , m_osd( nullptr )
      , m_stream( nullptr )
      , m_eventQueue( nullptr )
      , m_videoPort( nullptr )
      , m_audioPort( nullptr )
      , m_post( nullptr )
      , m_xine( nullptr )
      , m_scope( Analyzer::SCOPE_SIZE * 2 ) // Multiply by two to account for interleaved PCM.
      , m_current_vpts( 0 )
{
   DEBUG_BLOCK

   s_instance = this;

   setWFlags( TQt::WNoAutoErase );
   setMouseTracking( true );
   setAcceptDrops( true );
   setUpdatesEnabled( false ); //to stop TQt drawing over us
   setPaletteBackgroundColor( TQt::black );
   setFocusPolicy( ClickFocus );

   // Detect xine version, this is used for volume adjustment.
   // Xine versions prior to 1.2.13 use linear volume, so the engine uses logarithmic volume.
   // Xine versions starting from 1.2.13 use logarithmic volume, so the engine uses linear volume.
   int xinemajor = 0, xineminor = 0, xinemaint = 0;
   xine_get_version(&xinemajor, &xineminor, &xinemaint);
   s_logarithmicVolume = (xinemajor * 1000000 + xineminor * 1000 + xinemaint < 1002013);
}

VideoWindow::~VideoWindow()
{
   DEBUG_BLOCK

   eject();

   // fade out volume on exit
   if( m_stream && xine_get_status( m_stream ) == XINE_STATUS_PLAY ) {
      int cum = 0;
      for( int v = 99; v >= 0; v-- ) {
         int vol = v;
         if (s_logarithmicVolume)
         {
           vol = makeVolumeLogarithmic(vol);
         }
         xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, vol );
         int sleep = int(32000 * (-std::log10( double(v + 1) ) + 2));

         ::usleep( sleep );

         cum += sleep;
      }

      debug() << "Total sleep: " << cum << "x10^-6 s\n";

      xine_stop( m_stream );

      ::sleep( 1 );
   }

   //xine_set_param( m_stream, XINE_PARAM_IGNORE_VIDEO, 1 );

   if( m_osd )        xine_osd_free( m_osd );
   if( m_stream )     xine_close( m_stream );
   if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue );
   if( m_stream )     xine_dispose( m_stream );
   if( m_audioPort )  xine_close_audio_driver( m_xine, m_audioPort );
   if( m_videoPort )  xine_close_video_driver( m_xine, m_videoPort );
   if( m_post )       xine_post_dispose( m_xine, m_post );
   if( m_xine )       xine_exit( m_xine );

   cleanUpVideo();
}

bool
VideoWindow::init()
{
   DEBUG_BLOCK

   initVideo();

   debug() << "xine_new()\n";
   m_xine = xine_new();
   if( !m_xine )
      return false;

   xine_engine_set_param(m_xine, XINE_ENGINE_PARAM_VERBOSITY, XINE_VERBOSITY_DEBUG);

   debug() << "xine_config_load()\n";
   xine_config_load( m_xine, TQFile::encodeName( TQDir::homeDirPath() + "/.xine/config" ) );

   debug() << "xine_init()\n";
   xine_init( m_xine );

#if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \
   (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10)
   xine_register_plugins( m_xine, scope_plugin_info );
#endif

   debug() << "xine_open_video_driver()\n";
   m_videoPort = xine_open_video_driver( m_xine, "auto", XINE_VISUAL_TYPE_X11, videoWindow()->x11Visual() );

   debug() << "xine_open_audio_driver()\n";
   m_audioPort = xine_open_audio_driver( m_xine, "auto", nullptr );

   debug() << "xine_stream_new()\n";
   m_stream = xine_stream_new( m_xine, m_audioPort, m_videoPort );
   if( !m_stream )
      return false;

   // we do these after creating the stream as they are non-fatal
   // and the messagebox creates a modal event loop that allows
   // events that require a stream to have been created..
   if( !m_videoPort )
      MessageBox::error( i18n("xine was unable to initialize any video-drivers.") );
   if( !m_audioPort )
      MessageBox::error( i18n("xine was unable to initialize any audio-drivers.") );

   debug() << "xine_osd_new()\n";
   m_osd = xine_osd_new( m_stream, 10, 10, 1000, 18 * 6 + 10 );
   if( m_osd ) {
      xine_osd_set_font( m_osd, "sans", 18 );
      xine_osd_set_text_palette( m_osd, XINE_TEXTPALETTE_WHITE_BLACK_TRANSPARENT, XINE_OSD_TEXT1 );
   }

   debug() << "scope_plugin_new()\n";
#if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \
   (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10)
   m_post = xine_post_init( m_xine, "codeine-scope", 1, &m_audioPort, nullptr );
#else
   m_post = scope_plugin_new( m_xine, m_audioPort );

   //FIXME this one seems to make seeking unstable for Codeine, perhaps
   xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); //less buffering, faster seeking..

   // causes an abort currently
   //xine_trick_mode( m_stream, XINE_TRICK_MODE_SEEK_TO_TIME, 1 );
#endif


   {
      typedef TQValueList<int> List;
      List params( List()
            << XINE_PARAM_VO_HUE << XINE_PARAM_VO_SATURATION << XINE_PARAM_VO_CONTRAST << XINE_PARAM_VO_BRIGHTNESS
            << XINE_PARAM_SPU_CHANNEL << XINE_PARAM_AUDIO_CHANNEL_LOGICAL << XINE_PARAM_VO_ASPECT_RATIO );

      for( List::ConstIterator it = params.constBegin(), end = params.constEnd(); it != end; ++it )
         debug1( xine_get_param( m_stream, *it ) );
   }


   debug() << "xine_event_create_listener_thread()\n";
   xine_event_create_listener_thread( m_eventQueue = xine_event_new_queue( m_stream ), &VideoWindow::xineEventListener, (void*)this );

   //set the UI up to a default state
   announceStateChange();

   startTimer( 200 ); //prunes the scope

   return true;
}

void
VideoWindow::eject()
{
   //WARNING! don't xine_stop or that, buggers up dtor

   if( m_url.isEmpty() )
      return;

   TDEConfig *profile = TheStream::profile(); // the config profile for this video file

   #define writeParameter( param, default ) { \
         const int value = xine_get_param( m_stream, param ); \
         const TQString key = TQString::number( param ); \
         if( value != default ) \
            profile->writeEntry( key, value ); \
         else \
            profile->deleteEntry( key ); }

   writeParameter( XINE_PARAM_VO_HUE, 32768 );
   writeParameter( XINE_PARAM_VO_SATURATION, 32772 );
   writeParameter( XINE_PARAM_VO_CONTRAST, 32772 );
   writeParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 )
   writeParameter( XINE_PARAM_SPU_CHANNEL, -1 );
   writeParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 );
   writeParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 );

   #undef writeParameter


   if( xine_get_status( m_stream ) == XINE_STATUS_PLAY && //XINE_STATUS_PLAY = playing OR paused
            length() - time() > 5000 ) // if we are really close to the end, don't remember the position
      profile->writeEntry( "Position", position() );
   else
      profile->deleteEntry( "Position" );

   const TQSize s = videoWindow()->size();
   const TQSize defaultSize = TheStream::defaultVideoSize();
   if( s.width() == defaultSize.width() || s.height() == defaultSize.height() )
      profile->deleteEntry( "Preferred Size" );
   else
      profile->writeEntry( "Preferred Size", s );

   profile->sync();

   m_url = KURL();
}

bool
VideoWindow::load( const KURL &url )
{
   mxcl::WaitCursor allocateOnStack;

   eject(); //save profile for this video

   m_url = url;

   // only gets shown if there is an error generally, as no event processing
   // occurs, so no paint event. This is fine IMO, TODO although if xine_open hangs
   // due to something, it would be good to show the message...
   emit statusMessage( i18n("Loading media: %1" ).arg( url.fileName() ) );

   debug() << "xine_open()\n";
   if( xine_open( m_stream, url.url().local8Bit() ) )
   {
      TDEConfig *profile = TheStream::profile();
      #define setParameter( param, default ) xine_set_param( m_stream, param, profile->readNumEntry( TQString::number( param ), default ) );
      setParameter( XINE_PARAM_VO_HUE, 32768 );
      setParameter( XINE_PARAM_VO_SATURATION, 32772 );
      setParameter( XINE_PARAM_VO_CONTRAST, 32772 );
      setParameter( XINE_PARAM_VO_BRIGHTNESS, 32800 )
      setParameter( XINE_PARAM_SPU_CHANNEL, -1 );
      setParameter( XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1 );
      setParameter( XINE_PARAM_VO_ASPECT_RATIO, 0 );
      // 100 is the same for both linear and logarithmic volume control
      setParameter( XINE_PARAM_AUDIO_AMP_LEVEL, 100 );
      #undef setParameter

      videoWindow()->setShown( xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_VIDEO ) );

      //TODO popup message for no audio
      //TODO popup message for no video + no audio

      // ensure old buffers are deleted
      // FIXME leaves one erroneous buffer
      timerEvent( nullptr );

      if( m_post ) {
         xine_post_out_t *source = xine_get_audio_source( m_stream );
         xine_post_in_t  *target = (xine_post_in_t*)xine_post_input( m_post, const_cast<char*>("audio in") );
         xine_post_wire( source, target );
      }

      announceStateChange();

      return true;
   }

   showErrorMessage();
   announceStateChange();
   m_url = KURL();
   return false;
}

bool
VideoWindow::play( uint offset )
{
   mxcl::WaitCursor allocateOnStack;

   const bool resume = offset > 0 && /*FIXME*/ m_url.protocol() != "dvd";
   if( resume )
      //HACK because we have to do xine_play() the audio "stutters"
      //     so we mute it and then unmute it to make it sound better
      xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 );

   debug() << "xine_play()\n";
   if( xine_play( m_stream, offset, 0 ) )
   {
      if( resume ) {
         //we have to set this or it stays at 0
         Slider::instance()->setValue( offset );

         // we come up paused if we are resuming playback from a previous session
         pause();

         // see above from HACK
         xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 );
      }
      else
         announceStateChange();

      return true;
   }

   showErrorMessage();
   return false;
}

void
VideoWindow::record()
{
   xine_cfg_entry_t config;

   if( xine_config_lookup_entry( m_xine, "misc.save_dir", &config ) )
   {
      //TODO which fricking KDE function tells me this? Who can tell, stupid KDE API
      TQDir d( TQDir::home().filePath( "Desktop" ) );
      config.str_value = tqstrdup( d.exists() //FIXME tiny-mem-leak, *shrug*
            ? d.path().utf8()
            : TQDir::homeDirPath().utf8() );
      xine_config_update_entry( m_xine, &config );

      const TQString fileName = m_url.filename();

      TQString
      url  = m_url.url();
      url += "#save:";
      url += m_url.host();
      url += " [";
      url += TQDate::currentDate().toString();
      url += ']';
      url += fileName.mid( fileName.findRev( '.' ) + 1 ).lower();

      xine_open( m_stream, url.local8Bit() );
      xine_play( m_stream, 0, 0 );

      emit statusMessage( i18n( "Recording to: %1" ).arg( url ) );

      debug() << url << endl;
   }
   else
      debug() << "unable to set misc.save_dir\n";
}

void
VideoWindow::stop()
{
   xine_stop( m_stream );

   std::fill(m_scope.begin(), m_scope.end(), 0);

   announceStateChange();
}

void
VideoWindow::pause()
{
   if( xine_get_status( m_stream ) == XINE_STATUS_STOP )
      play();

   else if( m_url.protocol() == "http" )
      // we are playing and it's an HTTP stream
      stop();

   else if( xine_get_param( m_stream, XINE_PARAM_SPEED ) ) {
      // do first because xine is slow to pause and is bad feedback otherwise
      emit stateChanged( Engine::Paused );
      xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE );
      xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
      showOSD( i18n( "Playback paused" ) );
   }
   else {
      xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL );
      announceStateChange();
      showOSD( i18n( "Playback resumed" ) );
   }
}

void
VideoWindow::showErrorMessage()
{
   const TQString name = m_url.fileName();

   debug() << "xine_get_error()\n";
   switch( xine_get_error( m_stream ) )
   {
   case XINE_ERROR_NO_INPUT_PLUGIN:
      MessageBox::sorry( i18n("There is no input plugin that can read: %1.").arg( name ) );
      break;
   case XINE_ERROR_NO_DEMUX_PLUGIN:
      MessageBox::sorry( i18n("There is no demux plugin available for %1.").arg( name ) );
      break;
   case XINE_ERROR_DEMUX_FAILED:
      MessageBox::sorry( i18n("Demuxing failed for %1.").arg( name ) );
      break;
   case XINE_ERROR_INPUT_FAILED:
   case XINE_ERROR_MALFORMED_MRL:
   case XINE_ERROR_NONE:
      MessageBox::sorry( i18n("Internal error while attempting to play %1.").arg( name ) );
      break;
   }
}

Engine::State
VideoWindow::state() const
{
   //FIXME this is for the analyzer, but I don't like the analyzer being dodgy like this
   if( !m_xine || !m_stream )
      return Engine::Uninitialised;

   switch( xine_get_status( m_stream ) )
   {
   case XINE_STATUS_PLAY: return xine_get_param( m_stream, XINE_PARAM_SPEED ) ? Engine::Playing : Engine::Paused;
   case XINE_STATUS_IDLE: return Engine::Empty; //FIXME this route never used!
   case XINE_STATUS_STOP:
   default:               return m_url.isEmpty() ? Engine::Empty : Engine::Loaded;
   }
}

uint
VideoWindow::posTimeLength( PosTimeLength type ) const
{
   int pos = 0, time = 0, length = 0;
   xine_get_pos_length( m_stream, &pos, &time, &length );

   switch( type ) {
      case Pos:    return pos;
      case Time:   return time;
      case Length: return length;
   }

   return 0; //--warning
}

bool
VideoWindow::isMuted() const
{
   return xine_get_param(m_stream, XINE_PARAM_AUDIO_AMP_MUTE);
}

uint
VideoWindow::volume() const
{
   int vol = xine_get_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL );
   if (s_logarithmicVolume)
   {
      vol = 100 - 100.0 * (pow(10, (100.0 - vol) / 100.0) - 1) / 9.0;
   }
   if (vol < 0)
   {
      vol = 0;
   }
   if (vol > 100)
   {
      vol = 100;
   }
   return (uint)vol;
}

void
VideoWindow::seek( uint pos )
{
   bool wasPaused = false;

   // If we seek to the end the track ended event is sent, but it is
   // delayed as it happens in xine-event loop and before that we are
   // already processing the next seek event (if user uses mouse wheel
   // or keyboard to seek) and this causes the ui to think video is
   // stopped but xine is actually playing the track. Tada!
   // TODO set state based on events from xine only
   if( pos > 65534 )
      pos = 65534;

   switch( state() ) {
   case Engine::Uninitialised:
      //NOTE should never happen
      Debug::warning() << "Seek attempt thwarted! xine not initialised!\n";
      return;
   case Engine::Empty:
      Debug::warning() << "Seek attempt thwarted! No media loaded!\n";
      return;
   case Engine::Loaded:
      // then the state is changing and we should announce it
      play( pos );
      return;
   case Engine::Paused:
      // xine_play unpauses stream if stream was paused
      // was broken at 1.0.1 still
      wasPaused = true;
      xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 1 );
      break;
   default:
      ;
   }

   if( !TheStream::canSeek() ) {
      // for http streaming it is not a good idea to seek as xine freezes
      // and/or just breaks, this is xine 1.0.1
      Debug::warning() << "We won't try to seek as the media is not seekable!\n";
      return;
   }

   //better feedback
   //NOTE doesn't work! I can't tell why..
   Slider::instance()->TQSlider::setValue( pos );
   Slider::instance()->repaint( false );

   const bool fullscreen = toggleAction("fullscreen")->isChecked();
   if( fullscreen ) {
      //TODO don't use OSD (sucks) show slider widget instead
      TQString osd = "[";
      TQChar separator = '|';

      for( uint x = 0, y = int(pos / (65535.0/20.0)); x < 20; x++ ) {
         if( x > y )
            separator = '.';
         osd += separator;
      }
      osd += ']';

      xine_osd_clear( m_osd );
      xine_osd_draw_text( m_osd, 0, 0, osd.utf8(), XINE_OSD_TEXT1 );
      xine_osd_show( m_osd, 0 );
   }

   xine_play( m_stream, (int)pos, 0 );

   if( fullscreen )
      //after xine_play because the hide command uses stream position
      xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000  ); //2 seconds

   if( wasPaused )
      xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ),
      xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_MUTE, 0 );
}

int
VideoWindow::makeVolumeLogarithmic(int volume)
{
   // We're using a logarithmic function to make the volume ramp more natural.
   return static_cast<uint>( 100 - 100.0 * std::log10( ( 100 - volume ) * 0.09 + 1.0 ) );
}

void
VideoWindow::setStreamParameter( int value )
{
   TQCString sender = this->sender()->name();
   int parameter;

   if( sender == "hue" )
      parameter = XINE_PARAM_VO_HUE;
   else if( sender == "saturation" )
      parameter = XINE_PARAM_VO_SATURATION;
   else if( sender == "contrast" )
      parameter = XINE_PARAM_VO_CONTRAST;
   else if( sender == "brightness" )
      parameter = XINE_PARAM_VO_BRIGHTNESS;
   else if( sender == "subtitle_channels_select" )
      parameter = XINE_PARAM_SPU_CHANNEL,
      value -= 2;
   else if( sender == "audio_channels_select" )
      parameter = XINE_PARAM_AUDIO_CHANNEL_LOGICAL,
      value -= 2;
   else if( sender == "aspect_ratio_select" )
      parameter = XINE_PARAM_VO_ASPECT_RATIO;
   else if( sender == "volume" )
   {
      parameter = XINE_PARAM_AUDIO_AMP_LEVEL;
      value = 100 - value; // TQt sliders are the wrong way round when vertical
      if (s_logarithmicVolume)
      {
        value = makeVolumeLogarithmic(value);
      }
   }
   else
      return;

   xine_set_param( m_stream, parameter, value );
}

void
VideoWindow::setMuted(bool mute)
{
   xine_set_param(m_stream, XINE_PARAM_AUDIO_AMP_MUTE, mute);
}

const Engine::Scope&
VideoWindow::scope()
{
   using Analyzer::SCOPE_SIZE;

   if (!m_post || !m_stream || xine_get_status(m_stream) != XINE_STATUS_PLAY)
   {
      return m_scope;
   }

   MyNode *const myList        = scope_plugin_list(m_post);
   const int64_t pts_per_smpls = scope_plugin_pts_per_smpls(m_post);
   const int channels          = scope_plugin_channels(m_post);
   int scopeIdx = 0;

   if (channels > 2)
   {
      return m_scope;
   }

   //prune the buffer list and update the m_current_vpts timestamp
   timerEvent( nullptr );

   for (int n, frame = 0; frame < SCOPE_SIZE; /* no-op */)
   {
      MyNode *best_node = nullptr;

      for( MyNode *node = myList->next; node != myList; node = node->next )
         if( node->vpts <= m_current_vpts && (!best_node || node->vpts > best_node->vpts) )
            best_node = node;

      if( !best_node || best_node->vpts_end < m_current_vpts )
         break;

      int64_t
      diff  = m_current_vpts;
      diff -= best_node->vpts;
      diff *= 1<<16;
      diff /= pts_per_smpls;

      const int16_t*
      data16  = best_node->mem;
      data16 += diff;

      diff += diff % channels; // important correction to ensure we don't overflow the buffer.
      diff /= channels;        // use units of frames, not samples.

      // calculate the number of available samples in this buffer.
      n  = best_node->num_frames;
      n -= diff;
      n += frame; //clipping for # of frames we need

      if( n > SCOPE_SIZE )
         n = SCOPE_SIZE; //bounds limiting

      for( int a, c; frame < n; ++frame, data16 += channels ) {
         for( a = c = 0; c < channels; ++c )
         {
            // now we give interleaved PCM to the scope.
            m_scope[scopeIdx++] = data16[c];
            if (channels == 1)
            {
               // Duplicate mono samples.
               m_scope[scopeIdx++] = data16[c];
            }
         }
      }

      m_current_vpts = best_node->vpts_end;
      m_current_vpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again
   }

   return m_scope;
}

void
VideoWindow::timerEvent( TQTimerEvent* )
{
   if (!m_stream)
   {
      return;
   }

   /// here we prune the buffer list regularly
   MyNode *myList = scope_plugin_list(m_post);

   if (!myList)
   {
      return;
   }

   // We operate on a subset of the list for thread-safety.
   MyNode *const firstNode = myList->next;
   const MyNode *const listEnd = myList;

   // If we're not playing or paused, empty the list.
   m_current_vpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY)
         ? xine_get_current_vpts( m_stream )
         : std::numeric_limits<int64_t>::max();

   for( MyNode *prev = firstNode, *node = firstNode->next; node != listEnd; node = node->next )
   {
      // we never delete firstNode
      // this maintains thread-safety
      if( node->vpts_end < m_current_vpts ) {
         prev->next = node->next;

         free( node->mem );
         free( node );

         node = prev;
      }

      prev = node;
   }
}

void
VideoWindow::customEvent( TQCustomEvent *e )
{
   switch( e->type() - 2000 ) {
   case XINE_EVENT_UI_PLAYBACK_FINISHED:
      emit stateChanged( Engine::TrackEnded );
      break;

   case XINE_EVENT_FRAME_FORMAT_CHANGE:
      //TODO not ideal really
      debug() << "XINE_EVENT_FRAME_FORMAT_CHANGE\n";
      break;

   case XINE_EVENT_UI_CHANNELS_CHANGED:
   {
      char s[128]; //apparently sufficient

      {
         TQStringList languages;
         int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL );
         for( int j = 0; j < channels; j++ )
            languages += xine_get_spu_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 );
         emit subtitleChannelsChanged(languages);
      }

      {
         TQStringList languages;
         int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_MAX_AUDIO_CHANNEL );
         for( int j = 0; j < channels; j++ )
            languages += xine_get_audio_lang( m_stream, j, s ) ? s : i18n("Channel %1").arg( j+1 );
         emit audioChannelsChanged(languages);
      }
      break;
   }

   case 1000:
      #define message static_cast<TQString*>(e->data())
      emit statusMessage( *message );
      delete message;
      break;

   case 1001:
      MessageBox::sorry( (*message).arg( m_url.prettyURL() ) );
      delete message;
      break;

   case 1002:
      emit titleChanged( *message );
      delete message;
      break;
      #undef message

   default:
      ;
   }
}

void
VideoWindow::xineEventListener( void *p, const xine_event_t* xineEvent )
{
   if( !p )
      return;

   #define engine static_cast<VideoWindow*>(p)

   switch( xineEvent->type ) {
   case XINE_EVENT_UI_NUM_BUTTONS: debug() << "XINE_EVENT_UI_NUM_BUTTONS\n"; break;
   case XINE_EVENT_MRL_REFERENCE: {
      //FIXME this is not the right way, it will have bugs
      debug() << "XINE_EVENT_MRL_REFERENCE\n";
      engine->m_url = TQString::fromUtf8( ((xine_mrl_reference_data_ext_t*)xineEvent->data)->mrl );
      TQTimer::singleShot( 0, engine, TQ_SLOT(play()) );
      break;
   }
   case XINE_EVENT_DROPPED_FRAMES: debug() << "XINE_EVENT_DROPPED_FRAMES\n"; break;

   case XINE_EVENT_UI_PLAYBACK_FINISHED:
   case XINE_EVENT_FRAME_FORMAT_CHANGE:
   case XINE_EVENT_UI_CHANNELS_CHANGED:
   {
      TQCustomEvent *ce;
      ce = new TQCustomEvent( 2000 + xineEvent->type );
      ce->setData( const_cast<xine_event_t*>(xineEvent) );
      TQApplication::postEvent( engine, ce );
      break;
   }

   case XINE_EVENT_UI_SET_TITLE:
      TQApplication::postEvent( engine, new TQCustomEvent(
            TQEvent::Type(3002),
            new TQString( TQString::fromUtf8( static_cast<xine_ui_data_t*>(xineEvent->data)->str ) ) ) );
      break;

   case XINE_EVENT_PROGRESS:
   {
      xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data;

      TQString
      msg = "%1 %2%";
      msg = msg.arg( TQString::fromUtf8( pd->description ) )
               .arg( TDEGlobal::locale()->formatNumber( pd->percent, 0 ) );

      TQApplication::postEvent( engine, new TQCustomEvent( TQEvent::Type(3000), new TQString( msg ) ) );
      break;
   }
   case XINE_EVENT_UI_MESSAGE:
   {
      debug() << "message received from xine\n";

      xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data;
      TQString message;

      switch( data->type ) {
      case XINE_MSG_NO_ERROR:
      {
         //series of \0 separated strings, terminated with a \0\0
         char str[2000];
         char *p = str;
         for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p )
            *p = *msg == '\0' ? '\n' : *msg;
         *p = '\0';

         debug() << str << endl;

         break;
      }

      case XINE_MSG_ENCRYPTED_SOURCE:
         message = i18n("The source is encrypted and can not be decrypted."); goto param;
      case XINE_MSG_UNKNOWN_HOST:
         message = i18n("The host is unknown for the URL: <i>%1</i>"); goto param;
      case XINE_MSG_UNKNOWN_DEVICE:
         message = i18n("The device name you specified seems invalid."); goto param;
      case XINE_MSG_NETWORK_UNREACHABLE:
         message = i18n("The network appears unreachable."); goto param;
      case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
         message = i18n("Audio output unavailable; the device is busy."); goto param;
      case XINE_MSG_CONNECTION_REFUSED:
         message = i18n("The connection was refused for the URL: <i>%1</i>"); goto param;
      case XINE_MSG_FILE_NOT_FOUND:
         message = i18n("xine could not find the URL: <i>%1</i>"); goto param;
      case XINE_MSG_PERMISSION_ERROR:
         message = i18n("Access was denied for the URL: <i>%1</i>"); goto param;
      case XINE_MSG_READ_ERROR:
         message = i18n("The source cannot be read for the URL: <i>%1</i>"); goto param;
      case XINE_MSG_LIBRARY_LOAD_ERROR:
         message = i18n("A problem occurred while loading a library or decoder."); goto param;

      case XINE_MSG_GENERAL_WARNING:
      case XINE_MSG_SECURITY:
      default:

            if(data->explanation)
            {
               message += "<b>";
               message += TQString::fromUtf8( (char*) data + data->explanation );
               message += "</b>";
            }
            else break; //if no explanation then why bother!

            //FALL THROUGH

      param:

            message.prepend( "<p>" );
            message += "<p>";

            if(data->parameters)
            {
               message += "xine says: <i>";
               message += TQString::fromUtf8( (char*) data + data->parameters);
               message += "</i>";
            }
            else message += i18n("Sorry, no additional information is available.");

            TQApplication::postEvent( engine, new TQCustomEvent(TQEvent::Type(3001), new TQString(message)) );
      }

   } //case
   } //switch

   #undef engine
}

void
VideoWindow::toggleDVDMenu()
{
   xine_event_t e;
   e.type = XINE_EVENT_INPUT_MENU1;
   e.data = nullptr;
   e.data_length = 0;

   xine_event_send( m_stream, &e );
}

void
VideoWindow::showOSD( const TQString &message )
{
   if( m_osd ) {
      xine_osd_clear( m_osd );
      xine_osd_draw_text( m_osd, 0, 0, message.utf8(), XINE_OSD_TEXT1 );
      xine_osd_show( m_osd, 0 );
      xine_osd_hide( m_osd, xine_get_current_vpts( m_stream ) + 180000  ); //2 seconds
   }
}

TQString
VideoWindow::fileFilter() const
{
   char *supportedExtensions = xine_get_file_extensions( m_xine );

   TQString filter("*.");
   filter.append(supportedExtensions);

   // Remove protocols
   filter.remove(" dvb://");
   filter.remove(" dvbc://");
   filter.remove(" dvbs://");
   filter.remove(" dvbt://");
   filter.remove(" vcd:/");
   filter.remove(" vdr:/");
   filter.remove(" netvdr:/");
   filter.remove(" dvd:/");
   filter.remove(" pvr:/");
   filter.remove(" slave://");
   filter.remove(" cdda:/");

   // Remove image files
   filter.remove(" bmp");
   filter.remove(" gif");
   filter.remove(" jpg");
   filter.remove(" jpeg");
   filter.remove(" png");

   // Remove misc. files
   filter.remove(" txt");

   // Remove spaces (prevent multiple *.)
   filter.replace("  ", " ");
   filter.replace(' ', " *.");

   std::free(supportedExtensions);

   return filter;
}

} //namespace Codeine

#include "xineEngine.moc"
