
/*************************************************************************
* @mainpage
*  mplex - General-purpose MPEG-1/2 multiplexer.
* (C) 2000, 2001 Andrew Stevens <andrew.stevens@philips.com>
* 
* Doxygen documentation and MPEG Z/Alpha multiplexing part by
* Gernot Ziegler <gz@lysator.liu.se>
*  Constructed using mplex - MPEG-1/SYSTEMS multiplexer as starting point
*  Copyright (C) 1994 1995 Christoph Moar
*  Siemens ZFE ST SN 11 / T SN 6
*
*  This program 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.					 *
*
*  This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*************************************************************************/

/*
 * Modifications to work with streamdvd by Reinhardt Wolf
 */



#include "config.h"
#include <stdio.h>
#include <string.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <string>
#include <memory>
#include <sys/stat.h>
#ifndef _WIN32
#include <sys/param.h>
#endif
#include <ctype.h>
#include <math.h>
#include "cpu_accel.h"
#include "mjpeg_types.h"
#include "mjpeg_logging.h"
#include "mpegconsts.h"

#include "interact.hpp"
#include "bits.hpp"
#include "outputstrm.hpp"
#include "multiplexor.hpp"
#include <streamdvd.h>


using std::auto_ptr;

extern int read_from_buffer ( unsigned char, unsigned char *, int, int );


/*************************************************************************
 Command line wrapper.  Basically, all the command line and file (actually
 pipe and FIFO is what would be more normal) I/O specific sub-classes

 Plus the top-level entry point.  That's all!!

*************************************************************************/


#if	!defined(HAVE_LROUND)
extern "C" {
long
lround(double x)
{
	long l = ceil(x);
	return(l);
}
};
#endif



class FileOutputStream : public OutputStream
{
public:
    FileOutputStream( const char *filename_pat );
    //FileOutputStream();
    virtual int  Open( );
    virtual void Close();
    virtual off_t SegmentSize( );
    virtual void NextSegment();
    virtual void Write(uint8_t *data, unsigned int len);

private:
    FILE *strm;
    char filename_pat[MAXPATHLEN];
    char cur_filename[MAXPATHLEN];

};



FileOutputStream::FileOutputStream( const char *name_pat ) 
//FileOutputStream::FileOutputStream( )
{
	strncpy( filename_pat, name_pat, MAXPATHLEN );
	snprintf( cur_filename, MAXPATHLEN, filename_pat, segment_num );
}
      
int FileOutputStream::Open()
{
	strm = fopen( cur_filename, "wb" );
	if( strm == NULL )
	{
		mjpeg_error_exit1( "Could not open for writing: %s", cur_filename );
	}

	return 0;
}

void FileOutputStream::Close()
{ 
    fclose(strm);
}


off_t
FileOutputStream::SegmentSize()
{
	struct stat stb;
    fstat(fileno(strm), &stb);
	off_t written = stb.st_size;
    return written;

   return (1);
}

void 
FileOutputStream::NextSegment( )
{
    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
FileOutputStream::Write( uint8_t *buf, unsigned int len )
{
//    if( fwrite( buf, 1, len, strm ) != len )
//    {
//        mjpeg_error_exit1( "Failed write: %s", cur_filename );
//    }
   write(STDOUT_FILENO, buf, len);
}



/********************************
 *
 * IFileBitStream - Input bit stream class for bit streams sourced
 * from standard file I/O (this of course *includes* network sockets,
 * fifo's, et al).
 *
 * OLAF: To hook into your PES reader/reconstructor you need to define
 * a class like this one, where 'ReadStreamBytes' calls you code to
 * generate the required number of bytes of ES data and transfer it 
 * to the specified buffer.  The logical way to do this would be to
 * inherit IBitStream as a base class of the top-level classes for the ES
 * reconstructors.
 *
 ********************************/

class IFileBitStream : public IBitStream
{
public:
 	//IFileBitStream( const char *bs_filename, unsigned int buf_size = BUFFER_SIZE);
        IFileBitStream( unsigned char stream_id, unsigned int buf_size = BUFFER_SIZE);
	~IFileBitStream();
        virtual bool GetStreamID() { return (mystream); };

private:
	//FILE *fileh == 0;
	char filename[64];
        int is_eof;
        unsigned char mystream;
	virtual size_t ReadStreamBytes( uint8_t *buf, size_t number ) 
		{
			//return fread(buf,sizeof(uint8_t), number, fileh ); 
                        int was_read = read_from_buffer( mystream, buf, number, 1 );
                        is_eof = number - was_read;
                        return ( was_read );
		}
	//virtual bool EndOfStream() { return feof(fileh) != 0; }
        virtual bool EndOfStream() { return (is_eof != 0); };
	
};



//IFileBitStream::IFileBitStream( const char *bs_filename, unsigned int buf_size) : IBitStream()
IFileBitStream::IFileBitStream( unsigned char stream_id, unsigned int buf_size) : IBitStream()
{
//	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 );
    if( (stream_id >= 0xA0) && (stream_id <= 0xAF) ) {
       sprintf(filename, "%s-0x%x.%s", "unnamed", stream_id, "lpcm"); }
    else if( (stream_id >= 0xC0) && (stream_id <= 0xCF) ) {
       sprintf(filename, "%s-0x%x.%s", "unnamed", stream_id, "mpa"); }
    else if( (stream_id >= 0x80) && (stream_id <= 0x87) ) {
       sprintf(filename, "%s-0x%x.%s", "unnamed", stream_id, "ac3"); }
    else if( (stream_id >= 0x88) && (stream_id <= 0x8F) ) {
       sprintf(filename, "%s-0x%x.%s", "unnamed", stream_id, "dts"); }
    else if( (stream_id >= 0xE0) && (stream_id <= 0xEF) ) {
       sprintf(filename, "%s-0x%x.%s", "unnamed", stream_id, "m2v"); }
    else {
       sprintf(filename, "%s-0x%x", "unnamed", stream_id); }
    streamname = filename;
    is_eof = 0;
    mystream = stream_id;

    SetBufSize(buf_size);
	eobs = false;
    byteidx = 0;
	if (!ReadIntoBuffer())
	{
		if (buffered==0)
		{
			//mjpeg_error_exit1( "Unable to read from %s.", bs_filename);
                        mjpeg_error_exit1( "Unable to read from %x.", stream_id);
		}
	}
}


/**
   Destructor: close the device containing the bit stream after a read
   process
*/
IFileBitStream::~IFileBitStream()
{
//	if (fileh)
//	{
//		fclose(fileh);
//		delete filename;
//	}
//	fileh = 0;
    Release();
}


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

class CmdLineMultiplexJob : public MultiplexJob
{
public:
        CmdLineMultiplexJob( int stream_count, int stream_list[], int av_delay);

private:
        void InputStreamsFromCmdLine ( int stream_count, int stream_list[] );

};

CmdLineMultiplexJob::CmdLineMultiplexJob( int stream_count, int stream_list[], int av_delay ) : 
MultiplexJob()
{
    //outfile_pattern = NULL;
    outfile_pattern = "/tmp/mplextmp";

        verbose = 1;
	mux_format = MPEG_FORMAT_DVD_NAV;
        //video_offset = lround(av_delay*CLOCKS/(1000.0));
        video_offset = av_delay * (90*300);
        audio_offset = 0;

        if( video_offset < 0 )
        {
                audio_offset = - video_offset;
                video_offset = 0;
        }

	//fprintf(stderr, "Coding with V_off: %d, A_off: %d\n", video_offset, audio_offset);

	(void)mjpeg_default_handler_verbosity(verbose);
	mjpeg_info( "mplex version %s (%s %s)",VERSION,MPLEX_VER,MPLEX_DATE );

    InputStreamsFromCmdLine( stream_count, stream_list );
}



void CmdLineMultiplexJob::InputStreamsFromCmdLine(int stream_count, int stream_list[] )
{
	vector<IBitStream *> inputs;
    int i;
	for( i = 0; i < stream_count; ++i ) {
		inputs.push_back( new IFileBitStream( stream_list[i] ) );
	}
	SetupInputStreams( inputs );
}



int run_mplex( int stream_count, int *stream_list, int av_delay ) {
   CmdLineMultiplexJob job( stream_count, stream_list, av_delay );
   FileOutputStream output( job.outfile_pattern );
   //FileOutputStream output();
   Multiplexor mux(job, output);
   mux.Multiplex();

   return(1);
}
