/***************************************************************************//**
 * @file	asyncwriter.c
 * @date	2010/02/03 21:29:27
 * @author	jef-Linux
 * @brief
 *	Brief description
 * @details
 * (c) ALPHA SOFT\n
 * encoding	UTF8 !-UTF8-!
 * Long description
*******************************************************************************/
#define __USE_LARGEFILE64
#define _LARGEFILE64_SOURCE
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#define __USE_GNU
#include <pthread.h>
#include <semaphore.h>

#include "globals.h"
#include "const.h"
#include "dvdtools.h"
#include "ac.h"

// #define BUFFER_SIZE	DVD_VIDEO_LB_LEN
#define BUFFER_SIZE	(1024 * 64 )

typedef struct BUFFER_T {
	unsigned char data[BUFFER_SIZE];
	int size;
	struct BUFFER_T * next;
} Buffer_t;

typedef struct {
	long long bytesWrited;
	long long bytesFlushed;
	int errorCondition;
	FILE * outFp;
	pthread_t thread;

	sem_t 	bSem;
	pthread_mutex_t bMutex;
	Buffer_t * bList;

	sem_t 	fSem;
	pthread_mutex_t fMutex;
	Buffer_t * fList;
} Stream_t;

static Stream_t Stream;

/***************************************************************************//**
 *	WRITEBUFFER-
 * @date	2010/02/03 21:43:41
 * @author	jef-Linux
 * @brief
 *	Pas d'explication
 * @details
 *	Pas de details
 * @param[in]	Pas de detail
 * @param[out]	Pas de detail
*******************************************************************************/
static int WriteBuffer( FILE * fp, void * buffer, int size )
{
// DBG( 'f',fprintf(stderr,"%s(%d)\n", __FUNCTION__, size ););
	while( size > 0 ) {
		int lg = fwrite( buffer, 1, size, fp );
// DBG( 'f',fprintf(stderr,"%s(%d)=%d errno=%d\n", __FUNCTION__, size, lg, errno ););
		if( lg == 0 && errno == EPIPE )	return( -1 );
		if( lg < 0 )	return( -1 );
		buffer += lg;
		size -= lg;
	}
	return( 0 );
}
/***************************************************************************//**
 *	FLUSHERTHREAD-
 * @date	2010/02/03 21:34:06
 * @author	jef-Linux
 * @brief
 *	Pas d'explication
 * @details
 *	Pas de details
 * @param[in]	Pas de detail
 * @param[out]	Pas de detail
*******************************************************************************/
static void * FlusherThread( void * arg )
{
	Stream_t * stream =(Stream_t *)arg;

	DBG( 'f',fprintf(stderr,"%s: start stream %p thread: %p\n", __FUNCTION__, stream, (void *)stream->thread ););
	while( 1 ) {
		sem_wait( &stream->bSem );
		pthread_testcancel();
		if( stream->bList ) {
			Buffer_t * b;
			int eof = 0;

			pthread_mutex_lock( &stream->bMutex );
			b = stream->bList;
			stream->bList = b->next;
			pthread_mutex_unlock( &stream->bMutex );
			if( b->size != -1 ) {
				if( WriteBuffer( stream->outFp, b->data, b->size ) < 0 ) {
					DBG( 'f',fprintf(stderr,"%s: error set\n", __FUNCTION__ ););
					stream->errorCondition = 1;
				}
				Stream.bytesFlushed += b->size;
			}
			else
				eof = 1;
			pthread_mutex_lock( &stream->fMutex );
			b->next = stream->fList;
			stream->fList = b;
			pthread_mutex_unlock( &stream->fMutex );
			sem_post( &stream->fSem );
			if( eof )	break;
		}
	}
	DBG( 'f',fprintf(stderr,"%s: EOF\n", __FUNCTION__ ););
	pthread_exit( NULL );
	return( NULL );
}

/***************************************************************************//**
 *	FLUSHERSTART-
 * @date	2010/02/03 21:51:55
 * @author	jef-Linux
 * @brief
 *	Pas d'explication
 * @details
 *	Pas de details
 * @param[in]	Pas de detail
 * @param[out]	Pas de detail
*******************************************************************************/
int FlusherStart( FILE * fp, int totalBufferSize )
{
	pthread_mutexattr_t attr;
	int nbBuffer;
	int i;

	nbBuffer = ( totalBufferSize * 1024 * 1024 ) / BUFFER_SIZE;

	memset( &Stream, 0, sizeof(Stream));

	Stream.outFp = fp;
	sem_init( &Stream.bSem, 0, 0 );
	pthread_mutexattr_init( &attr );
	pthread_mutex_init( &Stream.bMutex, &attr );
	for( i = 0; i < nbBuffer; i++ ) {
		Buffer_t * b = (Buffer_t *)malloc(sizeof(*b));

		b->next = Stream.fList;
		Stream.fList = b;
	}
	sem_init( &Stream.fSem, 0, nbBuffer );
	pthread_mutexattr_init( &attr );
	pthread_mutex_init( &Stream.fMutex, &attr );

	pthread_create( &Stream.thread, NULL, FlusherThread, (void *)&Stream );
	return( 0 );
}

/***************************************************************************//**
 *	FLUSHERWRITE-
 * @date	2010/02/03 21:52:27
 * @author	jef-Linux
 * @brief
 *	Pas d'explication
 * @details
 *	Pas de details
 * @param[in]	Pas de detail
 * @param[out]	Pas de detail
*******************************************************************************/
int FlusherWrite( unsigned char * buffer, int size )
{
	DBG( 'f',fprintf(stderr,"%s(%d)\n", __FUNCTION__, size ););
	Stream.bytesWrited += size;
	while( size > 0 ) {
		Buffer_t * b;

		if( Stream.errorCondition )	return( -1 );

		sem_wait( &Stream.fSem );

		pthread_mutex_lock( &Stream.fMutex );
		b = Stream.fList;
		if( b )	Stream.fList = b->next;
		pthread_mutex_unlock( &Stream.fMutex );
		if( b ) {
			int len = MyMin( size, BUFFER_SIZE );

			tc_memcpy( b->data, buffer, len );
			b->size = len;
			b->next = NULL;
			pthread_mutex_lock( &Stream.bMutex );
			if( !Stream.bList ) {
				Stream.bList = b;
			}
			else {
				Buffer_t * l = Stream.bList;

				while( l->next )	l = l->next;
				l->next = b;
			}
			pthread_mutex_unlock( &Stream.bMutex );
			sem_post( &Stream.bSem );
			buffer += len;
			size -= len;
		}
	}
	return( 0 );
}

/***************************************************************************//**
 *	FLUSHERSTOP-
 * @date	2010/02/03 21:52:47
 * @author	jef-Linux
 * @brief
 *	Pas d'explication
 * @details
 *	Pas de details
 * @param[in]	Pas de detail
 * @param[out]	Pas de detail
*******************************************************************************/
int FlusherStop()
{
	Buffer_t * b;

	DBG( 'f',fprintf(stderr,"%s()\n", __FUNCTION__ ););
/* Post last bloc with size = -1 */
	while( 1 ) {
		sem_wait( &Stream.fSem );

		pthread_mutex_lock( &Stream.fMutex );
		b = Stream.fList;
		if( b )	Stream.fList = b->next;
		pthread_mutex_unlock( &Stream.fMutex );
		if( b ) {
			b->size = -1;
			b->next = NULL;
			pthread_mutex_lock( &Stream.bMutex );
			if( !Stream.bList ) {
				Stream.bList = b;
			}
			else {
				Buffer_t * l = Stream.bList;

				while( l->next )	l = l->next;
				l->next = b;
			}
			pthread_mutex_unlock( &Stream.bMutex );
			sem_post( &Stream.bSem );
			break;
		}
	}

	pthread_join( Stream.thread, NULL );
	b = Stream.fList;
	while( b ) {
		Buffer_t * n = b->next;
		free( b );
		b = n;
	}
	if( Stream.errorCondition )	return( -1 );
	DBG( 'f',fprintf(stderr,"%s(): Writed: %lld Flushed: %lld\n", __FUNCTION__, Stream.bytesWrited, Stream.bytesFlushed ););
	return( 0 );
}
