/*
 * Copyright (C) 2008 the xine project
 * Copyright (C) 2008 Christophe Thommeret <hftom@free.fr>
 *
 * 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
 */

/*
 * demultiplexer for wmv9/vc1 elementary streams
 *
 *
 *    SMP (.rcv) format:
 *
 *    ** header ***
 *    le24 number of frames
 *    C5 04 00 00 00
 *    4 bytes sequence header
 *    le32 height
 *    le32 width
 *    0C 00 00 00
 *    8 bytes unknown
 *    le32 fps
 *    ************
 *    le24 frame_size
 *    80
 *    le32 pts (ms)
 *    frame_size bytes of picture data
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

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

#include "xine_internal.h"
#include "xineutils.h"
#include "compat.h"
#include "bswap.h"
#include "demux.h"

#define SCRATCH_SIZE 36
#define PRIVATE_SIZE 44

#define MODE_SMP 1
#define MODE_AP  2



typedef struct {
  demux_plugin_t      demux_plugin;

  xine_stream_t       *stream;
  fifo_buffer_t       *video_fifo;
  fifo_buffer_t       *audio_fifo;
  input_plugin_t      *input;
  int                  status;
  int                  mode;
  int                  first_chunk;
  uint8_t              private[PRIVATE_SIZE];
  uint32_t             video_step;

  uint32_t             blocksize;
} demux_vc1_es_t ;



typedef struct {
  demux_class_t     demux_class;
} demux_vc1_es_class_t;



static int demux_vc1_es_next_smp( demux_vc1_es_t *this )
{
  buf_element_t *buf;
  uint32_t pts=0, frame_size=0;
  off_t done;
  uint8_t head[SCRATCH_SIZE];
  int start_flag = 1;

  if ( this->first_chunk ) {
    this->input->read( this->input, head, SCRATCH_SIZE );
    this->first_chunk = 0;
  }

  done = this->input->read( this->input, head, 8 );
  frame_size = _X_LE_24( head );
  pts = _X_LE_32( head+4 );

  done = 0;
  while ( frame_size>0 ) {
    buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
    off_t read = (frame_size>buf->max_size) ? buf->max_size : frame_size;
    done = this->input->read( this->input, buf->mem, read );
    if ( done<=0 ) {
      buf->free_buffer( buf );
      this->status = DEMUX_FINISHED;
      return 0;
    }
    buf->size = done;
    buf->content = buf->mem;
    buf->type = BUF_VIDEO_WMV9;
    buf->pts = pts*90;
    frame_size -= done;
    if ( start_flag ) {
      buf->decoder_flags = BUF_FLAG_FRAME_START;
      start_flag = 0;
    }
    if ( !(frame_size>0) )
      buf->decoder_flags = BUF_FLAG_FRAME_END;
    this->video_fifo->put(this->video_fifo, buf);
  }

  return 1;
}



static int demux_vc1_es_next_ap( demux_vc1_es_t *this )
{
  buf_element_t *buf;
  uint32_t blocksize;
  off_t done;

  buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
  blocksize = (this->blocksize ? this->blocksize : buf->max_size);
  done = this->input->read(this->input, buf->mem, blocksize);

  if (done <= 0) {
    buf->free_buffer (buf);
    this->status = DEMUX_FINISHED;
    return 0;
  }

  buf->size = done;
  buf->content = buf->mem;
  buf->pts = 0;
  buf->type = BUF_VIDEO_VC1;

  if( this->input->get_length (this->input) )
    buf->extra_info->input_normpos = (int)( (double)this->input->get_current_pos( this->input )*65535/this->input->get_length( this->input ) );

  this->video_fifo->put(this->video_fifo, buf);

  return 1;
}



static int demux_vc1_es_send_chunk( demux_plugin_t *this_gen )
{
  demux_vc1_es_t *this = (demux_vc1_es_t *) this_gen;

  if ( this->mode==MODE_SMP ) {
    if (!demux_vc1_es_next_smp(this))
      this->status = DEMUX_FINISHED;
    return this->status;
  }

  if (!demux_vc1_es_next_ap(this))
    this->status = DEMUX_FINISHED;
  return this->status;
}



static int demux_vc1_es_get_status( demux_plugin_t *this_gen )
{
  demux_vc1_es_t *this = (demux_vc1_es_t *) this_gen;

  return this->status;
}



static void demux_vc1_es_send_headers( demux_plugin_t *this_gen )
{
  demux_vc1_es_t *this = (demux_vc1_es_t *) this_gen;

  this->video_fifo  = this->stream->video_fifo;
  this->audio_fifo  = this->stream->audio_fifo;
  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 1);
  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 0);
  _x_demux_control_start(this->stream);
  this->blocksize = this->input->get_blocksize(this->input);
  this->status = DEMUX_OK;

  if ( this->mode==MODE_SMP ) {
    buf_element_t *buf;
    buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
    xine_fast_memcpy( buf->mem, this->private, PRIVATE_SIZE );
    buf->size = PRIVATE_SIZE;
    buf->content = buf->mem;
    buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAME_END;
    if ( this->video_step ) {
      buf->decoder_flags |= BUF_FLAG_FRAMERATE;
      buf->decoder_info[0] = 90000/this->video_step;
    }
    buf->type = BUF_VIDEO_WMV9;
    this->video_fifo->put(this->video_fifo, buf);
  }
}



static int demux_vc1_es_seek( demux_plugin_t *this_gen, off_t start_pos, int start_time, int playing )
{
  demux_vc1_es_t *this = (demux_vc1_es_t *) this_gen;

  if ( this->mode==MODE_SMP ) {
    this->status = DEMUX_OK;
    return this->status;
  }

  start_pos = (off_t) ( (double) start_pos / 65535 *
              this->input->get_length (this->input) );

  this->status = DEMUX_OK;

  if (playing)
    _x_demux_flush_engine(this->stream);

  if (INPUT_IS_SEEKABLE(this->input)) {

    /* FIXME: implement time seek */

    if (start_pos != this->input->seek (this->input, start_pos, SEEK_SET)) {
      this->status = DEMUX_FINISHED;
      return this->status;
    }
    lprintf ("seeking to %"PRId64"\n", start_pos);
  }

  /*
   * now start demuxing
   */
  this->status = DEMUX_OK;

  return this->status;
}



static void demux_vc1_es_dispose( demux_plugin_t *this )
{
  free (this);
}



static int demux_vc1_es_get_stream_length( demux_plugin_t *this_gen )
{
  return 0 ; /*FIXME: implement */
}



static uint32_t demux_vc1_es_get_capabilities( demux_plugin_t *this_gen )
{
  return DEMUX_CAP_NOCAP;
}



static int demux_vc1_es_get_optional_data( demux_plugin_t *this_gen, void *data, int data_type )
{
  return DEMUX_OPTIONAL_UNSUPPORTED;
}



static demux_plugin_t *open_plugin( demux_class_t *class_gen, xine_stream_t *stream, input_plugin_t *input )
{

  demux_vc1_es_t *this;
  uint8_t scratch[SCRATCH_SIZE];
  int i, read, found=0;

  switch (stream->content_detection_method) {

  case METHOD_BY_CONTENT: {
    read = _x_demux_read_header(input, scratch, SCRATCH_SIZE);
    if (!read)
      return NULL;
    lprintf("read size =%d\n",read);

    /* simple and main profiles */
    if ( read>=SCRATCH_SIZE ) {
      lprintf("searching for rcv format..\n");
      if ( scratch[3]==0xc5 && scratch[4]==4 && scratch[5]==0 && scratch[6]==0 && scratch[7]==0 && scratch[20]==0x0c && scratch[21]==0 && scratch[22]==0 && scratch[23]==0 ) {
        lprintf("rcv format found\n");
        found = MODE_SMP;
      }
    }

    if ( found==0 ) {
      /* advanced profile */
      for (i = 0; i < read-4; i++) {
        lprintf ("%02x %02x %02x %02x\n", scratch[i], scratch[i+1], scratch[i+2], scratch[i+3]);
        if ((scratch[i] == 0x00) && (scratch[i+1] == 0x00) && (scratch[i+2] == 0x01)) {
          if (scratch[i+3] == 0x0f) {
            found = MODE_AP;
            lprintf ("found header at offset 0x%x\n", i);
            break;
          }
        }
      }
    }

    if (found == 0)
      return NULL;
    lprintf ("input accepted.\n");
  }
  break;

  case METHOD_BY_EXTENSION: {
    const char *extensions, *mrl;

    mrl = input->get_mrl (input);
    extensions = class_gen->get_extensions (class_gen);

    if (!_x_demux_check_extension (mrl, extensions))
      return NULL;
  }
  break;

  case METHOD_EXPLICIT:
  break;

  default:
    return NULL;
  }

  this = calloc(1, sizeof(demux_vc1_es_t));
  this->mode = found;
  this->first_chunk = 1;
  if ( found==MODE_SMP ) {
    xine_fast_memcpy( this->private+8, scratch+12, 4 ); /* height */
    xine_fast_memcpy( this->private+4, scratch+16, 4 ); /* width */
    xine_fast_memcpy( this->private+40, scratch+8, 4 ); /* sequence header */
    this->video_step = _X_LE_32( scratch+32 );
  }
  this->stream = stream;
  this->input  = input;

  this->demux_plugin.send_headers      = demux_vc1_es_send_headers;
  this->demux_plugin.send_chunk        = demux_vc1_es_send_chunk;
  this->demux_plugin.seek              = demux_vc1_es_seek;
  this->demux_plugin.dispose           = demux_vc1_es_dispose;
  this->demux_plugin.get_status        = demux_vc1_es_get_status;
  this->demux_plugin.get_stream_length = demux_vc1_es_get_stream_length;
  this->demux_plugin.get_capabilities  = demux_vc1_es_get_capabilities;
  this->demux_plugin.get_optional_data = demux_vc1_es_get_optional_data;
  this->demux_plugin.demux_class       = class_gen;

  this->status = DEMUX_FINISHED;

  return &this->demux_plugin;
}



static const char *get_description( demux_class_t *this_gen )
{
  return "VC1 elementary stream demux plugin";
}



static const char *get_identifier( demux_class_t *this_gen )
{
  return "VC1_ES";
}



static const char *get_extensions( demux_class_t *this_gen )
{
  return "";
}



static const char *get_mimetypes( demux_class_t *this_gen )
{
  return NULL;
}



static void class_dispose( demux_class_t *this_gen )
{
  demux_vc1_es_class_t *this = (demux_vc1_es_class_t *) this_gen;

  free (this);
}



static void *init_plugin( xine_t *xine, void *data )
{
  demux_vc1_es_class_t     *this;

  this = calloc(1, sizeof(demux_vc1_es_class_t));

  this->demux_class.open_plugin     = open_plugin;
  this->demux_class.get_description = get_description;
  this->demux_class.get_identifier  = get_identifier;
  this->demux_class.get_mimetypes   = get_mimetypes;
  this->demux_class.get_extensions  = get_extensions;
  this->demux_class.dispose         = class_dispose;

  return this;
}


/*
 * exported plugin catalog entry
 */
static const demuxer_info_t demux_info_vc1es = {
  0                       /* priority */
};



const plugin_info_t xine_plugin_info[] EXPORTED = {
  /* type, API, "name", version, special_info, init_function */
  { PLUGIN_DEMUX, 26, "vc1es", XINE_VERSION_CODE, &demux_info_vc1es, init_plugin },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
