/*
 * Copyright (C) 2000-2018 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
 *
 * post plugin definitions
 */

#ifndef XINE_POST_H
#define XINE_POST_H

#include <pthread.h>

#include <xine.h>
#include <xine/attributes.h>
#include <xine/video_out.h>
#include <xine/audio_out.h>
#include <xine/xine_internal.h>
#include <xine/xineutils.h>

struct plugin_node_s;

#define POST_PLUGIN_IFACE_VERSION 10


typedef struct post_class_s post_class_t;
typedef struct post_plugin_s post_plugin_t;
typedef struct post_in_s post_in_t;
typedef struct post_out_s post_out_t;

struct post_class_s {

  /*
   * open a new instance of this plugin class
   */
  post_plugin_t* (*open_plugin) (post_class_t *this_gen, int inputs,
				 xine_audio_port_t **audio_target,
				 xine_video_port_t **video_target);

  /**
   * @brief short human readable identifier for this plugin class
   */
  const char *identifier;

  /**
   * @brief human readable (verbose = 1 line) description for this plugin class
   *
   * The description is passed to gettext() to internationalise.
   */
  const char *description;

  /**
   * @brief Optional non-standard catalog to use with dgettext() for description.
   */
  const char *text_domain;

  /*
   * free all class-related resources
   */

  void (*dispose) (post_class_t *this_gen);
};

#define default_post_class_dispose (void (*) (post_class_t *this_gen))free

struct post_plugin_s {

  /* public part of the plugin */
  xine_post_t         xine_post;

  /*
   * the connections announced by the plugin
   * the plugin must fill these with xine_post_{in,out}_t on init
   */
  xine_list_t        *input;
  xine_list_t        *output;

  /*
   * close down, free all resources
   */
  void (*dispose) (post_plugin_t *this_gen);

  /* plugins don't have to init the stuff below */

  /*
   * the running ticket
   *
   * the plugin must assure to check for ticket revocation in
   * intervals of finite length; this means that you must release
   * the ticket before any operation that might block;
   * note that all port functions are safe in this respect
   *
   * the running ticket is assigned to you by the engine
   */
  xine_ticket_t      *running_ticket;

  /* this is needed by the engine to decrement the reference counter
   * on disposal of the plugin, but since this is useful, we expose it */
  xine_t             *xine;

  /* used when the user requests a list of all inputs/outputs */
  const char        **input_ids;
  const char        **output_ids;

  /**
   * @brief Pointer to the loaded plugin node.
   *
   * Used by the plugins loader. It's an opaque type when using the
   * structure outside of xine's build.
   */
  struct plugin_node_s *node XINE_PRIVATE_FIELD;

  /* has dispose been called */
  int                 dispose_pending;
};

/* helper function to initialize a post_plugin_t */
void _x_post_init(post_plugin_t *post, int num_audio_inputs, int num_video_inputs) XINE_PROTECTED;

struct post_in_s {

  /* public part of the input */
  xine_post_in_t   xine_in;

  /* backward reference so that you have access to the post plugin */
  post_plugin_t   *post;

  /* you can fill this to your liking */
  void            *user_data;
};

struct post_out_s {

  /* public part of the output */
  xine_post_out_t  xine_out;

  /* backward reference so that you have access to the post plugin */
  post_plugin_t   *post;

  /* you can fill this to your liking */
  void            *user_data;
};


/* Post plugins work by intercepting calls to video or audio ports
 * in the sense of the decorator design pattern. They reuse the
 * functions of a given target port, but add own functionality in
 * front of that port by creating a new port structure and filling in
 * the function pointers with pointers to own functions that
 * would do something and then call the original port function.
 *
 * Much the same is done with video frames which have their own
 * set of functions attached that you might need to decorate.
 */


/* helper structure for intercepting video port calls */
typedef struct post_video_port_s post_video_port_t;
struct post_video_port_s {

  /* the new public port with replaced function pointers */
  xine_video_port_t         new_port;

  /* the original port to call its functions from inside yours */
  xine_video_port_t        *original_port;

  /* if you want to decide yourself, whether a given frame should
   * be intercepted, fill in this function; get_frame() acts as
   * a template method and asks your function; return a boolean;
   * the default is to intercept all frames */
  int (*intercept_frame)(post_video_port_t *self, vo_frame_t *frame);

  /* the new frame function pointers */
  vo_frame_t               *new_frame;

  /* if you want to decide yourself, whether the preprocessing functions
   * should still be routed when draw is intercepted, fill in this
   * function; _x_post_intercept_video_frame() acts as a template method
   * and asks your function; return a boolean; the default is _not_ to
   * route preprocessing functions when draw is intercepted */
  int (*route_preprocessing_procs)(post_video_port_t *self, vo_frame_t *frame);

  /* if you want to decide yourself, whether the overlay manager should
   * be intercepted, fill in this function; get_overlay_manager() acts as
   * a template method and asks your function; return a boolean;
   * the default is _not_ to intercept the overlay manager */
  int (*intercept_ovl)(post_video_port_t *self);

  /* the new public overlay manager with replaced function pointers */
  video_overlay_manager_t  *new_manager;

  /* the original manager to call its functions from inside yours */
  video_overlay_manager_t  *original_manager;

  /* usage counter: how many objects are floating around that need
   * these pointers to exist */
  int                       usage_count;
  pthread_mutex_t           usage_lock;

  /* the stream we are being fed by; NULL means no stream is connected;
   * this may be an anonymous stream */
  xine_stream_t            *stream;

  /* point to a mutex here, if you need some synchronization */
  pthread_mutex_t          *port_lock;
  pthread_mutex_t          *frame_lock;
  pthread_mutex_t          *manager_lock;

  /* backward reference so that you have access to the post plugin
   * when the call only gives you the port */
  post_plugin_t            *post;

  /* you can fill this to your liking */
  void                     *user_data;

#ifdef POST_INTERNAL
  /* some of the above members are to be directly included here, but
   * adding the structures would mean that post_video_port_t becomes
   * depended of the sizes of these structs; solution: we add pointers
   * above and have them point into the memory provided here;
   * note that the overlay manager needs to be first so that we can
   * reconstruct the post_video_port_t* from overlay manager calls */

  /* any change here requires a change in _x_post_ovl_manager_to_port()
   * below! */

  video_overlay_manager_t   manager_storage;
  vo_frame_t                frame_storage;

  /* this is used to keep a linked list of free vo_frame_t's */
  vo_frame_t               *free_frame_slots;
  pthread_mutex_t           free_frames_lock;
#endif
};

/* use this to create a new decorated video port in which
 * port functions will be replaced with own implementations;
 * for convenience, this can also create a related post_in_t and post_out_t */
post_video_port_t *_x_post_intercept_video_port(post_plugin_t *post, xine_video_port_t *port,
						post_in_t **input, post_out_t **output) XINE_PROTECTED;

/* use this to decorate and to undecorate a frame so that its functions
 * can be replaced with own implementations, decoration is usually done in
 * get_frame(), undecoration in frame->free() */
vo_frame_t *_x_post_intercept_video_frame(vo_frame_t *frame, post_video_port_t *port) XINE_PROTECTED;
vo_frame_t *_x_post_restore_video_frame(vo_frame_t *frame, post_video_port_t *port) XINE_PROTECTED;

/* when you want to pass a frame call on to the original issuer of the frame,
 * you need to propagate potential changes up and down the pipe, so the usual
 * procedure for this situation would be:
 *
 *   _x_post_frame_copy_down(frame, frame->next);
 *   frame->next->function(frame->next);
 *   _x_post_frame_copy_up(frame, frame->next);
 */
void _x_post_frame_copy_down(vo_frame_t *from, vo_frame_t *to) XINE_PROTECTED;
void _x_post_frame_copy_up(vo_frame_t *to, vo_frame_t *from) XINE_PROTECTED;

/* when you shortcut a frames usual draw() travel so that it will never reach
 * the draw() function of the original issuer, you still have to do some
 * housekeeping on the frame, before returning control up the pipe */
void _x_post_frame_u_turn(vo_frame_t *frame, xine_stream_t *stream) XINE_PROTECTED;

/* use this to create a new, trivially decorated overlay manager in which
 * port functions can be replaced with own implementations */
void _x_post_intercept_overlay_manager(video_overlay_manager_t *manager, post_video_port_t *port) XINE_PROTECTED;

/* pointer retrieval functions */
static inline post_video_port_t *_x_post_video_frame_to_port(vo_frame_t *frame) {
  return (post_video_port_t *)frame->port;
}

static inline post_video_port_t *_x_post_ovl_manager_to_port(video_overlay_manager_t *manager) {
#ifdef POST_INTERNAL
  return (post_video_port_t *)( (uint8_t *)manager -
    (uint8_t*)&(((post_video_port_t *)NULL)->manager_storage) );
#else
  return (post_video_port_t *)( (uint8_t *)manager - sizeof(post_video_port_t) );
#endif
}


/* helper structure for intercepting audio port calls */
typedef struct post_audio_port_s post_audio_port_t;
struct post_audio_port_s {

  /* the new public port with replaced function pointers */
  xine_audio_port_t  new_port;

  /* the original port to call its functions from inside yours */
  xine_audio_port_t *original_port;

  /* the stream we are being fed by; NULL means no stream is connected;
   * this may be an anonymous stream */
  xine_stream_t     *stream;

  pthread_mutex_t    usage_lock;
  /* usage counter: how many objects are floating around that need
   * these pointers to exist */
  int                usage_count;

  /* some values remembered by (port->open) () */
  uint32_t           bits;
  uint32_t           rate;
  uint32_t           mode;

  /* point to a mutex here, if you need some synchronization */
  pthread_mutex_t   *port_lock;

  /* backward reference so that you have access to the post plugin
   * when the call only gives you the port */
  post_plugin_t     *post;

  /* you can fill this to your liking */
  void              *user_data;
};

/* use this to create a new decorated audio port in which
 * port functions will be replaced with own implementations */
post_audio_port_t *_x_post_intercept_audio_port(post_plugin_t *post, xine_audio_port_t *port,
						post_in_t **input, post_out_t **output) XINE_PROTECTED;


/* If you do intercept these decoder-called functions
 * (that is, you do not use post defaults), please
 * open ():
 *   _x_post_inc_usage (port);
 *   _x_post_rewire (port->post);
 *   ...
 * close ():
 *   ...
 *   _x_post_dec_usage (port);
 * get_buffer ():
 *   _x_post_inc_usage (port);
 *   ...
 *   port->original_port->get_buffer (port->original_port); and/or
 *   while (not_done_yet) {
 *     _x_post_rewire (port->post);
 *     timed_wait (done);
 *   }
 *   ...
 *   _x_post_dec_usage (port);
 * get_frame ():
 *   _x_post_inc_usage (port);
 *   ...
 *   port->original_port->get_frame (port->original_port, ...); and/or
 *   while (not_done_yet) {
 *     _x_post_rewire (port->post);
 *     timed_wait (done);
 *   }
 *   ...
 *   if (this_frame_is_not_intercepted)
 *     _x_post_dec_usage (port);
 * frame.free ():
 *   ...
 *   _x_post_dec_usage (port);
 * this will allow pending rewire operations, while preventing your port from getting
 * pulled from under your feet by the possible rewire. */
static inline void _x_post_rewire(post_plugin_t *post) {
  if (post->running_ticket->ticket_revoked)
    post->running_ticket->renew(post->running_ticket, 1);
}

/* with these functions you can switch interruptions like rewiring or engine pausing
 * off for a block of code; use this only when really necessary */
static inline void _x_post_lock(post_plugin_t *post) {
  post->running_ticket->acquire(post->running_ticket, 1);
}
static inline void _x_post_unlock(post_plugin_t *post) {
  post->running_ticket->release(post->running_ticket, 1);
  _x_post_rewire(post);
}

/* the standard disposal operation; returns 1 if the plugin is really
 * disposed and you should free everything you malloc()ed yourself */
int _x_post_dispose(post_plugin_t *post) XINE_PROTECTED;


/* macros to handle usage counter */

/* WARNING!
 * note that _x_post_dec_usage() can call dispose, so be sure to
 * not use any potentially already freed memory after this */

#define _x_post_inc_usage(port)                                    \
do {                                                               \
  pthread_mutex_lock(&(port)->usage_lock);                         \
  (port)->usage_count++;                                           \
  pthread_mutex_unlock(&(port)->usage_lock);                       \
} while(0)

#define _x_post_dec_usage(port)                                    \
do {                                                               \
  pthread_mutex_lock(&(port)->usage_lock);                         \
  (port)->usage_count--;                                           \
  if ((port)->usage_count == 0) {                                  \
    if ((port)->post->dispose_pending) {                           \
      pthread_mutex_unlock(&(port)->usage_lock);                   \
      (port)->post->dispose((port)->post);                         \
    } else                                                         \
      pthread_mutex_unlock(&(port)->usage_lock);                   \
  } else                                                           \
    pthread_mutex_unlock(&(port)->usage_lock);                     \
} while(0)

#ifdef POST_INTERNAL
/* try to recognize post ports, do the above, return new use count.
 * otherwise, return -1. */
int _x_post_video_port_ref (xine_video_port_t *port_gen);
int _x_post_video_port_unref (xine_video_port_t *port_gen);
int _x_post_audio_port_ref (xine_audio_port_t *port_gen);
int _x_post_audio_port_unref (xine_audio_port_t *port_gen);
#endif

/* macros to create parameter descriptors */

#define START_PARAM_DESCR( param_t ) \
typedef param_t temp_t; \
static xine_post_api_parameter_t temp_p[] = {

#ifndef offsetof
#include <stddef.h>
#endif

#define PARAM_ITEM( param_t, param_type, var, enumv, min, max, readonly, descr ) \
{ param_type, #var, sizeof(((temp_t*)0)->var),                        \
  offsetof(temp_t, var), enumv, min, max, readonly, descr },

#define END_PARAM_DESCR( name ) \
  { POST_PARAM_TYPE_LAST, NULL, 0, 0, NULL, 0, 0, 1, NULL } \
}; \
static xine_post_api_descr_t name = { \
  sizeof( temp_t ), \
  temp_p \
};

#endif
