/*
 * xine_frontend.c:
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 */

#ifndef XINE_VERSION_CODE
#  define XINE_VERSION_CODE (XINE_MAJOR_VERSION*10000 + \
                             XINE_MINOR_VERSION*100 + \
                             XINE_SUB_VERSION)
#endif

#define LOG_MODULENAME "[vdr-fe]    "
#include "logdefs.h"

#include "xine/post.h"

#ifdef FE_STANDALONE
  /* next two symbols are dynamically linked from input plugin */
  int SysLogLevel = 2; /* errors and info, no debug */
  int LogToSysLog = 0; /* log to syslog instead of console */

  static int verbose_xine_log = 0;
#else
  extern int SysLogLevel; /* vdr tools.c */
  int LogToSysLog = 1;    /* dynamically linked from input plugin */
#endif

/* from vdr_input_plugin: */
typedef struct {
  input_plugin_t           input_plugin;
  vdr_input_plugin_funcs_t f;
  /* ... */
} vdr_input_plugin_t;


#if !defined(XINELIBOUTPUT_DEBUG_STDOUT) && \
    !defined(XINELIBOUTPUT_DEBUG_STDERR)
# undef x_syslog

  _syscall0(pid_t, gettid)

  static void x_syslog(int level, const char *fmt, ...)
  {
    va_list argp;
    char buf[512];
    va_start(argp, fmt);
    vsnprintf(buf, 512, fmt, argp);
    if(!LogToSysLog) {
      printf(LOG_MODULENAME "%s\n", buf);
    } else {
      syslog(level, "[%d] " LOG_MODULENAME "%s", gettid(), buf);
    }
    va_end(argp);
  }
#endif


/* detect input plugin */
static int find_input(fe_t *this)
{
  if(!this->input) {
    if(!this->stream || !this->stream->input_plugin ||
       !this->stream->input_plugin->input_class || this->playback_finished) {
      LOGMSG("find_input: stream not initialized or playback finished !");
      usleep(100*1000);
      return 0;
    }
    if(strcmp(this->stream->input_plugin->input_class->get_identifier(
	      this->stream->input_plugin->input_class),
              "xvdr")) {
      LOGMSG("find_input: current xine input plugin is not xvdr !");
      /*usleep(100*1000);*/
      return 0;
    }
    this->input = this->stream->input_plugin;
  }
  return 1;
}

static void *fe_control(void *fe_handle, char *cmd);

/*
 * xine callbacks
 */

static double fe_dest_pixel_aspect(fe_t *this, double video_pixel_aspect,
				   int video_width, int video_height)
{
  int new_cropping = 0;
  double result = 1.0;

  switch(this->aspect) {
    /* Auto */
    default: {
               double correction = 
		 ((double)video_width/(double)video_height) / 
		 ((double)this->width/(double)this->height);

               result = video_pixel_aspect * correction; 
               if(result > (16.9/9.0 * (double)this->height/
			    (double)this->width))
	         result = (16.0/9.0 * (double)this->height/
			   (double)this->width);
	       break;    
             }
    /* Default */
    case 1:  result = this->display_ratio; break;

    /* 4:3 */
    case 2:  result = (4.0/3.0 * (double)this->height/(double)this->width); break;
    /* 16:9 */
    case 3:  result = (16.0/9.0 * (double)this->height/(double)this->width); break;
    /* Pan&Scan */
    case 4: {
      double aspect_diff /*= video_pixel_aspect - 1.0*/;
	      /* TODO */
	      /* does not work (?) */
aspect_diff=(video_pixel_aspect*(double)video_width/(double)video_height) - 4.0 / 3.0;
              if ((aspect_diff < 0.05) && (aspect_diff > -0.05)) {
		result = (4.0/3.0 * (double)this->height/(double)this->width);
		/*LOGDBG("diff: %f", aspect_diff);*/
		new_cropping = 1;
	      } else {
		result = (16.0/9.0 * (double)this->height/(double)this->width);
	      }
	      /*result = (4.0/3.0 * (double)this->height/(double)this->width);*/
	      break;
            }
    /* center cut-out */
    case 5: {
/*#warning center cut-out mode not implemented*/
      break;
    }

  }
#if 0
  if(this->cropping && !new_cropping) {
    LOGDBG("pan&scan CROP OFF");
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_LEFT, 0);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_TOP, 72);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_RIGHT, 0);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_BOTTOM, 72);
    this->cropping = 0;
  }
  if(!this->cropping && new_cropping) {
    LOGDBG("pan&scan CROP ON");
    /*** Should set unscaled osd (or top & bottom will be cropped off) */
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_LEFT, 0);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_TOP, 0);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_RIGHT, 0);
    xine_set_param(this->stream, XINE_PARAM_VO_CROP_BOTTOM, 0);
    this->cropping = 1;
  }
#endif
  return result;
}

static void fe_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) 
{
  fe_t *this = (fe_t *)data;

  if (!this)
    return;

  *dest_x = 0;
#ifndef HAVE_XV_FIELD_ORDER
  *dest_y = 0 + this->field_order;
#else
  *dest_y = 0;
#endif

  *win_x = this->xpos;
  *win_y = this->ypos;  
  *dest_width  = this->width;
  *dest_height = this->height;
  
  *dest_pixel_aspect = fe_dest_pixel_aspect(this, video_pixel_aspect,
					    video_width, video_height);
#if 0
  if(this->cropping) {
    *dest_pixel_aspect = *dest_pixel_aspect * (16.0/9.0)/(4.0/3.0);
    *dest_y = *dest_y - 72;
    *dest_height = *dest_height + 144;
  }
#endif

#if 0
  static int n=0,t=25/**10*/; n++;
  static char *s_aspect[] = {"Auto","Default","4:3","16:9","Pan&Scan"};
  if((n % t) == 0) {
    LOGMSG("fe_frame_output_cb:");
    LOGMSG("  vid=%dx%d (frame %2d:9, pixel %2d:9)",
	   video_width, video_height, 
	   (int)(video_pixel_aspect * 9.0 * ((double)video_width)/((double)video_height) + 0.5),
	   (int)(video_pixel_aspect * 9.0 + 0.5));
    LOGMSG("            %2.3f %2.3f",
	   (video_pixel_aspect*((double)video_width)/((double)video_height)),
	   (video_pixel_aspect));


    LOGMSG("  win=%dx%d (frame %2d:9, pixel %2d:9)",
	   this->width, this->height,
	   (int)(this->display_ratio * 9.0 + 0.5), 
	   (int)(*dest_pixel_aspect * 9.0 + 0.5));
    LOGMSG("            %2.3f %2.3f",
	   (this->display_ratio), 
	   (*dest_pixel_aspect));

    LOGMSG("    given display aspect=%s (%d), zoom=%s",
	   s_aspect[this->aspect], this->aspect, 
	   this->scale_video?"ON":"OFF");
    
#warning TODO: vmode_switch, scale_video
  }
#endif

#if 0
  /* input side uses video size for osd re-scaling */
  if(this->input || find_input(this)) {
    vdr_input_plugin_t *input_vdr;
    input_vdr = (vdr_input_plugin_t *)this->input;
    if(input_vdr->f.set_frame_size)
      input_vdr->f.set_frame_size(this->input, video_width, video_height);
  }
#endif
}

static void xine_event_cb (void *user_data, const xine_event_t *event) 
{
  fe_t *this = (fe_t *)user_data;

  switch (event->type) {
    /* in local mode: vdr stream / slave stream ; in remote mode: vdr stream only */
    case XINE_EVENT_UI_PLAYBACK_FINISHED:
      LOGMSG("xine_event_cb: XINE_EVENT_UI_PLAYBACK_FINISHED");
      if(this)
	this->playback_finished = 1;
      else
	LOGMSG("xine_event_cb: NO USER DATA !");
      break;
  }
}

/*
 * fe_xine_init
 *
 * initialize xine engine, load audio and video ports, setup stream
 */

static int fe_xine_init(frontend_t *this_gen, char *audio_driver, char *audio_port, 
			char *video_driver, int pes_buffers, int priority,
			char *static_post_plugins)
{
  fe_t *this = (fe_t*)this_gen;
  post_plugins_t *posts = NULL;

  if(!this)
    return 0;

  /*
   * init xine engine
   */

  if(this->xine)
    this->fe.xine_exit(this_gen);
  
  this->stream          = NULL;
  this->video_port      = NULL;
  this->audio_port      = NULL;
  this->input           = NULL;

  /* create a new xine and load config file */
  this->xine = xine_new();
  if(!this->xine)
    return 0;

#ifdef FE_STANDALONE
  this->xine->verbosity = verbose_xine_log;
#else
  this->xine->verbosity = (SysLogLevel>2);
#endif

  /*xine_register_log_cb(this->xine, xine_log_cb, this);*/

  /* TODO: use different config file ? (vdr conf.dir/xine/config_vdr ?) */
  sprintf(this->configfile, "%s%s", xine_get_homedir(), 
	  "/.xine/config_xineliboutput");
  
  xine_config_load (this->xine, this->configfile);

  /*this->xine->config->update_num(this->xine->config, "video.device.xv_double_buffer", 1); */
  /*this->xine->config->update_num(this->xine->config, "engine.buffers.video_num_buffers", 
                                   pes_buffers); */
  /*#warning update ??? add TYPE_UNKNOWN ... ? like when xine reads config file ...?*/
  this->xine->config->register_num (this->xine->config,
				    "engine.buffers.video_num_buffers",
				    500,
				    "number of video buffers",
				    "The number of video buffers "
				    "(each is 8k in size) "
				    "xine uses in its internal queue. "
				    "Higher values "
				    "mean smoother playback for unreliable "
				    "inputs, but "
				    "also increased latency and memory "
				    "consumption.",
				    20, NULL, NULL);

  xine_init (this->xine);

  this->xine->config->update_num(this->xine->config,"video.device.xv_double_buffer", 1);
  this->xine->config->update_num(this->xine->config,"engine.buffers.video_num_buffers", pes_buffers);

  this->playback_finished = 0;

  /* create video port */
  
  this->video_port = xine_open_video_driver(this->xine,
					    video_driver,
					    this->xine_visual_type,
					    (void *) &(this->vis));
  if(!this->video_port) {
    LOGMSG("fe_xine_init: xine_open_video_driver(\"%s\") failed",
	   video_driver?video_driver:"(NULL)"); 
    xine_exit(this->xine);
    this->xine = NULL;
    return 0;
  }

  /* create audio port */

  if(audio_driver && audio_port) {
    if(!strcmp("alsa", audio_driver) && strlen(audio_port)>0) {
      this->xine->config->register_string(this->xine->config,
					  "audio.device.alsa_default_device",
					  "default",
					  "device used for mono output",
					  "xine will use this alsa device "
					  "to output mono sound.\n"
					  "See the alsa documentation "
					  "for information on alsa devices.",
					  10, NULL,
					  NULL);
      this->xine->config->register_string(this->xine->config,
					  "audio.device.alsa_front_device",
					  "plug:front:default",
					  "device used for stereo output",
					  "xine will use this alsa device "
					  "to output stereo sound.\n"
					  "See the alsa documentation "
					  "for information on alsa devices.",
					  10, NULL,
					  NULL);
      this->xine->config->update_string(this->xine->config,
					"audio.device.alsa_front_device",
					audio_port);
      this->xine->config->update_string(this->xine->config,
					"audio.device.alsa_default_device",
					audio_port);
    }
    if(!strcmp("oss", audio_driver) && !strncmp("/dev/dsp", audio_port, 8)) {
      int num = 0;
      sscanf(audio_port+8,"%d",&num);
      this->xine->config->update_num(this->xine->config,"audio.device.oss_device_num",num);
    }
  }

  if(audio_driver && !strcmp(audio_driver, "auto"))
    this->audio_port = xine_open_audio_driver (this->xine, NULL, NULL);
  if(audio_driver && !strcmp(audio_driver, "none"))
    ;
  else
    this->audio_port = xine_open_audio_driver (this->xine, audio_driver, NULL);
  
  if(!this->audio_port && (audio_driver && !!strcmp(audio_driver, "none"))) {
    LOGMSG("fe_xine_init: xine_open_audio_driver(\"%s%s%s\") failed",
	   audio_driver?audio_driver:"(NULL)", 
	   audio_port?":":"", audio_port?audio_port:""); 
  }

  /* create stream */

  this->stream = xine_stream_new(this->xine, this->audio_port, this->video_port);

  if(!this->stream) {
    LOGMSG("fe_xine_init: xine_stream_new failed"); 

    if(this->audio_port)
      xine_close_audio_driver(this->xine, this->audio_port);
    this->audio_port = NULL;
    xine_close_video_driver(this->xine, this->video_port);
    this->video_port = NULL;
    xine_exit(this->xine);
    this->xine = NULL;

    return 0;
  }

  /* event handling */

  this->event_queue = xine_event_new_queue (this->stream);
  xine_event_create_listener_thread (this->event_queue, xine_event_cb, this);

  if(!this->event_queue)
    LOGMSG("fe_xine_init: xine_event_new_queue failed");

  /* misc. config */

  this->priority = priority;
  this->pes_buffers = pes_buffers;

  posts = this->postplugins = malloc(sizeof(post_plugins_t));
  memset(posts, 0, sizeof(post_plugins_t));
  posts->xine = this->xine;
  posts->audio_port = this->audio_port;
  posts->video_port = this->video_port;
  posts->stream = this->stream;

  LOGDBG("static post plugins (from command line): %s", static_post_plugins);
  if(static_post_plugins && *static_post_plugins) {
    int i;
    posts->static_post_plugins = strdup(static_post_plugins);
    vpplugin_parse_and_store_post(posts, posts->static_post_plugins);
    applugin_parse_and_store_post(posts, posts->static_post_plugins);

    for(i=0; i<posts->post_audio_elements_num; i++)
      if(posts->post_audio_elements[i])
	posts->post_audio_elements[i]->enable = 2;
    for(i=0; i<posts->post_video_elements_num; i++)
      if(posts->post_video_elements[i])
	posts->post_video_elements[i]->enable = 2;
    posts->post_video_enable = 1;
    posts->post_audio_enable = 1;
  }

  return 1;
}

/*
 * fe_xine_open
 *
 * open xine stream
 */

static int fe_xine_open(frontend_t *this_gen, char *mrl)
{
  fe_t *this = (fe_t*)this_gen;
  int result = 0;
  char url[1024] = "";

  if(!this)
    return 0;

  this->input = NULL;
  this->playback_finished = 1;

  if(!mrl)
    mrl = "xvdr://";

  sprintf(url, "%s#nocache;demux:mpeg_block", mrl);

  result = xine_open(this->stream, url);

  if(!result) {
    LOGMSG("fe_xine_open: xine_open(\"%s\") failed", mrl);
    return 0;
  }

#if 0
  /* priority */
  {
    struct sched_param temp;
    temp.sched_priority = this->priority;
    if(!pthread_setschedparam(this->stream->demux_thread, SCHED_RR, &temp))
      LOGERR("pthread_setschedparam(demux_thread, SCHED_RR, %d) failed", 
	     temp.sched_priority);
    if(!pthread_setschedparam(this->stream->video_thread, SCHED_RR, &temp))
      LOGERR("pthread_setschedparam(video_thread, SCHED_RR, %d) failed", 
	     temp.sched_priority);
    if(!pthread_setschedparam(this->stream->audio_thread, SCHED_RR, &temp))
      LOGERR("pthread_setschedparam(audio_thread, SCHED_RR, %d) failed", 
	     temp.sched_priority);
  }
#endif

#if 0
  this->xine->config->update_num(this->xine->config,
				 "video.output.xv_double_buffer",
				 1);
#endif
  this->xine->config->update_num(this->xine->config,
				 "engine.buffers.video_num_buffers",
				 this->pes_buffers);  
  return result;
}

/*
 * post plugin handling
 *
 */

#define POST_AUDIO_VIS  0
#define POST_AUDIO      1
#define POST_VIDEO      2
#define POST_VIDEO_PIP  3

static void fe_post_unwire(fe_t *this)
{
  xine_post_out_t  *vo_source = xine_get_video_source(this->stream);
  xine_post_out_t  *ao_source = xine_get_audio_source(this->stream);
  LOGDBG("unwiring post plugins");
  (void) xine_post_wire_video_port(vo_source, this->video_port);
  (void) xine_post_wire_audio_port(ao_source, this->audio_port);
}

static void fe_post_rewire(fe_t *this)
{
  LOGDBG("re-wiring post plugins");
  vpplugin_rewire_posts(this->postplugins);
  applugin_rewire_posts(this->postplugins);
}

static void fe_post_unload(fe_t *this)
{
  LOGDBG("unloading post plugins");
  vpplugin_unload_post(this->postplugins, NULL);
  applugin_unload_post(this->postplugins, NULL);
}

static int fe_post_close(fe_t *this, char *name, int which)
{
  post_plugins_t *posts = this->postplugins;
  int result = 0;

  if(!this)
    return 0;

  if(name && !strcmp(name, "AudioVisualization")) {
    name = NULL;
    which = POST_AUDIO_VIS;
  }
  if(name && !strcmp(name, "Pip")) {
    name = NULL;
    which = POST_VIDEO_PIP;
  }

  /* by name */
  if(name) {
    LOGMSG("closing post plugin: %s", name);
    if(applugin_unload_post(posts, name)) {
      /*LOGDBG("  * rewiring audio");*/
      applugin_rewire_posts(posts);
      return 1;
    }
    if(vpplugin_unload_post(posts, name)) {
      /*LOGDBG("  * rewiring video");*/
      vpplugin_rewire_posts(posts);
      return 1;
    }
    return 0;
  }

  /* by type */
  if(which == POST_AUDIO_VIS || which < 0) { /* audio visualization */
    if(posts->post_vis_elements_num && 
       posts->post_vis_elements &&   
       posts->post_vis_elements[0]) {
      LOGMSG("Closing audio visualization post plugins");
      if(applugin_unload_post(posts, posts->post_vis_elements[0]->name)) {
	/*LOGDBG("  * rewiring audio");*/
	applugin_rewire_posts(posts);
	result = 1;
      }
    }
  }

  if(which == POST_AUDIO || which < 0) { /* audio effect(s) */
    LOGMSG("Closing audio post plugins");
    if(applugin_disable_post(posts, NULL)) {
      /*LOGDBG("  * rewiring audio");*/
      applugin_rewire_posts(posts);
      result = 1;
    }
  }
  if(which == POST_VIDEO || which < 0) { /* video effect(s) */
    LOGMSG("Closing video post plugins");
    if(vpplugin_unload_post(posts, NULL)) {
      /*LOGDBG("  * rewiring video");*/
      vpplugin_rewire_posts(posts);
      result = 1;
    }
  }

  if(which == POST_VIDEO_PIP || which < 0) { /* Picture-In-Picture */
    if(posts->post_pip_elements_num && 
       posts->post_pip_elements &&   
       posts->post_pip_elements[0]) {
      LOGMSG("Closing PIP (mosaico) post plugins");
      if(vpplugin_unload_post(posts, "mosaico")) {
	/*LOGDBG("  * rewiring video");*/
	vpplugin_rewire_posts(posts);
	result = 1;
      }
    }
  }

  /*LOGDBG("Post plugin(s) closed : result=%d", result);*/

  return result;
}

static int fe_post_open(fe_t *this, char *name, char *args)
{
  post_plugins_t *posts = this->postplugins;
  char initstr[1024];
  int found = 0;

  if(!this || !this->xine || !this->stream)
    return 0;

  /* pip */
  if(!strcmp(name, "Pip")) {
    posts->post_pip_enable = 1;
    name = "mosaico";
    if(!posts->post_pip_elements ||
       !posts->post_vis_elements[0] ||
       !posts->post_vis_elements[0]->enable)
      LOGMSG("enabling picture-in-picture (\"%s\") post plugin", initstr);
  }

  if(args)
    sprintf(initstr, "%s:%s", name, args);
  else
    strcpy(initstr, name);

  LOGDBG("opening post plugin: %s", initstr);

  /* close old audio visualization plugin */
  if(!strcmp(name,"goom") || !strcmp(name,"oscope") || 
     !strcmp(name,"fftscope") || !strcmp(name,"fftgraph")) {

    /*LOGDBG("  * %s is audio visualization", name);*/
    /* close if changed */
    if(posts->post_vis_elements_num && 
       posts->post_vis_elements &&   
       posts->post_vis_elements[0] &&
       strcmp(name, posts->post_vis_elements[0]->name)) {
      /*LOGDBG("  * visualization changed, unloading %s", 
	posts->post_vis_elements[0]->name);*/
      fe_post_close(this, NULL, POST_AUDIO_VIS);
    }

    posts->post_vis_enable = 1;
  }

  if(vpplugin_enable_post(posts, initstr, &found)) {
    posts->post_video_enable = 1;
    /*LOGDBG("  * rewiring video");*/
    vpplugin_rewire_posts(posts);
    return 1;
  }
  if(!found && applugin_enable_post(posts, initstr, &found)) {
    posts->post_audio_enable = 1;
    /*LOGDBG("  * rewiring audio");*/
    applugin_rewire_posts(posts);
    return 1;
  }

  if(!found)
    LOGERR("Can't load post plugin %s", name);
  else
    LOGDBG("Post plugin %s loaded and wired", name);
    
  return 0;
}

static int fe_xine_play(frontend_t *this_gen) 
{
  fe_t *this = (fe_t*)this_gen;
  vdr_input_plugin_t *input_vdr;

  if(!this)
    return 0;

  fe_post_rewire(this);

  this->input = NULL;
  this->playback_finished = xine_play(this->stream, 0, 0) ? 0 : 1;

  if(!this->input && !find_input(this))
    return -1;
  input_vdr = (vdr_input_plugin_t *)this->input;
  input_vdr->f.xine_input_event = this->keypress;
  input_vdr->f.fe_control = fe_control;
  input_vdr->f.fe_handle  = (void*)this;

  if(!this->playback_finished && this->keypress)
    this->keypress("XKeySym", "");

  if(this->playback_finished)
    LOGMSG("Error playing xvdr:// !");

  return !this->playback_finished;
}

static int fe_xine_stop(frontend_t *this_gen) 
{
  fe_t *this = (fe_t*)this_gen;

  if(!this)
    return 0;

  this->input = NULL;
  this->playback_finished = 1;

  xine_stop(this->stream);

  fe_post_unwire(this);

  return 1;
}

static void fe_xine_close(frontend_t *this_gen) 
{
  fe_t *this = (fe_t*)this_gen;

  if(!this)
    return;

  if (this && this->xine) {
#ifndef FE_STANDALONE
    if(this->input) {
      vdr_input_plugin_t *input_vdr;
      input_vdr = (vdr_input_plugin_t *)this->input;
      input_vdr->f.xine_input_event = NULL;
    }
#endif

    fe_xine_stop(this_gen);

    fe_post_unload(this);

    xine_close(this->stream);
    if(this->postplugins->pip_stream) 
      xine_close(this->postplugins->pip_stream);
  }
}

static void fe_xine_exit(frontend_t *this_gen) 
{
  fe_t *this = (fe_t*)this_gen;

  if (this && this->xine) {

    if(this->input || !this->playback_finished)
      fe_xine_close(this_gen);
    fe_post_unload(this);

    xine_config_save (this->xine, this->configfile);
    if(this->event_queue)
      xine_event_dispose_queue(this->event_queue);
    this->event_queue = NULL;

    if(this->stream)
      xine_dispose(this->stream);
    this->stream = NULL;

    if(this->postplugins->pip_stream) 
      xine_dispose(this->postplugins->pip_stream);
    this->postplugins->pip_stream = NULL;

    if(this->postplugins->slave_stream) 
      xine_dispose(this->postplugins->slave_stream);
    this->postplugins->slave_stream = NULL;

    if(this->audio_port)
      xine_close_audio_driver(this->xine, this->audio_port);
    this->audio_port = NULL;

    if(this->video_port)
      xine_close_video_driver(this->xine, this->video_port);
    this->video_port = NULL;

    if(this->postplugins->static_post_plugins)
      free(this->postplugins->static_post_plugins);
    free(this->postplugins);
    this->postplugins = NULL;

    xine_exit(this->xine);
    this->xine = NULL;
  }
}

static void fe_free(frontend_t *this_gen) 
{
  fe_t *this = (fe_t*)this_gen;

  if (this) {
    if(this->display)
      this->fe.fe_display_close(this_gen);
    free(this);
  }
}

static int fe_is_finished(frontend_t *this_gen)
{
  fe_t *this = (fe_t*)this_gen;
  return this ? this->playback_finished : 1;
}

/************************** hooks to input plugin ****************************/

#ifndef FE_STANDALONE

static int xine_control(frontend_t *this_gen, char *cmd)
{
  fe_t *this = (fe_t*)this_gen;
  vdr_input_plugin_t *input_vdr;

  if(!this->input && !find_input(this))
    return -1;

  input_vdr = (vdr_input_plugin_t *)this->input;
  return input_vdr->f.push_input_control(this->input, cmd);
}

static int xine_osd_command(frontend_t *this_gen, osd_command_t *cmd) {
  fe_t *this = (fe_t*)this_gen;
  vdr_input_plugin_t *input_vdr;

  if(!this->input && !find_input(this))
    return -1;

  input_vdr = (vdr_input_plugin_t *)this->input;
  return input_vdr->f.push_input_osd(this->input, cmd);
}

static int xine_queue_pes_packet(frontend_t *this_gen, char *data, int len)
{
  fe_t *this = (fe_t*)this_gen;
  vdr_input_plugin_t *input_vdr;

  if(!this->input && !find_input(this))
    return 0/*-1*/;

#if 0
  if(len<6) {
    LOGMSG("xine_queue_pes_packet: len == %d, too short!", len);
    abort();
  }
  /* must contain single pes packet and nothing else */
  if(data[0] || data[1] || (data[2] != 1)) {
    static int counter=0;
    counter++;
    LOGMSG("xine_queue_pes_packet: packet not starting with 00 00 01 \n"
	   "    packet #%d, size=%d : %02x %02x %02x %02x %02x %02x\n",
	   counter, len,
	   data[0], data[1], data[2], data[3], data[4], data[5]);
    abort();
  }
#endif

  input_vdr = (vdr_input_plugin_t *)this->input;
  return input_vdr->f.push_input_write(this->input, data, len);
}

#else /* #ifndef FE_STANDALONE */

static void process_xine_keypress(input_plugin_t *input, char *map, char *key,
				  int repeat, int release)
{
  /* from UI --> input plugin --> vdr */
  LOGDBG("Keypress: %s %s %s %s\n", 
	 map, key, repeat?"Repeat":"", release?"Release":"");
  if(input) {
    vdr_input_plugin_t *input_vdr = (vdr_input_plugin_t *)input;
    if(input_vdr->f.input_control) {
      input_vdr->f.input_control(input, map, key, repeat, release);
    } else {
      LOGMSG("Keypress --- NO HANDLER SET");
    }
  } else {
    LOGMSG("Keypress --- NO PLUGIN FOUND");
  }
}

#endif /* #ifndef FE_STANDALONE */

/*
 *  Control messages from input plugin
 */
static void *fe_control(void *fe_handle, char *cmd)
{
  fe_t *this = (fe_t*)fe_handle;
  post_plugins_t *posts = this->postplugins;

  /*LOGDBG("fe_control(\"%s\")", cmd);*/

  if(!strncmp(cmd, "SLAVE 0x", 8)) {
    unsigned int pt;
    if(1 == sscanf(cmd, "SLAVE 0x%x", &pt)) {
      xine_stream_t *slave_stream = (xine_stream_t*)pt;
      if(posts->slave_stream != slave_stream) {
	fe_post_unwire(this);
	posts->slave_stream = slave_stream;
	fe_post_rewire(this);
      }
    }

  } else if(!strncmp(cmd, "SUBSTREAM ", 10)) {
    unsigned int pid;
    int x, y, w, h;
    if(5 == sscanf(cmd, "SUBSTREAM 0x%x %d %d %d %d", &pid, &x, &y, &w, &h)) {
      char mrl[256];
      if(!posts->pip_stream)
	posts->pip_stream = xine_stream_new(this->xine, 
					    this->audio_port, 
					    this->video_port);
      LOGMSG("  PIP %d: %dx%d @ (%d,%d)", pid & 0xf0, w, h, x, y);
      LOGMSG("create pip stream done");
      sprintf(mrl, "xvdr:slave:0x%x#nocache;demux:mpeg_block",(int)this);
      if(!xine_open(posts->pip_stream, mrl) ||
	 !xine_play(posts->pip_stream, 0, 0)) {
	LOGERR("  pip stream open/play failed");
      } else {
	char params[256];
	sprintf(params, "pip_num=1,x=%d,y=%d,w=%d,h=%d", x,y,w,h);
	fe_post_open(this, "Pip", params);
	return posts->pip_stream;
      }
    }
    fe_post_close(this, NULL, POST_VIDEO_PIP);
    if(posts->pip_stream) {
      xine_close(posts->pip_stream);
      xine_dispose(posts->pip_stream);
      posts->pip_stream = NULL;
    }
    return NULL;
    
  } else if(!strncmp(cmd, "POST ", 5)) {
    char *name = strdup(cmd+5), *args = name, *pt;

    if(NULL != (pt=strchr(name, '\r')))
      *pt = 0;
    if(NULL != (pt=strchr(name, '\n')))
      *pt = 0;

    while(*args && *args != ' ') /* skip name */
      args++;
    if(*args /*== ' '*/)
      *args++ = 0;

    while(*args && *args == ' ')  /* skip whitespace between name and args */
      args++;

    if(!strncmp(args, "On", 2)) {
      args += 2;
      while(*args == ' ') 
	args++;
      /*LOGDBG("  POST: %s On \"%s\"", name, args);*/
      fe_post_open(this, name, args);
    } else if(!strncmp(args, "Off", 3)) {
      /*LOGDBG("  POST: %s Off (name len=%d), name => int = %d", name, strlen(name), atoi(name));*/
      if(strlen(name) == 1)
	fe_post_close(this, NULL, atoi(name));
      else
	fe_post_close(this, name, -1);
    } else {
      LOGMSG("fe_control: POST: unknown command %s", cmd);
    }
    free(name);
    return NULL;
  }
  
  return NULL;
}

#ifndef FE_STANDALONE

/*
 *  TODO: remote version: send to ctrl stream
 *        - move to xine_input_vdr ?
 *
 * --- RgbToJpeg -------------------------------------------------------------
 *
 *     source: vdr-1.3.42, tools.c
 *     modified to accept YUV data
 */

#define JPEGCOMPRESSMEM 500000

typedef struct tJpegCompressData_s {
  int size;
  unsigned char *mem;
} tJpegCompressData;

static void JpegCompressInitDestination(j_compress_ptr cinfo)
{
  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
  if (jcd) {
     cinfo->dest->free_in_buffer = jcd->size = JPEGCOMPRESSMEM;
     cinfo->dest->next_output_byte = jcd->mem = 
       (unsigned char *)malloc(jcd->size);
     }
}

static boolean JpegCompressEmptyOutputBuffer(j_compress_ptr cinfo)
{
  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
  if (jcd) {
     int Used = jcd->size;
     jcd->size += JPEGCOMPRESSMEM;
     jcd->mem = (unsigned char *)realloc(jcd->mem, jcd->size);
     if (jcd->mem) {
        cinfo->dest->next_output_byte = jcd->mem + Used;
        cinfo->dest->free_in_buffer = jcd->size - Used;
        return TRUE;
        }
     }
  return FALSE;
}

static void JpegCompressTermDestination(j_compress_ptr cinfo)
{
  tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
  if (jcd) {
     int Used = cinfo->dest->next_output_byte - jcd->mem;
     if (Used < jcd->size) {
        jcd->size = Used;
        jcd->mem = (unsigned char *)realloc(jcd->mem, jcd->size);
        }
     }
}

static char *fe_grab(frontend_t *this_gen, int *size, int jpeg, 
		     int quality, int width, int height)
{
  struct jpeg_destination_mgr jdm;
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  tJpegCompressData jcd;

  fe_t *this = (fe_t*)this_gen;
  vo_frame_t *frame;

#ifndef PPM_SUPPORTED
  if(!jpeg) {
    LOGMSG("fe_grab: PPM grab not implemented");
    return 0;
  }
#else
  /* #warning TODO: convert to RGB PPM */
#endif

  if(!this->input && !find_input(this))
    return 0;

  LOGMSG("fe_grab: grabbing %s %d %dx%d", 
	 jpeg ? "JPEG" : "PNM", quality, width, height);

  if (quality < 0 || quality > 100)
    quality = 100; /* -1 defaults to 100 */

  this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 0);
  frame = this->stream->video_out->get_last_frame (this->stream->video_out);
  if(frame)
    frame->lock(frame);
  this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 0);

  if(!frame)
    return NULL;

  /* #warning TODO: no scaling implemented */
  if(width != frame->width)
    width = frame->width;
  if(height != frame->height)
    height = frame->height;

  /* Compress JPEG */

  jdm.init_destination = JpegCompressInitDestination;
  jdm.empty_output_buffer = JpegCompressEmptyOutputBuffer;
  jdm.term_destination = JpegCompressTermDestination;

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);
  cinfo.dest = &jdm;

  cinfo.client_data = &jcd;
  cinfo.image_width = width;
  cinfo.image_height = height;
  cinfo.input_components = 3;
  /*cinfo.in_color_space = JCS_RGB;*/
  cinfo.in_color_space = JCS_YCbCr;

  jpeg_set_defaults(&cinfo);
  jpeg_set_quality(&cinfo, quality, TRUE);

  switch (frame->format) {
    case XINE_IMGFMT_YV12: {
      JSAMPARRAY pp[3];
      JSAMPROW rpY[height];
      JSAMPROW rpU[height];
      JSAMPROW rpV[height];
      int k;
      cinfo.raw_data_in = TRUE;
      jpeg_set_colorspace(&cinfo, JCS_YCbCr);
      cinfo.comp_info[0].h_samp_factor =
      cinfo.comp_info[0].v_samp_factor = 2;
      cinfo.comp_info[1].h_samp_factor =
      cinfo.comp_info[1].v_samp_factor =
      cinfo.comp_info[2].h_samp_factor =
      cinfo.comp_info[2].v_samp_factor = 1;
      jpeg_start_compress(&cinfo, TRUE);
      
      for (k = 0; k < height; k+=2) {
	rpY[k]   = frame->base[0] + k*frame->pitches[0];
	rpY[k+1] = frame->base[0] + (k+1)*frame->pitches[0];
	rpU[k/2] = frame->base[1] + (k/2)*frame->pitches[1];
	rpV[k/2] = frame->base[2] + (k/2)*frame->pitches[2];
      }
      for (k = 0; k < height; k+=2*DCTSIZE) {
	pp[0] = &rpY[k];
	pp[1] = &rpU[k/2];
	pp[2] = &rpV[k/2];
	jpeg_write_raw_data(&cinfo, pp, 2*DCTSIZE);
      }
      break;
    }
    case XINE_IMGFMT_YUY2: {
      JSAMPROW rp[height];
      int rs, k;
      jpeg_start_compress(&cinfo, TRUE);
      rs = frame->pitches[0];
      for (k = 0; k < height; k++)
	rp[k] = frame->base[0] + k*rs;
      jpeg_write_scanlines(&cinfo, rp, height);
      break;
    }
#if 0
    case XINE_IMGFMT_RGB: {
      JSAMPROW rp[height];
      int rs, k;
      cinfo.in_color_space = JCS_RGB;
      jpeg_start_compress(&cinfo, TRUE);
      rs = frame->pitches[0];
      for (k = 0; k < height; k++)
	rp[k] = frame->base[0] + k*rs;
      jpeg_write_scanlines(&cinfo, rp, height);
      break;
    }
#endif
    default:
      LOGMSG("fe_grab: grabbing failed (unsupported image format %d)", 
	     frame->format);
      break;
  }
  jpeg_finish_compress(&cinfo);
  jpeg_destroy_compress(&cinfo);
  
  frame->free(frame);

  *size = jcd.size;
  return (char*) jcd.mem;
}

#endif /* #ifndef FE_STANDALONE */

#ifdef FE_STANDALONE

/* VDR discovery protocol code */
#include "xine_frontend_vdrdiscovery.c"
/* LIRC forwarding code */
#include "xine_frontend_lirc.c"
/* frontend main() */
#include "xine_frontend_main.c"


#endif /* #ifdef FE_STANDALONE */

