/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            libmplex_wrapper.cc
 *
 *  Sun Oct 30 12:28:47 CET 2005
 *  Copyright  2005 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of MIaV.
 *
 *  MIaV 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.
 *
 *  MIaV 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 MIaV; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "config.h"
#include "libmplex_wrapper.h"
#include "miav_config.h"

#ifdef WITH_LIBMPLEX

#include <mjpeg_types.h>
#include <mjpeg_logging.h>
#include <mpegconsts.h>

#include <mplex/interact.hpp>
#include <mplex/bits.hpp>
#include <mplex/outputstrm.hpp>
#include <mplex/multiplexor.hpp>

/**
 * FrameOutputStream - Wraps the File object
 */
class FrameOutputStream : public OutputStream
{
public:
    FrameOutputStream( Info *info, File *outputfile );
    int  Open( );
    void Close();
    off_t SegmentSize( );
    void NextSegment();
    void Write(uint8_t *data, unsigned int len);

private:
  Info *info;
  off_t written;
  File *file;
};



FrameOutputStream::FrameOutputStream( Info *info, File *outputfile ) 
{
  this->info = info;
  file = outputfile;
  written = 0;
  info->info("FrameOutputStream - constructor");
}
      
int FrameOutputStream::Open()
{
  //  info->info("FrameOutputStream::Open");
  // Nothing to do here!
	return 0;
}

void FrameOutputStream::Close()
{ 
  //  info->info("FrameOutputStream::Close");
  // Nothing to do here!
}


off_t FrameOutputStream::SegmentSize()
{
  //  info->info("FrameOutputStream::SegmentSize - return: %d", written);
  return written;

  /*
	struct stat stb;
  fstat(fileno(strm), &stb);
	off_t written = stb.st_size;
  return written;
  */
}

void FrameOutputStream::NextSegment( )
{
  //  info->info("FrameOutputStream::NextSegment");
  // Nothing to do here!
  /*
  auto_ptr<char> prev_filename_buf( new char[strlen(cur_filename)+1] );
  char *prev_filename = prev_filename_buf.get();
	fclose(strm);
	++segment_num;
  strcpy( prev_filename, cur_filename );
	snprintf( cur_filename, MAXPATHLEN, filename_pat, segment_num );
	if( strcmp( prev_filename, cur_filename ) == 0 ) {
    mjpeg_error_exit1("Need to split output but there appears to be no %%d in the filename pattern %s", 
                      filename_pat );
	}
	strm = fopen( cur_filename, "wb" );
	if( strm == NULL ) {
		mjpeg_error_exit1( "Could not open for writing: %s", cur_filename );
	}
  */
}

void FrameOutputStream::Write( uint8_t *buf, unsigned int len )
{
  unsigned int write;
  write = file->Write(buf, len);
  written += write;
  //  info->info("FrameOutputStream::Write - len: %d", len);
}

/**
 * FrameInputStream - Wraps the ThreadSafeQueuePriority objects, containing 
 * the prosessed frames from libfame and liblame.
 */
class FrameInputStream : public IBitStream
{
public:
 	FrameInputStream(  Info *info, ThreadSafeQueuePriority *queue );
	~FrameInputStream();

private:
  Frame *getFrame();
  size_t ReadStreamBytes( uint8_t *buf, size_t size );
	bool EndOfStream();

  Info *info;
  ThreadSafeQueuePriority *queue;
  bool seen_eof;
  Frame *frame;
  unsigned int read;
};

FrameInputStream::FrameInputStream( Info *info, ThreadSafeQueuePriority *queue ) :
    IBitStream()
{
  this->info = info;
  this->queue = queue;
  seen_eof = false;
  frame = NULL;
  read = 0;
  streamname = "MIaV Stream\0";
  
  //  info->info("FrameInputStream - constructor", seen_eof);

  /*
	if ((fileh = fopen(bs_filename, "rb")) == NULL)
	{
		mjpeg_error_exit1( "Unable to open file %s for reading.", bs_filename);
	}
	filename = strcpy( new char[strlen(bs_filename)+1], bs_filename );
    streamname = filename;

    SetBufSize(buf_size);
	eobs = false;
    byteidx = 0;
	if (!ReadIntoBuffer())
	{
		if (buffered==0)
		{
			mjpeg_error_exit1( "Unable to read from %s.", bs_filename);
		}
	}
  */
  SetBufSize(BUFFER_SIZE);
  // SetBufSize(buf_size);
	eobs = false;
  byteidx = 0;
	if (!ReadIntoBuffer()) {
		if (buffered==0) {
      info->error( "Unable to read from %s.", streamname);
		}
	}

  //  info->info("FrameInputStream - leaving constructor", seen_eof);
}


/**
   Destructor: close the device containing the bit stream after a read
   process
*/
FrameInputStream::~FrameInputStream()
{
  //  info->info("FrameInputStream - destructor", seen_eof);
  // Nothing to do here!
  /*
	if (fileh)
	{
		fclose(fileh);
		delete filename;
	}
	fileh = 0;
  */
  Release(); // Hmmm.. wonder what this 'Release()' does!?
}

Frame *FrameInputStream::getFrame()
{
  read = 0;
  return queue->pop();
}

bool FrameInputStream::EndOfStream()
{ 
  //  info->info("FrameInputStream::EndOfStream - return: %d", seen_eof);
  return seen_eof;
}

size_t FrameInputStream::ReadStreamBytes( uint8_t *buf, size_t size ) 
{
  //  info->info("FrameInputStream::ReadStreamBytes - size: %d", size);
  unsigned int copied = 0;
  
  while( copied < size ) {
    
    // If we read the entire frame, prepare to get a new one
    if(frame && read == frame->size) {
      delete frame;
      frame = NULL;
    }
    
    // If no frame is in the buffer, get one from the queue
    if(frame == NULL) frame = getFrame();
    
    // check for end of stream
    if( frame->endOfFrameStream == true) {
      seen_eof = true;
      return copied;
    }
    
    // If a frame exists in the buffer copy it to the output buffer
    // (No frame ocurres when *running is set to false)
    if( frame ) {
      unsigned int doread = (size - copied) < (frame->size - read) ?
        size - copied : (frame->size - read);
      
      //info->info("Requested: %d. Read: %d. Doread: %d. In buffer %d", size, (*read), doread, (*frame)->size);
      
      memcpy(buf + copied, frame->data + read, doread);
      read += doread;
      copied += doread;
    }
    }
  
  return copied;
}

/*******************************
 *
 * Command line job class - sets up a Multiplex Job based on command
 * line and File I/O...
 *
 ******************************/

class MIaVMultiplexJob : public MultiplexJob
{
public:
	MIaVMultiplexJob(Info *info, 
                   ThreadSafeQueuePriority *video_queue,
                   ThreadSafeQueuePriority *audio_queue);

private:
	bool ParseVideoOpt( const char *optarg );
	bool ParseLpcmOpt( const char *optarg );
};

MIaVMultiplexJob::MIaVMultiplexJob(Info *info, 
                                   ThreadSafeQueuePriority *video_queue,
                                   ThreadSafeQueuePriority *audio_queue) :
	MultiplexJob()
{
  //  this->info = info;
  //  info->info("MIaVMultiplexJob - constructor");
  outfile_pattern = "/tmp/aaargh.mpg"; // Output file... or something

  verbose = 0; // Level of verbosity. 0 = quiet, 1 = normal 2 = verbose/debug

  VBR = config->readInt("video_bitrate") == 0; // Multiplex variable bit-rate video

  always_system_headers = true; //  Create System header in every pack in generic formats

  // Specifies decoder buffers size in kB.  [ 20...2000]
  if( ! ParseVideoOpt( "500" ) )
    info->error( "Illegal video decoder buffer size(s): %s", "500" );

  // --lpcm-params | -L samppersec:chan:bits [, samppersec:chan:bits]
  //  if( ! ParseLpcmOpt( "48000:2:16" ) ) info->error( "Illegal LPCM option(s): %s", "48000:2:16" );

  data_rate = 0; //Specify data rate of output stream in kbit/sec (default 0=Compute from source streams)
  // Convert from kbit/sec (user spec) to 50B/sec units...
  data_rate = (( data_rate * 1000 / 8 + 49) / 50 ) * 50;

  audio_offset = 0; //Specify offset of timestamps (video-audio) in mSec
  video_offset = 0; //Specify offset of timestamps (video-audio) in mSec
          
  max_PTS = 0; // Multiplex only num seconds of material (default 0=multiplex all)
  
  packets_per_pack = 5; //Number of packets per pack generic formats [1..100]
  
  mux_format = 3; // Set defaults for particular MPEG profiles:
  // 0 = Generic MPEG1
  // 1 = VCD
  // 2 = user-rate VCD
  // 3 = Generic MPEG2
  // 4 = SVCD
  // 5 = user-rate SVCD
  // 6 = VCD Stills
  // 7 = SVCD Stills
  // 8 = DVD with NAV sectors
  // 9 = DVD

  sector_size = 2042; // Specify sector size in bytes for generic formats [256..16384]
  
  //max_segment_size = 0; // Maximum size of output file(s) in Mbyte (default: 0) (no limit)
  
  multifile_segment = true; // Don't switch to a new output file if a sequence end marker
  // is encountered ithe input video

  (void)mjpeg_default_handler_verbosity(verbose);
  info->info( "mplex version %s (%s %s)", VERSION,MPLEX_VER, MPLEX_DATE );
  
  // Connect streams
	vector<IBitStream *> inputs;
  if(video_queue) inputs.push_back( new FrameInputStream( info, video_queue ) );
  if(audio_queue) inputs.push_back( new FrameInputStream( info, audio_queue ) );
	SetupInputStreams( inputs );
}

/*************************************************************************
 Usage banner for the command line wrapper.
*************************************************************************/
/*
	mjpegtools mplex-2 version  VERSION  ( MPLEX_VER )
	Usage: %s [params] -o <output filename pattern> <input file>... 
	         %%d in the output file name is by segment count
	  where possible params are:
	--verbose|-v num
      Level of verbosity. 0 = quiet, 1 = normal 2 = verbose/debug
	--format|-f fmt
      Set defaults for particular MPEG profiles
	  [0 = Generic MPEG1, 1 = VCD, 2 = user-rate VCD, 3 = Generic MPEG2,
       4 = SVCD, 5 = user-rate SVCD
	   6 = VCD Stills, 7 = SVCD Stills, 8 = DVD with NAV sectors, 9 = DVD]
  --mux-bitrate|-r num
      Specify data rate of output stream in kbit/sec
	    (default 0=Compute from source streams)
	--video-buffer|-b num [, num...] 
      Specifies decoder buffers size in kB.  [ 20...2000]
  --lpcm-params | -L samppersec:chan:bits [, samppersec:chan:bits]
	--mux-limit|-l num
      Multiplex only num seconds of material (default 0=multiplex all)
	--sync-offset|-O num ms|s|mpt
      Specify offset of timestamps (video-audio) in mSec
	--sector-size|-s num
      Specify sector size in bytes for generic formats [256..16384]
  --vbr|-V
      Multiplex variable bit-rate video
	--packets-per-pack|-p num
      Number of packets per pack generic formats [1..100]
	--system-headers|-h
      Create System header in every pack in generic formats
	--max-segment-size|-S size
      Maximum size of output file(s) in Mbyte (default: 0) (no limit)
	--ignore-seqend-markers|-M
      Don't switch to a new output file if a  sequence end marker
	  is encountered ithe input video.
  --workaround|-W workaround [, workaround ]
	--help|-?
      Print this lot out!
*/


bool MIaVMultiplexJob::ParseLpcmOpt( const char *optarg )
{
  char *endptr, *startptr;
  unsigned int samples_sec;
  unsigned int channels;
  unsigned int bits_sample;
  endptr = const_cast<char *>(optarg);
  do {
    startptr = endptr;
    samples_sec = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
    if( startptr == endptr || *endptr != ':' )
      return false;
    
    startptr = endptr+1;
    channels = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
    if(startptr == endptr || *endptr != ':' )
      return false;
    
    startptr = endptr+1;
    bits_sample = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
    if( startptr == endptr )
      return false;
      
    LpcmParams *params = LpcmParams::Checked( samples_sec,
                                              channels,
                                              bits_sample );
    if( params == 0 )
      return false;
    lpcm_param.push_back(params);
    if( *endptr == ',' )
      ++endptr;
  } while( *endptr != '\0' );
  return true;
}

bool MIaVMultiplexJob::ParseVideoOpt( const char *optarg )
{
    char *endptr, *startptr;
    unsigned int buffer_size;
    endptr = const_cast<char *>(optarg);
    do 
    {
        startptr = endptr;
        buffer_size = static_cast<unsigned int>(strtol(startptr, &endptr, 10));
        if( startptr == endptr )
            return false;

        VideoParams *params = VideoParams::Checked( buffer_size );
        if( params == 0 )
            return false;
        video_param.push_back(params);
        if( *endptr == ',' )
            ++endptr;
    } 
    while( *endptr != '\0' );
    return true;
}

LibMPlexWrapper::LibMPlexWrapper(Info *info,
                                 File *outputfile,
                                 ThreadSafeQueuePriority *video_queue,
                                 ThreadSafeQueuePriority *audio_queue)
{
  this->info = info;
  this->outputfile = outputfile;
  this->video_queue = video_queue;
  this->audio_queue = audio_queue;
}

LibMPlexWrapper::~LibMPlexWrapper()
{
}


void LibMPlexWrapper::multiplex()
{
  //  info->info("MPLEX!");
  //  sleep(10);
  //  info->info("The road goes ever on and on...");
	MIaVMultiplexJob job(info, video_queue, audio_queue);
	FrameOutputStream output( info, outputfile );
	Multiplexor mux(job, output);
	mux.Multiplex();
}


#ifdef LIBMPLEX_WRAPPER_TEST
int main (int argc, char* argv[])
{
  LibMPlexWrapper mplex;
  mplex.multiplex();
  return 0;	
}
#endif/*LIBMPLEX_WRAPPER_TEST*/
			
#endif/*WITH_LIBMPLEX*/