/*
 * Copyright (C) 2000-2004 the xine project
 * 
 * This file is part of xine, a free video player.
 * 
 * xine 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.
 * 
 * xine 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 */
 
/*
 * simple video mosaico plugin
 */

#define LOG_MODULE "mosaico"
#define LOG_VERBOSE
/*
#define LOG
*/

#include "xine_internal.h"
#include "post.h"

/* FIXME: This plugin needs to handle overlays as well. */

/* plugin class initialization function */
static void *mosaico_init_plugin(xine_t *xine, void *);

/* plugin catalog information */
static const post_info_t mosaico_special_info = { XINE_POST_TYPE_VIDEO_COMPOSE };

const plugin_info_t xine_plugin_info[] EXPORTED = {
  /* type, API, "name", version, special_info, init_function */  
  { PLUGIN_POST, 9, "mosaico", XINE_VERSION_CODE, &mosaico_special_info, &mosaico_init_plugin },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};

typedef struct mosaico_parameters_s {
  unsigned int  pip_num;
  unsigned int  x, y, w, h;
} mosaico_parameters_t;

START_PARAM_DESCR(mosaico_parameters_t)
PARAM_ITEM(POST_PARAM_TYPE_INT, pip_num, NULL, 1, INT_MAX, 1,
  "which picture slots settings are being edited")
PARAM_ITEM(POST_PARAM_TYPE_INT, x, NULL, 0, INT_MAX, 50,
  "x coordinate of the pasted picture")
PARAM_ITEM(POST_PARAM_TYPE_INT, y, NULL, 0, INT_MAX, 50,
  "y coordinate of the pasted picture")
PARAM_ITEM(POST_PARAM_TYPE_INT, w, NULL, 0, INT_MAX, 150,
  "width of the pasted picture")
PARAM_ITEM(POST_PARAM_TYPE_INT, h, NULL, 0, INT_MAX, 150,
  "height of the pasted picture")
END_PARAM_DESCR(mosaico_param_descr)

typedef struct post_class_mosaico_s post_class_mosaico_t;
typedef struct post_mosaico_s post_mosaico_t;

struct post_class_mosaico_s {
  post_class_t    class;
  xine_t         *xine;
};

/* plugin structures */
typedef struct mosaico_pip_s mosaico_pip_t;
struct mosaico_pip_s {
  unsigned int  x, y, w, h;
  vo_frame_t   *frame;
  char         *input_name;
};

struct post_mosaico_s {
  post_plugin_t    post;  
  xine_post_in_t   parameter_input;
  
  mosaico_pip_t   *pip;
  int64_t          vpts_limit;
  pthread_cond_t   vpts_limit_changed;
  int64_t          skip_vpts;
  int              skip;
  pthread_mutex_t  mutex;
  unsigned int     pip_count;
};

/* plugin class functions */
static post_plugin_t *mosaico_open_plugin(post_class_t *class_gen, int inputs,
					 xine_audio_port_t **audio_target,
					 xine_video_port_t **video_target);
static char          *mosaico_get_identifier(post_class_t *class_gen);
static char          *mosaico_get_description(post_class_t *class_gen);
static void           mosaico_class_dispose(post_class_t *class_gen);

/* plugin instance functions */
static void           mosaico_dispose(post_plugin_t *this_gen);

/* parameter functions */
static xine_post_api_descr_t *mosaico_get_param_descr(void);
static int            mosaico_set_parameters(xine_post_t *this_gen, void *param_gen);
static int            mosaico_get_parameters(xine_post_t *this_gen, void *param_gen);
static char          *mosaico_get_help(void);

/* replaced video port functions */
static void           mosaico_close(xine_video_port_t *port_gen, xine_stream_t *stream);

/* frame intercept check */
static int            mosaico_intercept_frame(post_video_port_t *port, vo_frame_t *frame);

/* replaced vo_frame functions */
static int            mosaico_draw_background(vo_frame_t *frame, xine_stream_t *stream);
static int            mosaico_draw(vo_frame_t *frame, xine_stream_t *stream);


static void *mosaico_init_plugin(xine_t *xine, void *data)
{
  post_class_mosaico_t *this = (post_class_mosaico_t *)xine_xmalloc(sizeof(post_class_mosaico_t));

  if (!this)
    return NULL;
  
  this->class.open_plugin     = mosaico_open_plugin;
  this->class.get_identifier  = mosaico_get_identifier;
  this->class.get_description = mosaico_get_description;
  this->class.dispose         = mosaico_class_dispose;
  this->xine                  = xine;

  return &this->class;
}

static post_plugin_t *mosaico_open_plugin(post_class_t *class_gen, int inputs,
					 xine_audio_port_t **audio_target,
					 xine_video_port_t **video_target)
{
  post_mosaico_t       *this = (post_mosaico_t *)xine_xmalloc(sizeof(post_mosaico_t));
  post_in_t            *input;
  xine_post_in_t       *input_api;
  post_out_t           *output;
  post_video_port_t    *port;
  static xine_post_api_t post_api = 
    { mosaico_set_parameters, mosaico_get_parameters, mosaico_get_param_descr, mosaico_get_help };
  int i;
    
  lprintf("mosaico open\n");

  if (inputs < 2 || !this || !video_target || !video_target[0]) {
    free(this);
    return NULL;
  }
  
  _x_post_init(&this->post, 0, inputs);

  this->pip       = (mosaico_pip_t *)xine_xmalloc(sizeof(mosaico_pip_t) * (inputs - 1));
  this->pip_count = inputs - 1;

  pthread_cond_init(&this->vpts_limit_changed, NULL);
  pthread_mutex_init(&this->mutex, NULL);
  
  /* the port for the background video */
  port = _x_post_intercept_video_port(&this->post, video_target[0], &input, &output);
  port->intercept_frame = mosaico_intercept_frame;
  port->new_frame->draw = mosaico_draw_background;
  port->port_lock       = &this->mutex;
  port->frame_lock      = &this->mutex;
  input->xine_in.name   = "video in 0";
  this->post.xine_post.video_input[0] = &port->new_port;

  for (i = 0; i < inputs - 1; i++) {
    this->pip[i].x = 50;
    this->pip[i].y = 50;
    this->pip[i].w = 150;
    this->pip[i].h = 150;
    this->pip[i].input_name = (char *)xine_xmalloc(sizeof("video in ") + 10);
    snprintf(this->pip[i].input_name, sizeof("video in ") + 10, "video in %d", i+1);
    
    port = _x_post_intercept_video_port(&this->post, video_target[0], &input, NULL);
    port->new_port.close  = mosaico_close;
    port->intercept_frame = mosaico_intercept_frame;
    port->new_frame->draw = mosaico_draw;
    port->port_lock       = &this->mutex;
    port->frame_lock      = &this->mutex;
    input->xine_in.name   = this->pip[i].input_name;
    this->post.xine_post.video_input[i+1] = &port->new_port;
  }

  input_api       = &this->parameter_input;
  input_api->name = "parameters";
  input_api->type = XINE_POST_DATA_PARAMETERS;
  input_api->data = &post_api;
  xine_list_push_back(this->post.input, input_api);

  this->post.dispose = mosaico_dispose;

  return &this->post;
}

static char *mosaico_get_identifier(post_class_t *class_gen)
{
  return "mosaico";
}

static char *mosaico_get_description(post_class_t *class_gen)
{
  return "Mosaico is a picture in picture (pip) post plugin";
}

static void mosaico_class_dispose(post_class_t *class_gen)
{
  free(class_gen);
}


static void mosaico_dispose(post_plugin_t *this_gen)
{
  post_mosaico_t *this = (post_mosaico_t *)this_gen;
  
  if (_x_post_dispose(this_gen)) {
    int i;
    for (i = 0; i < this->pip_count; i++)
      free(this->pip[i].input_name);
    free(this->pip);
    pthread_cond_destroy(&this->vpts_limit_changed);
    pthread_mutex_destroy(&this->mutex);
    free(this);
  }
}


static xine_post_api_descr_t *mosaico_get_param_descr(void)
{
  return &mosaico_param_descr;
}

static int mosaico_set_parameters(xine_post_t *this_gen, void *param_gen)
{
  post_mosaico_t *this = (post_mosaico_t *)this_gen;
  mosaico_parameters_t *param = (mosaico_parameters_t *)param_gen;
  
  if (param->pip_num > this->pip_count) return 0;
  this->pip[param->pip_num - 1].x = param->x;
  this->pip[param->pip_num - 1].y = param->y;
  this->pip[param->pip_num - 1].w = param->w;
  this->pip[param->pip_num - 1].h = param->h;
  return 1;
}

static int mosaico_get_parameters(xine_post_t *this_gen, void *param_gen)
{
  post_mosaico_t *this = (post_mosaico_t *)this_gen;
  mosaico_parameters_t *param = (mosaico_parameters_t *)param_gen;
  
  if (param->pip_num > this->pip_count || param->pip_num < 1)
    param->pip_num = 1;
  param->x = this->pip[param->pip_num - 1].x;
  param->y = this->pip[param->pip_num - 1].y;
  param->w = this->pip[param->pip_num - 1].w;
  param->h = this->pip[param->pip_num - 1].h;
  return 1;
}

static char *mosaico_get_help(void)
{
  return _("Mosaico does simple picture in picture effects.\n"
	   "\n"
	   "Parameters\n"
	   "  pip_num: the number of the picture slot the following settings apply to\n"
	   "  x: the x coordinate of the left upper corner of the picture\n"
	   "  y: the y coordinate of the left upper corner of the picture\n"
	   "  w: the width of the picture\n"
	   "  h: the height of the picture\n");
}


static void mosaico_close(xine_video_port_t *port_gen, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)port_gen;
  post_mosaico_t *this = (post_mosaico_t *)port->post;
  vo_frame_t *free_frame;
  int pip_num;

  for (pip_num = 0; pip_num < this->pip_count; pip_num++)
    if (this->post.xine_post.video_input[pip_num+1] == port_gen) break;
  
  pthread_mutex_lock(&this->mutex);
  free_frame = this->pip[pip_num].frame;
  this->pip[pip_num].frame = NULL;
  port->original_port->close(port->original_port, port->stream);
  pthread_mutex_unlock(&this->mutex);

  if (free_frame)
    free_frame->free(free_frame);
  port->stream = NULL;
  _x_post_dec_usage(port);
}


static int mosaico_intercept_frame(post_video_port_t *port, vo_frame_t *frame)
{
  /* TODO: only YV12 supported */
  return (frame->format == XINE_IMGFMT_YV12);
}


static void frame_copy_content(vo_frame_t *to, vo_frame_t *from)
{
  int size;

  switch (from->format) {
  case XINE_IMGFMT_YUY2:
    /* TODO: implement conversion to YV12 or implement support to paste
     * frames of different types together */
    break;     
  
  case XINE_IMGFMT_YV12:
    /* Y */
    size = to->pitches[0] * to->height;   
    xine_fast_memcpy(to->base[0], from->base[0], size);

    /* U */
    size = to->pitches[1] * ((to->height + 1) / 2);
    xine_fast_memcpy(to->base[1], from->base[1], size);

    /* V */
    size = to->pitches[2] * ((to->height + 1) / 2);
    xine_fast_memcpy(to->base[2], from->base[2], size);
  }
}

static void frame_paste(post_mosaico_t *this, vo_frame_t *background, int pip_num)
{
  unsigned long target_width, target_height;
  unsigned long source_width, source_height;
  unsigned long background_width;
  unsigned long scale_x, scale_y;
  const int shift_x = 3, shift_y = 3;
  unsigned long pos_x, pos_y, pos;
  unsigned long target_offset, source_offset;
  unsigned long i, j;

  if (!this->pip[pip_num].frame) return;
  
  target_width  = this->pip[pip_num].w;
  target_height = this->pip[pip_num].h;
  background_width = background->width;
  source_width = this->pip[pip_num].frame->width;
  source_height = this->pip[pip_num].frame->height;
  scale_x = (source_width << shift_x) / target_width;
  scale_y = (source_height << shift_y) / target_height;
  pos_x = this->pip[pip_num].x;
  pos_y = this->pip[pip_num].y;
  pos = pos_y * background_width + pos_x;

  switch (this->pip[pip_num].frame->format) {
  case XINE_IMGFMT_YUY2:
    /* TODO: implement YUY2 */
    break;     
  
  case XINE_IMGFMT_YV12:
    /* Y */
    target_offset = 0;
    for (j = 0; j < target_height; j++, target_offset += (background_width - target_width))
      for (i = 0; i < target_width; i++, target_offset++) {
	source_offset = ((i * scale_x) >> shift_x) + (((j * scale_y) >> shift_y) * source_width);
	background->base[0][pos + target_offset] = this->pip[pip_num].frame->base[0][source_offset];
      }

    background_width = (background_width + 1) / 2;
    source_width = (source_width + 1) / 2;
    pos_x = (pos_x + 1) / 2;
    pos_y = (pos_y + 1) / 2;
    pos = pos_y * background_width + pos_x;
    target_width = (target_width + 1) / 2;
    target_height = (target_height + 1) / 2;
    
    /* U */
    target_offset = 0;
    for (j = 0; j < target_height; j++, target_offset += (background_width - target_width))
      for (i = 0; i < target_width; i++, target_offset++) {
	source_offset = ((i * scale_x) >> shift_x) + (((j * scale_y) >> shift_y) * source_width);
	background->base[1][pos + target_offset] = this->pip[pip_num].frame->base[1][source_offset];
      }
    
    /* V */
    target_offset = 0;
    for (j = 0; j < target_height; j++, target_offset += (background_width - target_width))
      for (i = 0; i < target_width; i++, target_offset++) {
	source_offset = ((i * scale_x) >> shift_x) + (((j * scale_y) >> shift_y) * source_width);
	background->base[2][pos + target_offset] = this->pip[pip_num].frame->base[2][source_offset];
      }
    
    break;
  }
}

static int mosaico_draw_background(vo_frame_t *frame, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  post_mosaico_t *this = (post_mosaico_t *)port->post;
  vo_frame_t *background;
  int pip_num, skip;

  pthread_mutex_lock(&this->mutex);
  
  if (frame->bad_frame) {
    _x_post_frame_copy_down(frame, frame->next);
    skip = frame->next->draw(frame->next, stream);
    _x_post_frame_copy_up(frame, frame->next);
    
    this->vpts_limit = frame->vpts + frame->duration;
    if (skip) {
      this->skip      = skip;
      this->skip_vpts = frame->vpts;
    } else
      this->skip      = 0;
    
    pthread_mutex_unlock(&this->mutex);
    pthread_cond_broadcast(&this->vpts_limit_changed);
    
    return skip;
  }
  
  background = port->original_port->get_frame(port->original_port,
    frame->width, frame->height, frame->ratio, frame->format, frame->flags | VO_BOTH_FIELDS);
  _x_post_frame_copy_down(frame, background);
  frame_copy_content(background, frame);
  
  for (pip_num = 0; pip_num < this->pip_count; pip_num++)
    frame_paste(this, background, pip_num);
  
  skip = background->draw(background, stream);
  _x_post_frame_copy_up(frame, background);
  this->vpts_limit = background->vpts + background->duration;
  background->free(background);
  
  if (skip) {
    this->skip      = skip;
    this->skip_vpts = frame->vpts;
  } else
    this->skip      = 0;
  
  pthread_mutex_unlock(&this->mutex);
  pthread_cond_broadcast(&this->vpts_limit_changed);
  
  return skip;
}

static int mosaico_draw(vo_frame_t *frame, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  post_mosaico_t *this = (post_mosaico_t *)port->post;
  vo_frame_t *free_frame;
  int pip_num, skip;

  for (pip_num = 0; pip_num < this->pip_count; pip_num++)
    if (this->post.xine_post.video_input[pip_num+1] == frame->port) break;
  _x_assert(pip_num < this->pip_count);
  
  frame->lock(frame);
  
  pthread_mutex_lock(&this->mutex);
  
  /* the original output will never see this frame again */
  _x_post_frame_u_turn(frame, stream);
  while (frame->vpts > this->vpts_limit || !this->vpts_limit)
    /* we are too early */
    pthread_cond_wait(&this->vpts_limit_changed, &this->mutex);
  free_frame = this->pip[pip_num].frame;
  if (port->stream)
    this->pip[pip_num].frame = frame;
  
  if (this->skip && frame->vpts <= this->skip_vpts)
    skip = this->skip;
  else
    skip = 0;
  
  pthread_mutex_unlock(&this->mutex);

  if (free_frame)
    free_frame->free(free_frame);
  if (!port->stream)
    /* do not keep this frame when no stream is connected to us,
     * otherwise, this frame might never get freed */
    frame->free(frame);
  
  return skip;
}
