/* Author: Max Howell <max.howell@methylblue.com>, (C) 2004
   Copyright: See COPYING file that comes with this distribution */

/* need access to port_ticket */
#define XINE_ENGINE_INTERNAL

#define LOG_MODULE "codine-scope"
#define LOG_LEVEL LOG_LEVEL_DEBUG
// #define LOG

#include "xineScope.h"
#include <xine/post.h>
#include <xine/xine_internal.h>

typedef struct scope_plugin_s {
   post_plugin_t super;
   int channels;
   int64_t pts_per_smpls;
   MyNode *list;
} scope_plugin_t;

int scope_plugin_channels(void *post)
{
   scope_plugin_t *self = post;
   return self->channels;
}

MyNode *scope_plugin_list(void *post)
{
   scope_plugin_t *self = post;
   return self->list;
}

int64_t scope_plugin_pts_per_smpls(void *post)
{
   scope_plugin_t *self = post;
   return self->pts_per_smpls;
}

/*************************
* post plugin functions *
*************************/

static int
scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode )
{
   lprintf("scope_port_open()\n");

   post_audio_port_t *port = (post_audio_port_t *)port_gen;
   scope_plugin_t *self = (scope_plugin_t *)port->post;

   _x_post_rewire( (post_plugin_t*)port->post );
   _x_post_inc_usage( port );

   port->stream = stream;
   port->bits = bits;
   port->rate = rate;
   port->mode = mode;

   self->channels = _x_ao_mode2channels(mode);

   int ret = port->original_port->open( port->original_port, stream, bits, rate, mode );
#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)
   self->pts_per_smpls = ((uint32_t)90000 * (uint32_t)32768) / rate;
#else
   self->pts_per_smpls = stream->metronom->pts_per_smpls;
#endif
   return ret;
}

static void
scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream )
{
   lprintf("scope_port_close()\n");

   post_audio_port_t *port = (post_audio_port_t *)port_gen;
   scope_plugin_t *self = (scope_plugin_t *)port->post;

   /* ensure the buffers are deleted during the next VideoWindow::timerEvent */
   MyNode *node;
   for (node = self->list->next; node != self->list; node = node->next)
   {
      node->vpts = node->vpts_end - 1;
   }

   port->stream = NULL;
   port->original_port->close( port->original_port, stream );

   _x_post_dec_usage( port );
}

static void
scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream )
{
   post_audio_port_t *port = (post_audio_port_t *)port_gen;
   scope_plugin_t *self = (scope_plugin_t *)port->post;

   /* we are too simple to handle 8bit */
   /* what does it mean when stream == NULL? */
   if( port->bits == 8 ) {
      port->original_port->put_buffer( port->original_port, buf, stream ); return; }

   MyNode *new_node;
   const int num_samples = buf->num_frames * self->channels;

   new_node             = malloc( sizeof(MyNode) );
#ifdef METRONOM_VPTS
   new_node->vpts       = stream->metronom->get_option(stream->metronom, METRONOM_VPTS);
#else
   new_node->vpts       = stream->metronom->got_audio_samples( stream->metronom, 0, 0 );
#endif
   new_node->num_frames = buf->num_frames;
   new_node->mem        = malloc( num_samples * 2 );
   memcpy( new_node->mem, buf->mem, num_samples * 2 );

   {
      int64_t
      K  = self->pts_per_smpls; /*smpls = 1<<16 samples*/
      K *= num_samples;
      K /= (1<<16);
      K += new_node->vpts;

      new_node->vpts_end = K;
   }

   /* pass data to original port */
   port->original_port->put_buffer( port->original_port, buf, stream );

   /* finally we should append the current buffer to the list
   * NOTE this is thread-safe due to the way we handle the list in the GUI thread */
   new_node->next     = self->list->next;
   self->list->next   = new_node;
}

static void
scope_dispose( post_plugin_t *this )
{
   MyNode *list = ((scope_plugin_t *)this)->list;
   MyNode *prev;
   MyNode *node = list;

   /* Free all elements of the list (a ring buffer) */
   do
   {
      prev = node->next;

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

      node = prev;
   } while(node != list);

   free(this);
}


/************************
* plugin init function *
************************/

#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)
static post_plugin_t* scope_plugin_new( post_class_t *class_gen, int inputs, xine_audio_port_t *audio_target[], xine_video_port_t *video_target[] )
#else
xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target )
#endif
{
   if( audio_target == NULL )
      return NULL;

   scope_plugin_t *scope_plugin = calloc(1, sizeof(scope_plugin_t));
   post_plugin_t *post_plugin = (post_plugin_t *)scope_plugin;

   {
      post_in_t         *input;
      post_out_t        *output;
      post_audio_port_t *port;

      _x_post_init(post_plugin, 1, 0);

#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)
      port = _x_post_intercept_audio_port( post_plugin, audio_target[0], &input, &output );
#else
      port = _x_post_intercept_audio_port( post_plugin, audio_target, &input, &output );
#endif
      port->new_port.open       = scope_port_open;
      port->new_port.close      = scope_port_close;
      port->new_port.put_buffer = scope_port_put_buffer;

      post_plugin->xine_post.audio_input[0] = &port->new_port;
      post_plugin->xine_post.type = PLUGIN_POST;

      post_plugin->dispose = scope_dispose;
   }

#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)
   /* code is straight from xine_init_post()
      can't use that function as it only dlopens the plugins
      and our plugin is statically linked in */
   post_plugin->running_ticket = xine->port_ticket;
   post_plugin->xine = xine;
#endif

   /* scope_plugin_t init */
   scope_plugin->channels = 0;
   scope_plugin->pts_per_smpls = 0;
   scope_plugin->list = calloc(1, sizeof(MyNode));
   scope_plugin->list->next = scope_plugin->list;

#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)
   return post_plugin;
#else
   return &post_plugin->xine_post;
#endif
}

#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)
static void *scope_init_plugin(xine_t *xine, const void *data)
{
  static const post_class_t post_scope_class = {
    .open_plugin     = scope_plugin_new,
    .identifier      = "codeine-scope",
    .description     = "Codeine Scope",
    .dispose         = NULL,
  };

  (void)xine;
  (void)data;

  return (void*)&post_scope_class;
}

static const post_info_t scope_special_info = {
  .type = XINE_POST_TYPE_AUDIO_VISUALIZATION,
};

const plugin_info_t scope_plugin_info[] = {
  { PLUGIN_POST, 10, "codeine-scope", XINE_VERSION_CODE, &scope_special_info, scope_init_plugin },
  { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
};
#endif
