// Mikropuhe so-library cpp-wrapper and test program

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#include "mpwrfile.h"



/////////////////////////////////////////////////////////////////////////////
//  CMPChannel - The wrapper class for one channel

// All functions must be called from only on thread or calling must be synchronized other way
// Speech parameter are maintained by instance - instances are completely isolated.
// Internally libmplinux.so creates voice file cache when first InitEx is called.
// The cache is cleaned when its reference count drops to zero
class CMPChannel
{
// Creating & destroying
public:
	CMPChannel()
	{
		m_pLib = 0;
		m_pChannel = 0;
		m_pfnChannelInitEx = 0;
		m_pfnChannelUseSettings = 0;
		m_pfnChannelExit = 0;
		m_pfnChannelSpeakFile = 0;
	}
		

	~CMPChannel()
	{
		FreeAll();
	}

	// Frees the library, synthesizer MUST NOT be active when this is called.
	// (All running SpeakFile-calls must have been completed).
	void FreeAll();

	/*
	*	Loads given library and obtains all needed symbols from it
	*
	*	pSoFileName	-> Full path name for the libmplinux.so
	*	pErr			-> English error message is saved here in case of failure
	*	nErrLen		-> Length of previous
	*
	*	Return value: 0=Ok and pErr="", others=failed and pErr is set
	*/
	int Load( const char *pSoFileName, char *pErr, int nErrLen );


// Functions, all return 0 if successful and negative if error is from Mikropuhe
// Please see mpwrfile.h for more information
public:
	// You MUST call this before anything else
	int InitEx( const char *pKey, const char *pSettingsStr, void *pReserved=0 )
	{
		if ( !m_pfnChannelInitEx )
			return( -1 );

		return( m_pfnChannelInitEx(&m_pChannel, pKey, pSettingsStr, pReserved) );
	}


	int UseSettings( const char *pSettingsStr )
	{
		if ( !m_pfnChannelUseSettings )
			return( -1 );

		return( m_pfnChannelUseSettings(m_pChannel, pSettingsStr) );
	}


	int Exit(char *pSettingsStr, int nSettingsLen)
	{
		int	nRes;

		if ( !m_pfnChannelExit )
			return( -1 );

		nRes = m_pfnChannelExit(m_pChannel, pSettingsStr, nSettingsLen);
		m_pChannel = 0;

		return( nRes );
	}

	int SpeakFile( const char *pText, const char *pFileName, MPINT_SpeakFileParams *prParams )
	{
		if ( !m_pfnChannelSpeakFile )
			return( -1 );

		return( m_pfnChannelSpeakFile(m_pChannel, pText, pFileName, prParams) );
	}


protected:
	void                          *m_pLib;			// dlopen-handle
	void                          *m_pChannel;	// Channel "handle"


	// Function pointers
	MPINT_ChannelInitExType       m_pfnChannelInitEx;
	MPINT_ChannelUseSettingsType  m_pfnChannelUseSettings;
	MPINT_ChannelExitType         m_pfnChannelExit;
	MPINT_ChannelSpeakFileType    m_pfnChannelSpeakFile;
};



void CMPChannel::FreeAll()
{
	// Uninitialize Mikropuhe
	Exit( NULL, 0 );

	if ( m_pLib ) {
		dlclose( m_pLib );
		m_pLib = 0;
	}

	m_pfnChannelInitEx = 0;
	m_pfnChannelUseSettings = 0;
	m_pfnChannelExit = 0;
	m_pfnChannelSpeakFile = 0;
}


int CMPChannel::Load( const char *pSoFileName, char *pErr, int nErrLen )
{
   char	sNameTemp[4096];		// Because dlopen does not take const char *
	int	nIndex;

	*pErr = 0;
	FreeAll();

	memset( sNameTemp, 0, sizeof(sNameTemp) );

	// Load the so
   strncpy( sNameTemp, pSoFileName, sizeof(sNameTemp)-1 );
	m_pLib = dlopen( sNameTemp, RTLD_NOW );
	if ( !m_pLib ) {
		snprintf( pErr, nErrLen, "Loading library %s failed, error message=%s", sNameTemp, dlerror() );
		return( -1 );
	}

	// Get func addresses
	const char	*pFunc;

	if (	(m_pfnChannelInitEx      = (MPINT_ChannelInitExType)      dlsym(m_pLib, pFunc = "MPINT_ChannelInitEx")) == 0  ||
			(m_pfnChannelUseSettings = (MPINT_ChannelUseSettingsType) dlsym(m_pLib, pFunc = "MPINT_ChannelUseSettings")) == 0  ||
			(m_pfnChannelExit        = (MPINT_ChannelExitType)        dlsym(m_pLib, pFunc = "MPINT_ChannelExit")) == 0  ||
			(m_pfnChannelSpeakFile   = (MPINT_ChannelSpeakFileType)   dlsym(m_pLib, pFunc = "MPINT_ChannelSpeakFile")) == 0 ) {

		snprintf( pErr, nErrLen, "Could not get function %s from library %s, error message=%s", pFunc, pSoFileName, dlerror() );
		return( -2 );
	}

	// All ok
	return( 0 );
}

//  CMPChannel - The wrapper class
/////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////////////
// Testing multithreaded speaking

// Globals to simplify the program
static const char *_pSoFileName;

#define THREAD_COUNT	4


// How many threads are still running
// We can increment and decrement this as an atomic operation on single processor system.
// Actually we should have a mutex here, but not now for simplicity...
static int	_nWaitThreadCount = 0;



struct SThreadParam {
	int	nThreadNumber;			// Number to speak
};

static void *ThreadProc( void *pParam )
{
	SThreadParam	*prParams = (SThreadParam *) pParam;
	CMPChannel		rMP;
	char				sBuf[1024], sSettings[1024];
   const char		*pSpeaker;
	int				nErr;


	/*
	*	Initialize the synthesizer
	*	Note that threads are totally independent - system will perform initialization quickly if
	*	this is not first running thread
	*/

	if ( (nErr = rMP.Load(_pSoFileName, sBuf, sizeof(sBuf))) != 0 ) {
		printf( "ERROR LOAD:\n%s\n", sBuf );
		return( 0 );
	}

	// Different speech settings for each thread
   pSpeaker = "petteri";
   if ( (prParams->nThreadNumber & 1) == 1 )
	   pSpeaker = "saga";

	sprintf( sSettings,"<voice name=\"%s\"><rate absspeed=\"%d\">", pSpeaker, -5 + 3*prParams->nThreadNumber );
	printf( "Hello from thread %d, default voice settings:\n%s\n\n", prParams->nThreadNumber, sSettings );

	if ( (nErr = rMP.InitEx(NULL, sSettings)) != 0 ) {
		printf( "ERROR INIT:\n%d\n", nErr );
		return( 0 );
	}

	/*
	*	Write the file for few times
	*/

	MPINT_SpeakFileParams	rParams;
	char							sFileName[1024];
	int							nIndex, nSleep;


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

	rParams.nTags = MPINT_TAGS_SAPI5;		// SAPI5 tagging
	rParams.nWriteWavHeader = 1;

	// All threads save to different file and speak different text
	sprintf( sFileName,"test%d.wav", prParams->nThreadNumber );
	sprintf( sBuf,"<pitch absmiddle=\"%d\"/>Hei säikeestä %d.", -5 + 3*prParams->nThreadNumber, prParams->nThreadNumber );

	for ( nIndex = 0; nIndex < 10; nIndex++ ) {

		nSleep = 100000 + (random() % 1000000);
		printf( "Thread %d calls SpeakFile\n", prParams->nThreadNumber );
		nErr = rMP.SpeakFile( sBuf, sFileName, &rParams );
		printf( "Thread %d returns from SpeakFile, sleeping for %d us\n", prParams->nThreadNumber, nSleep );

		if ( nErr ) {
			printf( "SpeakFile-call failed [%d]\n", nErr );
			break;
		}

      if ( (nSleep & 3) == 0 ) {
			printf( "Thread %d calls UseSettings - should not affect anyone else\n", prParams->nThreadNumber );
		   if ( (nErr = rMP.UseSettings(sSettings)) != 0 ) {
		      printf( "ERROR INIT:\n%d\n", nErr );
				return( 0 );
			}
      }

		usleep( nSleep );
	}

	printf( "Bye from thread %d - created file %s containing text:\n%s\n\n", prParams->nThreadNumber, sFileName, sBuf );
	_nWaitThreadCount--;
	delete prParams;
   return( 0 );
}


static void ThreadTest( const char *pSoFileName )
{
	int				nIndex;
	pthread_t		hThread;
	SThreadParam	*prParam;


	_pSoFileName = pSoFileName;

	// Create threads
	for ( nIndex = 0; nIndex < THREAD_COUNT; nIndex++ ) {
		prParam = new SThreadParam;
		if ( prParam ) {
			prParam->nThreadNumber = nIndex;

			// Pass paramter from heap - thread will delete it
			if ( pthread_create(&hThread, NULL, ThreadProc, prParam) == 0 )
				_nWaitThreadCount++;
			else
				delete prParam;
		}
	}

	// Wait all to finish
	while ( _nWaitThreadCount )
		usleep( 10000 );

	printf( "All threads stopped\n" );
}

// Testing multithreaded speaking
/////////////////////////////////////////////////////////////////////////////



static char	_sSyntData[] = "own data";
static int	_nSyntResult = 0;

static int SyntWrite(const void * /*pData*/, unsigned uBytes, void *pWriteData, void * /*pReserved*/)
{
	// printf( "SyntWrite %u bytes\n", uBytes );

	if ( strcmp(_sSyntData, (char *) pWriteData) ) {
		printf( "SyntWrite - bad pWriteData\n" );
		return( 123 );		// Will stop synthesizer immediately
	}

	return( _nSyntResult );
}



int main(int argc, char* argv[])
{
	char           sFileName[1024];
	char           sErr[1024], *pPos;
	int            nStatus;

	CMPChannel     rMP;		// One instance for main program


	// libmplinux.so from program's folder
	strcpy( sFileName, argv[0] );
	pPos = strrchr(sFileName, '/');
	if ( pPos )
		pPos++;
	else
		pPos = sFileName;

	strcpy( pPos, "libmplinux.so" );


	if ( rMP.Load(sFileName, sErr, sizeof(sErr)) ) {
		printf( "ERROR:\n%s\n", sErr );
		return( 1 );
	}

	if ( (nStatus = rMP.InitEx(NULL, NULL)) != 0 ) {
		printf( "ERROR:\n%d\n", nStatus );
		return( 1 );
	}

	/*
	*	Speak tagged text to a file using telephony parameters without wav-header
	*/

	MPINT_SpeakFileParams	rParams;

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

	rParams.nTags = MPINT_TAGS_SAPI5;

	rParams.nSampleFreq = 8000;
	rParams.nBits = 16;
	rParams.nChannels = 1;
	rParams.nWriteWavHeader = 0;

	rParams.pfnWrite = SyntWrite;
	rParams.pWriteData = _sSyntData;

	nStatus = rMP.SpeakFile( "<beep freq=\"400\" length=\"1000\" volume=\"100\"/>Hei, mitä kuuluu?", NULL, &rParams );
	if ( nStatus ) {
		printf( "SpeakFile-call failed [%d]\n", nStatus );
		return( 1 );
	}

	// Test - should return same error code
	_nSyntResult = 55;
	nStatus = rMP.SpeakFile( "Hei, mitä kuuluu?", NULL, &rParams );
	if ( nStatus != _nSyntResult ) {
		printf( "SpeakFile-call did not return excpected value [%d]\n", nStatus );
		return( 1 );
	}

	/*
	*	Speak tagged text to a wav-file using default parameters
	*/

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

	rParams.nTags = 0;		// Not any tagging
	rParams.nWriteWavHeader = 1;

	// Let synthesizer use default values for everyting, note that this text will not be spoken tagged
	nStatus = rMP.SpeakFile( "Hei, mitä kuuluu?", "test.wav", &rParams );
	if ( nStatus ) {
		printf( "SpeakFile-call failed [%d]\n", nStatus );
		return( 1 );
	}

	// Multithreading test
	ThreadTest( sFileName, NULL );

   printf( "Everything ok, goodbye.\n" );
	return 0;
}
