/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * Loquendo TTS Rendering
 * 
 * Copyright (C) 2005, Manuel Guesdon / Oxymium
 *
 * Manuel Guesdon <mguesdon@oxymium.net>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License
 *
 * In Make file: 
 *  1. add app_loquendo.so to APPS=
 *  2. add these 4 lines:
 *      app_loquendo.o: app_loquendo.c
 *	  $(CC) -pipe -I/opt/Loquendo/LTTS/include/ $(CFLAGS) -c -o app_loquendo.o app_loquendo.c 
 *      app_loquendo.so : app_loquendo.o
 *	  $(CC) $(SOLINK) -o $@ $< /opt/Loquendo/LTTS/lib/LoqTTS6.so /opt/Loquendo/LTTS/lib/LoqFrench6.5.so -lcrypto
 */

#include <sys/types.h>
#include <stdio.h>
#include <asterisk/file.h>
#include <asterisk/logger.h>
#include <asterisk/channel.h>
#include <asterisk/pbx.h>
#include <asterisk/options.h>
#include <asterisk/config.h>
#include <asterisk/module.h>
#include <asterisk/utils.h>
#include <asterisk/app.h>
#include <asterisk/cli.h>
#include <asterisk/utils.h>
#include <asterisk/lock.h>
#include <asterisk/md5.h>

#include <asterisk/say.h>
#include <asterisk/adsi.h>
#include <asterisk/manager.h>
#include <asterisk/dsp.h>
#include <asterisk/localtime.h>

#include "asterisk.h"

#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>
#include <loqtts.h>	/* Loquendo TTS include file			*/
#include <openssl/sha.h>

#define LOQUENDOTTS_CONFIG "loquendo-tts.conf"
typedef enum _LoquendoTTSDataType
{
	LoquendoTTSDataType_Text,
	LoquendoTTSDataType_TextFile,
	LoquendoTTSDataType_XML,
	LoquendoTTSDataType_SSML
} LoquendoTTSDataType;

typedef struct _LoquendoTTSContext
{
  char* streamIt;
  char* sampleRate;
  char* coding;
  char* voice;
  char* pitch;
  char* pitchRange;
  char* speed;
  char* speedRange;
  char* volume;
  char* volumeRange;
  char* dataCoding;

  LoquendoTTSDataType dataType;
  char* complete_data_path;
  char* complete_wav_path;
  char* complete_wav_path_nosuffix;
  char* renderData;
  char* loquendoConfFilePath;
  struct ast_channel* channel;  
  const char* escapeDigitsString;
} LoquendoTTSContext;


static char *tdesc = "Loquendo TTS Interface";

static char *app = "LoquendoTTS";

static char *synopsis = "Say text to the user";

static char *descrip = 
"  LoquendoTTS(<type>=<filePath>|voice=<TheVoice>[|EscapeDigits]):  Make Loquendo TTS render filePath content, get back the waveform,"
"play it to the user, allowing any given EscapeDigits to immediately terminate and return\n"
"the value, or 'any' to allow any digit\n"
"type can be text,ssml or xml\n"
"You can also use LoquendoTTS(TextToSay|voice=TheVoice[|EscapeDigits]) where TextTosay is you text.\n";

STANDARD_LOCAL_USER;

LOCAL_USER_DECL;

#define strlenOr0(str)  ((str) ? strlen(str) : 0)

static char* strcatPlusSep(char* str1,const char* str2,const char* sep)
{
  strcat(str1,sep);
  if (str2)
    strcat(str1,str2);
  return str1;
}

static char* LoquendoParametersForUniqueID(LoquendoTTSContext* c,const char* suffix)
{
  char* buff=malloc(10
		    +strlenOr0(c->coding)+1
		    +strlenOr0(c->pitch)+1
		    +strlenOr0(c->pitchRange)+1
		    +strlenOr0(c->sampleRate)+1
		    +strlenOr0(c->speed)+1
		    +strlenOr0(c->speedRange)+1
		    +strlenOr0(c->voice)+1
		    +strlenOr0(c->volume)+1
		    +strlenOr0(c->volumeRange)+1
		    +strlenOr0(suffix)+1
		    +1);
  if (c->coding)
    strcpy(buff,c->coding);
  else
    buff[0]='\0';
  strcatPlusSep(buff,c->pitch,"|");
  strcatPlusSep(buff,c->pitchRange,"|");
  strcatPlusSep(buff,c->sampleRate,"|");
  strcatPlusSep(buff,c->speed,"|");
  strcatPlusSep(buff,c->speedRange,"|");
  strcatPlusSep(buff,c->voice,"|");
  strcatPlusSep(buff,c->volume,"|");
  strcatPlusSep(buff,c->volumeRange,"|");
  strcatPlusSep(buff,suffix,"|");
  return buff;
};
static int send_waveform_to_fd(char *waveform, int length, int fd) 
{
  int res;
  int x;
#ifdef __PPC__ 
  char c;
#endif
  
  res = fork();
  if (res < 0)
    ast_log(LOG_WARNING, "Fork failed\n");
  if (res)
    return res;
  for (x=0;x<256;x++)
    {
      if (x != fd)
	close(x);
    }
  //IAS
#ifdef __PPC__  
  for( x=0; x<length; x+=2)
    {
      c = *(waveform+x+1);
      *(waveform+x+1)=*(waveform+x);
      *(waveform+x)=c;
    }
#endif
  
  write(fd,waveform,length);
  write(fd,"a",1);
  close(fd);
  exit(0);
}


static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length,const char *intkeys)
{
	int res=0;
	int fds[2];
	int ms = -1;
	int pid = -1;
	int needed = 0;
	int owriteformat;
	struct ast_frame *f;
	struct myframe {
		struct ast_frame f;
		char offset[AST_FRIENDLY_OFFSET];
		char frdata[2048];
	} myf;
	
        if (pipe(fds)) {
                 ast_log(LOG_WARNING, "Unable to create pipe\n");
        	return -1;
        }
	                                                
	/* Answer if it's not already going */
	if (chan->_state != AST_STATE_UP)
		ast_answer(chan);
	ast_stopstream(chan);

	owriteformat = chan->writeformat;
	res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
	ast_log(LOG_NOTICE, "ast_set_write_format: %d\n",res);
	if (res < 0) {
		ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
		return -1;
	}
	
	res=send_waveform_to_fd(waveform,length,fds[1]);
	ast_log(LOG_NOTICE, "end_waveform_to_fd: res=%d \n",res);
	if (res >= 0) {
		pid = res;
		/* Order is important -- there's almost always going to be mp3...  we want to prioritize the
		   user */
		for (;;) {
			ms = 1000;
			res = ast_waitfor(chan, ms);
	ast_log(LOG_NOTICE, "ast_waitfor: res=%d \n",res);
			if (res < 1) {
				res = -1;
				break;
			}
			f = ast_read(chan);
	ast_log(LOG_NOTICE, "f->frametype=%d\n",f->frametype);
			if (!f) {
				ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
				res = -1;
				break;
			}
			if (f->frametype == AST_FRAME_DTMF) {
				ast_log(LOG_NOTICE, "User pressed a key\n");
				if (intkeys && strchr(intkeys, f->subclass)) {
					res = f->subclass;
					ast_frfree(f);
					break;
				}
			}
			if (f->frametype == AST_FRAME_VOICE) {
				/* Treat as a generator */
				needed = f->samples * 2;
				if (needed > sizeof(myf.frdata)) {
					ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
						(int)sizeof(myf.frdata) / 2, needed/2);
					needed = sizeof(myf.frdata);
				}
				res = read(fds[0], myf.frdata, needed);
				if (res > 0) {
					myf.f.frametype = AST_FRAME_VOICE;
					myf.f.subclass = AST_FORMAT_SLINEAR;
					myf.f.datalen = res;
					myf.f.samples = res / 2;
					myf.f.mallocd = 0;
					myf.f.offset = AST_FRIENDLY_OFFSET;
					myf.f.src = __PRETTY_FUNCTION__;
					myf.f.data = myf.frdata;
					if (ast_write(chan, &myf.f) < 0) {
						res = -1;
						break;
					}
					if (res < needed) { // last frame
						ast_log(LOG_NOTICE, "Last frame\n");
						res=0;
						break;
					}
				} else {
					ast_log(LOG_NOTICE, "No more waveform\n");
					res = 0;
				}
			}
			ast_frfree(f);
		}
	}
	close(fds[0]);
	close(fds[1]);
//	if (pid > -1)
//		kill(pid, SIGKILL);
	if (!res && owriteformat)
		ast_set_write_format(chan, owriteformat);
	return res;
}

#define MAXLEN 180
#define MAXFESTLEN 2048

static const char* next_sep(const char *path)
{
  while (*path)
    {
      if (*path == '/' || *path == '\\')
	return path;
      else
	path++;
    };
  return NULL;
}

static int mkdirhierforfilepath(const char* path)
{
  const char *next, *prev;
  char buf[2048];
  struct stat st;
  
  prev = path;
  ast_log(LOG_NOTICE, "path: '%s'\n",path);
  while ((next = next_sep(prev)))
    {
      //ast_log(LOG_NOTICE, "next: '%s'\n",next);
      if (next>path)//skip 1st /
	{
	  strncpy(buf, path, next - path);
	  buf[next - path] = '\0';
	  // if parent dir doesn't exist yet create it
	  if (stat(buf, &st)==0)
	    {
	      //ast_log(LOG_NOTICE, "%s exists\n",buf);
	    }
	  else
	    {
	      //ast_log(LOG_NOTICE, "Create %s\n",buf);
	      if (mkdir(buf,777)!=0)
		{
		  ast_log(LOG_ERROR, "Can't create: %s\n",buf);
		  return -1;
		};
	    };
	};
      prev = next + 1;
    }
  return 0;
}

       
static void toHex(char* dst,unsigned char* src, int srcLen)
{
  static const char     *hexChars = "0123456789ABCDEF";
  int i=0;
  for(i=0;i<srcLen;i++,src++,dst+=2)
    {
      unsigned char     c = *src;
      *dst = hexChars[(c >> 4) & 0x0f];
      *(dst+1) = hexChars[c & 0x0f];
    }
  *dst='\0';
}
// you must free the returned pointer
static char* soundFileNamePath(const char* filename,const char* extension)
{
  char *path;
  int path_size = 0;
  char tmp[AST_CONFIG_MAX_PATH]="";

  snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_VAR_DIR, "sounds");
  path_size = strlen(tmp) + strlen(filename) + (extension ? strlen(extension) : 0) + 10;
  path = malloc(path_size);
  if (path)
    {
      if (extension)
	{
	  if (filename[0] == '/') 
	    snprintf(path, path_size, "%s%s.%s", tmp,filename,extension);
	  else
	    snprintf(path, path_size, "%s/%s.%s",tmp,filename,extension);
	}
      else
	{
	  if (filename[0] == '/') 
	    snprintf(path, path_size, "%s%s", tmp,filename);
	  else
	    snprintf(path, path_size, "%s/%s",tmp,filename);
	}
    }
  return path;
};

static char* cacheFilePathForUniqID(const char* cacheDir,int cacheDirLevels,const char* uniqueID,int withSuffix)
{
  char* aPath=malloc(strlen(cacheDir)+cacheDirLevels*2+strlen(uniqueID)+20);
  int i=0;
  int iChar=0;
  int fnIndex=0;
  int length=strlen(uniqueID);
  strcpy(aPath,cacheDir);
  iChar=strlen(aPath);
  aPath[iChar++]='/';

  for(i=0;i<cacheDirLevels;i++)
    {
      while(fnIndex<length && 
	    !((uniqueID[fnIndex]>='0' && uniqueID[fnIndex]<='9')
	      ||(toupper(uniqueID[fnIndex])>='A' && toupper(uniqueID[fnIndex])<='F')))
	{
	  fnIndex++;
	};
      if (fnIndex>=length)
	break;
      else
	{
	  aPath[iChar++]=toupper(uniqueID[fnIndex]);
	  fnIndex++;
	  aPath[iChar++]='/';
	};
    }
  aPath[iChar++]='\0';
  strcat(aPath,uniqueID);
  if (withSuffix)
    strcat(aPath,".wav");
  return aPath;
};

static void TTSCALLBACK loquendoTTS_callbackFN(ttsEventType nReason,
					       void *lData,
					       void *pUser)
{
  LoquendoTTSContext* loquendoTTSContext=(LoquendoTTSContext*)pUser;
  switch(nReason)
    {
    case TTSEVT_DATA:
      {
      ttsPhonInfoType* p=(ttsPhonInfoType*)lData;
      //totalsize+=p->NBytes;
      //ast_log(LOG_NOTICE,"DATA reason size=%ld NBytes=%ld DurationMs=%d totalsize=%d\n",p->size,p->NBytes,p->DurationMs,totalsize);
      ast_log(LOG_NOTICE,"DATA reason size=%ld NBytes=%ld DurationMs=%d\n",p->size,p->NBytes,p->DurationMs);
//      fwrite(p->buffer,1,p->NBytes,aFile);
      /*res = */send_waveform_to_channel(loquendoTTSContext->channel,p->buffer,p->NBytes,loquendoTTSContext->escapeDigitsString);
/*
      void *buffer;                       // PCM buffer                       
      unsigned long size;                 // PCM buffer size in bytes         
      unsigned long ipacode;              // current phoneme IPA code         
      unsigned long NBytes;               // current phoneme size in bytes    
      unsigned short DurationMs;          // duration in msec                 
      unsigned char bValid;               // current phoneme is complete      
      char tag[ttsSTRINGMAXLEN];          // optional tag for current phoneme 
*/
      }
      break;
    case TTSEVT_BOOKMARK:
      ast_log(LOG_NOTICE,"BOOKMARK reason\n");
      break;
    case TTSEVT_AUDIOSTART:
      ast_log(LOG_NOTICE,"AUDIOSTART reason\n");
      break;
    case TTSEVT_ENDOFSPEECH:
      ast_log(LOG_NOTICE,"ENDOFSPEECH reason\n");
      break;
    case TTSEVT_FREESPACE:
      ast_log(LOG_NOTICE,"FREESPACE reason\n");
      break;
    case TTSEVT_OVERFLOW:
      ast_log(LOG_NOTICE,"OVERFLOW reason\n");
      break;
    case TTSEVT_ERROR:
      ast_log(LOG_NOTICE,"ERROR reason\n");
      break;
    case TTSEVT_PAUSE:
      ast_log(LOG_NOTICE,"PAUSE reason\n");
      break;
    case TTSEVT_RESUME:
      ast_log(LOG_NOTICE,"RESUME reason\n");
      break;
    case TTSEVT_POSITION:
      ast_log(LOG_NOTICE,"POSITION reason\n");
      break;
    case TTSEVT_TAG:
      ast_log(LOG_NOTICE,"TAG reason\n");
      break;
    case TTSEVT_NOTSENT:
      ast_log(LOG_NOTICE,"NOTSENT reason\n");
      break;
    case TTSEVT_PHONEMES:
      ast_log(LOG_NOTICE,"PHONEMES reason\n");
      break;
    case TTSEVT_DEBUG:
      //      ast_log(LOG_NOTICE,"DEBUG reason\n");
      break;
    case TTSEVT_WORDTRANSCRIPTION:
      ast_log(LOG_NOTICE,"WORDTRANSCRIPTION reason\n");
      break;
    case TTSEVT_TEXT:
      ast_log(LOG_NOTICE,"TEXT reason\n");
      break;
    case TTSEVT_SENTENCE:
      ast_log(LOG_NOTICE,"SENTENCE reason\n");
      break;
    case TTSEVT_AUDIO:
      ast_log(LOG_NOTICE,"AUDIO reason\n");
      break;
    case TTSEVT_VOICECHANGE:
      ast_log(LOG_NOTICE,"VOICECHANGE reason\n");
      break;
    case TTSEVT_LANGUAGECHANGE:
      ast_log(LOG_NOTICE,"LANGUAGECHANGE reason\n");
      break;
    case TTSEVT_RESERVED:
      ast_log(LOG_NOTICE,"RESERVED reason\n");
      break;
    default:
      ast_log(LOG_NOTICE,"Unknown reason %d\n",(int)nReason);
      break;
    }
};

#define DefRecordLoquendoError(); \
{ \
  char* loquendoErrorString=NULL; \
loquendoErrorString=ttsGetError(hInstance); \
if (loquendoErrorString) \
 *errorString=strdup(loquendoErrorString); \
result=-1; \
};

#define DefRecordError(ErrorString); \
{ \
 *errorString=strdup(ErrorString); \
result=-1; \
};

// 0=OK
// -1=error
// you should free errorString
static int renderWithLoquendoTTSContext(LoquendoTTSContext* loquendoTTSContext,char** errorString)
{
  ttsHandleType hInstance=NULL;		// Instance handle
  ttsHandleType hVoice=NULL;		// Voice handle
  ttsResultType err=tts_OK;	       	// Error code returned by TTS APIs 
  int result=0;

  *errorString=NULL;

  ast_log(LOG_NOTICE,"Start renderWithLoquendoTTSContext\n");

  // Initializes the LoquendoTTS Instance 
  err = ttsNewInstance(&hInstance,
		       NULL, 			// hSession
		       loquendoTTSContext->loquendoConfFilePath);	// ini file
  if (err != tts_OK)
    {
      DefRecordLoquendoError();
      ast_log(LOG_ERROR,"%s", *errorString);
    }
  else
    {
      // Sets the voice parameters
      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: setVoice\n");
      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: voice=%s\n",loquendoTTSContext->voice);
      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: sampleRate=%d\n",atoi(loquendoTTSContext->sampleRate));
      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: coding=%s\n",loquendoTTSContext->coding);
      err = ttsNewVoice(&hVoice, hInstance,loquendoTTSContext->voice, atoi(loquendoTTSContext->sampleRate), loquendoTTSContext->coding);
      if (err != tts_OK)
	{ 
	  DefRecordLoquendoError();
	  ast_log(LOG_ERROR,"%s", *errorString);
	}
      else
	{
	  // Sets the audio destination
	  err = ttsSetAudio(hInstance, "LoqAudioFile", loquendoTTSContext->complete_wav_path, loquendoTTSContext->coding, 0);
	    
	  if (err != tts_OK)
	    {
	      DefRecordLoquendoError();
	      ast_log(LOG_ERROR,"%s", *errorString);
	    }
	  else
	    {
	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: pitch\n");

	      if (err == tts_OK
		  && loquendoTTSContext->pitch
		  && strlen(loquendoTTSContext->pitch)>0)
		{
		  err=ttsSetPitch(hInstance,atoi(loquendoTTSContext->pitch));
		  if (err != tts_OK)
		    {
		      DefRecordLoquendoError();
		      ast_log(LOG_ERROR,"%s", *errorString);
		    };		  
		};

	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: pitchRange\n");
	      if (err == tts_OK
		  && loquendoTTSContext->pitchRange
		  && strlen(loquendoTTSContext->pitchRange)>0)
		{
		  int pitchMin=0;
		  int pitchNormal=0;
		  int pitchMax=0;
		  char* n=strchr(loquendoTTSContext->pitchRange,'-');
		  if (n)
		    {
		      pitchMin=atoi(loquendoTTSContext->pitchRange);
		      pitchNormal=atoi(n+1);
		      n=strchr(n,'-');
		      if (n)
			{
			  pitchMax=atoi(n+1);
			  err=ttsSetPitchRange(hInstance,pitchMin,pitchNormal,pitchMax);
			  if (err != tts_OK)
			    {
			      DefRecordLoquendoError();
			      ast_log(LOG_ERROR,"%s", *errorString);
			    };		  
			}
		      else
			{
			  DefRecordError("Bad pitchRange");
			  ast_log(LOG_ERROR,"Bad pitchRange: %s\n",loquendoTTSContext->pitchRange);
			};
		    }
		  else
		    {
		      DefRecordError("Bad pitchRange");
		      ast_log(LOG_ERROR,"Bad pitchRange: %s\n",loquendoTTSContext->pitchRange);
		    };
		  
		};

	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: speed\n");
	      if (err == tts_OK
		  && loquendoTTSContext->speed
		  && strlen(loquendoTTSContext->speed)>0)
		{
		  err=ttsSetSpeed(hInstance,atoi(loquendoTTSContext->speed));
		  if (err != tts_OK)
		    {
		      DefRecordLoquendoError();
		      ast_log(LOG_ERROR,"%s", *errorString);
		    };		  
		};

	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: speedRange\n");
	      if (err == tts_OK
		  && loquendoTTSContext->speedRange
		  && strlen(loquendoTTSContext->speedRange)>0)
		{
		  int speedMin=0;
		  int speedNormal=0;
		  int speedMax=0;
		  char* n=strchr(loquendoTTSContext->speedRange,'-');
		  if (n)
		    {
		      speedMin=atoi(loquendoTTSContext->speedRange);
		      speedNormal=atoi(n+1);
		      n=strchr(n,'-');
		      if (n)
			{
			  speedMax=atoi(n+1);
			  err=ttsSetSpeedRange(hInstance,speedMin,speedNormal,speedMax);
			  if (err != tts_OK)
			    {
			      DefRecordLoquendoError();
			      ast_log(LOG_ERROR,"%s", *errorString);
			    };		  
			}
		      else
			{
			  DefRecordError("Bad speedRange");
			  ast_log(LOG_ERROR,"Bad speedRange: %s\n",loquendoTTSContext->speedRange);
			};
		    }
		  else
		    {
		      DefRecordError("Bad speedRange");
		      ast_log(LOG_ERROR,"Bad speedRange: %s\n",loquendoTTSContext->speedRange);
		    };
		  
		};

	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: volume\n");
	      if (err == tts_OK
		  && loquendoTTSContext->volume
		  && strlen(loquendoTTSContext->volume)>0)
		{
		  err=ttsSetVolume(hInstance,atoi(loquendoTTSContext->volume));
		  if (err != tts_OK)
		    {
		      DefRecordLoquendoError();
		      ast_log(LOG_ERROR,"%s", *errorString);
		    };		  
		};

	      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: volumeRange\n");
	      if (err == tts_OK
		  && loquendoTTSContext->volumeRange
		  && strlen(loquendoTTSContext->volumeRange)>0)
		{
		  int volumeMin=0;
		  int volumeNormal=0;
		  int volumeMax=0;
		  char* n=strchr(loquendoTTSContext->volumeRange,'-');
		  if (n)
		    {
		      volumeMin=atoi(loquendoTTSContext->volumeRange);
		      volumeNormal=atoi(n+1);
		      n=strchr(n,'-');
		      if (n)
			{
			  volumeMax=atoi(n+1);
			  err=ttsSetVolumeRange(hInstance,volumeMin,volumeNormal,volumeMax);
			  if (err != tts_OK)
			    {
			      DefRecordLoquendoError();
			      ast_log(LOG_ERROR,"%s", *errorString);
			    };		  
			}
		      else
			{
			  DefRecordError("Bad volumeRange");
			  ast_log(LOG_ERROR,"Bad volumeRange: %s\n",loquendoTTSContext->volumeRange);
			};
		    }
		  else
		    {
		      DefRecordError("Bad volumeRange");
		      ast_log(LOG_ERROR,"Bad volumeRange: %s\n",loquendoTTSContext->volumeRange);
		    };		  
		};

	      if (err == tts_OK)
		{
		  int dataCoding=TTSAUTODETECT;
		  if (loquendoTTSContext->dataCoding)
		    {
		      if (strcmp(loquendoTTSContext->dataCoding,"unicode")==0)
			dataCoding=TTSUNICODE;
		      else if (strcmp(loquendoTTSContext->dataCoding,"ansi")==0)
			dataCoding=TTSANSI;
		      else if (strcmp(loquendoTTSContext->dataCoding,"autodetect")==0)
			dataCoding=TTSAUTODETECT;
		      else
			{
			  //DefRecordError("Unknown dataEncoding");
			  ast_log(LOG_ERROR,"Unknown dataEncoding: %s. Use autodetect",loquendoTTSContext->dataCoding);
			};
		    };
		      


		  /*if (atoi(loquendoTTSContext->streamIt))
		    {
		      err= ttsRegisterCallback(hInstance,			// hInstance
					       &loquendoTTS_callbackFN, 	// callBack function
					       (void*)loquendoTTSContext,      	// user parameter
					       0);				// reserved
		    };
		  */
		  if (err != tts_OK)
		    {
		      DefRecordLoquendoError();
		      ast_log(LOG_ERROR,"%s", *errorString);
		    }
		  else
		    {
		      // Converts text to speech 
		      ast_log(LOG_NOTICE,"renderWithLoquendoTTSContext: GoOn dataType=%d\n",loquendoTTSContext->dataType);
		      switch(loquendoTTSContext->dataType)
			{
			case LoquendoTTSDataType_Text:
			  err = ttsRead(hInstance,	// Instance handle
					loquendoTTSContext->renderData,	// Text
					TTSBUFFER,	// This is a buffer
					TTSANSI,	// Input text is in ANSI
					TTSDEFAULT,	// Default
					TTSBLOCKING);	// ttsRead keeps control until the end 
			  break;
			case LoquendoTTSDataType_TextFile:
			  err = ttsRead(hInstance,	// Instance handle
					loquendoTTSContext->complete_data_path,	// File Path of text
					TTSFILE,	// This is a file
					dataCoding,	// Input text is in ANSI
					TTSPARAGRAPH,	// From XML
					TTSBLOCKING);	// ttsRead keeps control until the end 
			  break;
			case LoquendoTTSDataType_XML:
			  err = ttsRead(hInstance,	// Instance handle
					loquendoTTSContext->complete_data_path,	// File Path of text
					TTSFILE,	// This is a file
					dataCoding,	// Input text is in ANSI
					TTSXML,	// From XML
					TTSBLOCKING);	// ttsRead keeps control until the end 
			  break;
			case LoquendoTTSDataType_SSML:
			  err = ttsRead(hInstance,	// Instance handle
					loquendoTTSContext->complete_data_path,	// File Path of text
					TTSFILE,	// This is a file
					dataCoding,	// Input text is in ANSI
					TTSSSML,	// From SSML
					TTSBLOCKING);	// ttsRead keeps control until the end 
			  break;
			};
		      if (err != tts_OK)
			{ 
			  DefRecordLoquendoError();
			  ast_log(LOG_ERROR,"%s", *errorString);
			};
		    };
		};
	    };
	};
      // Closes the Loquendo TTS instance; the voice will beautomatically closed 
      ttsDeleteInstance(hInstance);
      hInstance=NULL;
    };
  ast_log(LOG_NOTICE,"Stop renderWithLoquendoTTSContext\n");
  return result;
}

static void parseOption(LoquendoTTSContext* loquendoTTSContext,char* option)
{
  ast_log(LOG_NOTICE, "option : '%s'\n",option);
  if (strncmp(option,"streamIt=",9)==0)
    {
      loquendoTTSContext->streamIt=option+9;
      ast_log(LOG_NOTICE, "streamIt : '%s'\n",loquendoTTSContext->streamIt);
    }
  else if (strncmp(option,"coding=",7)==0)
    {
      loquendoTTSContext->coding=option+7;
      ast_log(LOG_NOTICE, "coding : '%s'\n",loquendoTTSContext->coding);
    }
  else if (strncmp(option,"sampleRate=",11)==0)
    {
      loquendoTTSContext->sampleRate=option+11;
      ast_log(LOG_NOTICE, "sampleRate : '%s'\n",loquendoTTSContext->sampleRate);
    }
  else if (strncmp(option,"pitch=",6)==0)
    {
      loquendoTTSContext->pitch=option+6;
      ast_log(LOG_NOTICE, "pitch : '%s'\n",loquendoTTSContext->pitch);
    }
  else if (strncmp(option,"voice=",6)==0)
    {
      loquendoTTSContext->voice=option+6;
      ast_log(LOG_NOTICE, "voice : '%s'\n",loquendoTTSContext->voice);
    }
  else if (strncmp(option,"pitch=",6)==0)
    {
      loquendoTTSContext->pitch=option+6;
      ast_log(LOG_NOTICE, "pitch : '%s'\n",loquendoTTSContext->pitch);
    }
  else if (strncmp(option,"pitchRange=",11)==0)
    {
      loquendoTTSContext->pitchRange=option+11;
      ast_log(LOG_NOTICE, "pitchRange : '%s'\n",loquendoTTSContext->pitchRange);
    }
  else if (strncmp(option,"speed=",6)==0)
    {
      loquendoTTSContext->speed=option+6;
      ast_log(LOG_NOTICE, "speed : '%s'\n",loquendoTTSContext->speed);
    }
  else if (strncmp(option,"speedRange=",11)==0)
    {
      loquendoTTSContext->speedRange=option+11;
      ast_log(LOG_NOTICE, "speedRange : '%s'\n",loquendoTTSContext->speedRange);
    }
  else if (strncmp(option,"volume=",7)==0)
    {
      loquendoTTSContext->volume=option+7;
      ast_log(LOG_NOTICE, "volume : '%s'\n",loquendoTTSContext->volume);
    }
  else if (strncmp(option,"volumeRange=",12)==0)
    {
      loquendoTTSContext->volumeRange=option+12;
      ast_log(LOG_NOTICE, "volumeRange : '%s'\n",loquendoTTSContext->volumeRange);
    }
  else if (strncmp(option,"textCoding=",11)==0)
    {
      loquendoTTSContext->dataCoding=option+11;
      ast_log(LOG_NOTICE, "textCoding : '%s'\n",loquendoTTSContext->dataCoding);
    }
  else
    {
      const char* eqSign=strchr(option,'=');
      if (eqSign)
	{
	  ast_log(LOG_NOTICE, "Unknown option : '%s'\n",option);
	}
      else
	{
	  loquendoTTSContext->escapeDigitsString=option;
	  
	  if (!strcasecmp(loquendoTTSContext->escapeDigitsString, "any"))
	    loquendoTTSContext->escapeDigitsString = AST_DIGIT_ANY;
	  ast_log(LOG_NOTICE, "escapeDigitsString : '%s'\n",loquendoTTSContext->escapeDigitsString);
	};
    }	  
};

void retrieveDefaultParam(struct ast_config *cfg,const char* paramName,char** paramValuePtr,int isMandatory)
{
  if (!*paramValuePtr)
    {
      if (!(*paramValuePtr=ast_variable_retrieve(cfg, "defaults", (char*)paramName)))
	{
	  if (isMandatory)
	    ast_log(LOG_ERROR, "no %s\n",paramName);
	};
    };
  ast_log(LOG_NOTICE, "%s: '%s'\n",paramName,*paramValuePtr);  
};

//RESULT_FAILURE: Pb
//RESULT_SUCCESS: OK. No key pressed
// >0: key pressed
static int playStreamAndWait(struct ast_channel *chan,const char* streamPathNoSuffix,char* escapeDigits,long* endPos)
{
  struct ast_filestream *fs;
  long max_length;
  int res=0;
  *endPos=0;

  ast_log(LOG_NOTICE, "PlayStream %p %s ?\n",streamPathNoSuffix,streamPathNoSuffix);
  fs = ast_openstream(chan, streamPathNoSuffix, NULL);
  if(!fs)
    {
      //fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, *endPos);
      ast_log(LOG_NOTICE, "PlayStreamNotOK ?\n");
      res=RESULT_FAILURE;
    }
  else
    {
      ast_seekstream(fs, 0, SEEK_END);
      max_length = ast_tellstream(fs);
      ast_seekstream(fs, 0, SEEK_SET);
      res = ast_applystream(chan, fs);
      res = ast_playstream(fs);
      ast_log(LOG_NOTICE, "PlayStream fs=%p res=%d\n",fs,res);
      if (res)
	{
	  ast_log(LOG_NOTICE, "PlayStreamOK res=%d\n",res);
	  //fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, *endPos);
	  if (res >= 0)
	    res=RESULT_SHOWUSAGE;//?
	  else
	    res=RESULT_FAILURE;
	}
      else
	{	  
	  ast_log(LOG_NOTICE, "will ast_waitstream esc %p=%s\n",escapeDigits,escapeDigits);
	  res = ast_waitstream(chan,escapeDigits);
	  ast_log(LOG_NOTICE, "done ast_waitstream res=%d\n",res);

	  // Problem or key pressed
	  if (res)
	    {
	      ast_log(LOG_NOTICE, "cached ast_waitstream=%d\n",res);
	      
	      ast_log(LOG_NOTICE, "chan->stream=%p\n",(void*)chan->stream);
	      *endPos = (chan->stream)?ast_tellstream(chan->stream):max_length;
	      ast_stopstream(chan);
	      if (res == 1)
		{
		  // Stop this command, don't print a result line, as there is a new command 
		  res=RESULT_SUCCESS;
		  ast_log(LOG_NOTICE, "PlayStreamOK \n");
		}
	      else
		{
		  ast_log(LOG_NOTICE, "PlayStreamOK? res=%d endpos=%ld\n",res,*endPos);
		  if (res<0)
		    res=RESULT_FAILURE;
		  //else res=keypressed 
		};
	    };
	};
    };
  return res;
};
/* -1: Parameter Error
RESULT_FAILURE (2): Pb
RESULT_SUCCESS: (0) OK. No key pressed
 >0: key pressed
*/
static int loquendoTTS_exec(struct ast_channel *chan, void *vdata)
{
  int res=0;
  
  ast_log(LOG_NOTICE, "Data Loquendo TTS : %s\n",(char *)vdata);
  if (!vdata || ast_strlen_zero(vdata))
    {
      ast_log(LOG_WARNING, "loquendoTTS requires an argument (test or pathname)\n");
      return -1;
    }
  else
    {
      struct localuser *u;
      char* separator=NULL;
      char options[256]="";
      LoquendoTTSContext loquendoTTSContext;
      memset(&loquendoTTSContext,0,sizeof(LoquendoTTSContext));
      loquendoTTSContext.channel=chan;

      separator=strchr(vdata, '|');
      int dataLen=0;
      ast_log(LOG_NOTICE, "separator : '%s'\n",separator);
      if (separator)
	{	  
	  dataLen=separator-(char*)vdata;
	  int optionsLen=strlen(separator);
	  if (optionsLen>255)
	    optionsLen=255;
	  strcpy(options,separator+1);

	  char* last=options;
	  do
	    {
	      ast_log(LOG_NOTICE, "last : '%s'\n",last);
	      separator=strchr(last, '|');
	      if (separator)
		*separator='\0';
	      ast_log(LOG_NOTICE, "=> last : '%s'\n",last);
	      parseOption(&loquendoTTSContext,last);
	      last=separator+1;
	    }
	  while(separator);
	}
      else
	dataLen=strlen(vdata);

      if (!loquendoTTSContext.sampleRate)
	loquendoTTSContext.sampleRate="8000";
      if (!loquendoTTSContext.coding)
	loquendoTTSContext.coding="l";
      if (!loquendoTTSContext.streamIt)
	loquendoTTSContext.streamIt="1";

      ast_log(LOG_NOTICE, "dataLen : %d\n",dataLen);
      ast_log(LOG_NOTICE, "vdata : %s\n",(char*)vdata);
      if (dataLen==0)
	{
	  
	}
      else
	{
	  int vdataOffset=0;
	  char* suffix=NULL;
	  ast_log(LOG_NOTICE, "vdata=[%s]\n",((char*)vdata));
	  if (((char*)vdata)[0]=='\"')
	    {
	      vdataOffset++;
	      if (((char*)vdata)[dataLen-1]=='\"')
		dataLen--;
	    };
	  if (strncmp(vdata+vdataOffset,"xml=",4)==0)
	    {
	      ast_log(LOG_NOTICE, "XML Data\n");
	      loquendoTTSContext.dataType=LoquendoTTSDataType_XML;
	      vdataOffset+=4;
	      suffix="xml";
	    }
	  else if (strncmp(vdata+vdataOffset,"ssml=",5)==0)
	    {
	      ast_log(LOG_NOTICE, "SSML Data\n");
	      loquendoTTSContext.dataType=LoquendoTTSDataType_SSML;
	      vdataOffset+=5;
	      suffix="ssml";
	    }
	  else if (strncmp(vdata+vdataOffset,"text=",5)==0)
	    {
	      ast_log(LOG_NOTICE, "TextFile Data\n");
	      loquendoTTSContext.dataType=LoquendoTTSDataType_TextFile;
	      vdataOffset+=5;
	      suffix="text";
	    }
	  else
	    {
	      ast_log(LOG_NOTICE, "Text Data\n");
	      loquendoTTSContext.dataType=LoquendoTTSDataType_Text;
	    };

	  dataLen-=vdataOffset;
	  loquendoTTSContext.renderData=malloc(dataLen+1);
	  strncpy(loquendoTTSContext.renderData,vdata+vdataOffset,dataLen);
	  loquendoTTSContext.renderData[dataLen]='\0';

	  ast_log(LOG_NOTICE, "loquendoTTSContext.dataType=%d loquendoTTSContext.renderData: '%s'\n",loquendoTTSContext.dataType,loquendoTTSContext.renderData);

	  LOCAL_USER_ADD(u);
	  
	  struct ast_config *cfg;
	  cfg = ast_config_load(LOQUENDOTTS_CONFIG);
	  if (!cfg)
	    {
	      ast_log(LOG_WARNING, "No such configuration file %s\n", LOQUENDOTTS_CONFIG);
	    }
	  else
	    {
	      if (!(loquendoTTSContext.loquendoConfFilePath = ast_variable_retrieve(cfg, "general", "configurationPath")))
		{
		  loquendoTTSContext.loquendoConfFilePath = "/opt/Loquendo/LTTS/default.session";
		}
	      retrieveDefaultParam(cfg,"voice",&loquendoTTSContext.voice,1);
	      retrieveDefaultParam(cfg,"pitch",&loquendoTTSContext.pitch,1);
	      retrieveDefaultParam(cfg,"pitchRange",&loquendoTTSContext.pitchRange,1);
	      retrieveDefaultParam(cfg,"speed",&loquendoTTSContext.speed,1);
	      retrieveDefaultParam(cfg,"speedRange",&loquendoTTSContext.speedRange,1);
	      retrieveDefaultParam(cfg,"volume",&loquendoTTSContext.volume,1);
	      retrieveDefaultParam(cfg,"volumeRange",&loquendoTTSContext.volumeRange,1);

	      switch(loquendoTTSContext.dataType)
		{
		case LoquendoTTSDataType_Text:
		  {
		    char* cacheDir=NULL;
		    char* cacheDirLevelsString=NULL;
		    int cacheDirLevels=0;
		    
		    if (!(cacheDir=ast_variable_retrieve(cfg, "general", "cacheDir")))
		      {
			cacheDir="/tmp/LoquendoCache";
		      }
		    
		    if ((cacheDirLevelsString=ast_variable_retrieve(cfg, "general", "cacheDirLevels")))
		      cacheDirLevels=atoi(cacheDirLevelsString);
		    
		    if (cacheDirLevels<=0)
		      cacheDirLevels=4;
		    
		    char* aText=LoquendoParametersForUniqueID(&loquendoTTSContext,loquendoTTSContext.renderData);
		    char uniqueID[(SHA_DIGEST_LENGTH+16)*2+1+1]="";
		    char* tmpUniqueID=uniqueID;
		    
		    struct MD5Context md5ctx;
		    unsigned char MD5Res[16];
		    
		    MD5Init(&md5ctx);
		    MD5Update(&md5ctx,aText,strlen(aText));
		    MD5Final(MD5Res,&md5ctx);
		    
		    unsigned char shaRes[SHA_DIGEST_LENGTH];
		    SHA1(aText,strlen(aText),shaRes);
		    
		    free(aText);
		    
		    toHex(tmpUniqueID,MD5Res,16);
		    tmpUniqueID+=32;
		    *tmpUniqueID='-';
		    tmpUniqueID++;
		    toHex(tmpUniqueID,shaRes,SHA_DIGEST_LENGTH);
		    ast_log(LOG_NOTICE, "uniqueID: '%s'\n",uniqueID);
		    
		    loquendoTTSContext.complete_wav_path=cacheFilePathForUniqID(cacheDir,cacheDirLevels,uniqueID,1);
		    loquendoTTSContext.complete_wav_path_nosuffix=cacheFilePathForUniqID(cacheDir,cacheDirLevels,uniqueID,0);
		    ast_log(LOG_NOTICE, "loquendoTTSContext.complete_wav_path: '%s'\n",loquendoTTSContext.complete_wav_path);
		    if (mkdirhierforfilepath(loquendoTTSContext.complete_wav_path)!=0)
		      {
			ast_log(LOG_ERROR, "Can't create directories for: '%s'\n",loquendoTTSContext.complete_wav_path);
		      };
		  };
		  break;
		case LoquendoTTSDataType_TextFile:
		case LoquendoTTSDataType_XML:
		case LoquendoTTSDataType_SSML:
		  {
		    loquendoTTSContext.complete_data_path=soundFileNamePath(loquendoTTSContext.renderData,suffix);
		    loquendoTTSContext.complete_wav_path=soundFileNamePath(loquendoTTSContext.renderData,"wav");
		    ast_log(LOG_NOTICE, "loquendoTTSContext.complete_data_path: '%s'\n",loquendoTTSContext.complete_data_path);
		    loquendoTTSContext.complete_wav_path_nosuffix=soundFileNamePath(loquendoTTSContext.renderData,NULL);
		    ast_log(LOG_NOTICE, "loquendoTTSContext.complete_wav_path: '%s'\n",loquendoTTSContext.complete_wav_path);
		  }
		  break;
		};

	  
	      struct stat buf;
	      if (stat(loquendoTTSContext.complete_wav_path,&buf)==0
		  && buf.st_size>0)
		{
		  ast_log(LOG_NOTICE, "cached loquendoTTSContext.complete_wav_path_nosuffix %p: '%s'\n",
			  loquendoTTSContext.complete_wav_path_nosuffix,
			  loquendoTTSContext.complete_wav_path_nosuffix);
		  
		  if (atoi(loquendoTTSContext.streamIt))
		    {
		      long endPos=0;
		      res=playStreamAndWait(chan,
					    loquendoTTSContext.complete_wav_path_nosuffix,
					    loquendoTTSContext.escapeDigitsString,
					    &endPos);
		    };
		}
	      else
		{
		  char* errorString=NULL;
		  // Not Cached		  
		  ast_log(LOG_NOTICE, "Start Render\n");
		  if (renderWithLoquendoTTSContext(&loquendoTTSContext,&errorString)<0)
		    {
		      ast_log(LOG_NOTICE, "Stop Render\n");
		      res=-1;
		    }
		  else
		    {
		      ast_log(LOG_NOTICE, "Stop Render\n");
		      if (atoi(loquendoTTSContext.streamIt))
			{
			  long endPos=0;
			  res=playStreamAndWait(chan,
						loquendoTTSContext.complete_wav_path_nosuffix,
						loquendoTTSContext.escapeDigitsString,
						&endPos);
			};
		    };
		  if (errorString)
		    {
		      free(errorString);
		      errorString=NULL;
		    };
		}
	      
	      ast_config_destroy(cfg);
	    };
	  if (loquendoTTSContext.complete_data_path)
	    {
	      free(loquendoTTSContext.complete_data_path);
	      loquendoTTSContext.complete_data_path=NULL;
	    };
	  if (loquendoTTSContext.complete_wav_path)
	    {
	      free(loquendoTTSContext.complete_wav_path);
	      loquendoTTSContext.complete_wav_path=NULL;
	    };
	  if (loquendoTTSContext.complete_wav_path_nosuffix)
	    {
	      free(loquendoTTSContext.complete_wav_path_nosuffix);
	      loquendoTTSContext.complete_wav_path_nosuffix=NULL;
	    };
	  if (loquendoTTSContext.renderData)
	    {
	      free(loquendoTTSContext.renderData);
	      loquendoTTSContext.renderData=NULL;	      
	    };
	  LOCAL_USER_REMOVE(u);         
	};
    };  

  ast_log(LOG_NOTICE, "Loquendo Result=%d\n",res);

  return res;

}

int unload_module(void)
{
	STANDARD_HANGUP_LOCALUSERS;
	return ast_unregister_application(app);
}

int load_module(void)
{
	
	return ast_register_application(app, loquendoTTS_exec, synopsis, descrip);
}

char *description(void)
{
	return tdesc;
}

int usecount(void)
{
	int res;
	STANDARD_USECOUNT(res);
	return res;
}

char *key()
{
	return ASTERISK_GPL_KEY;
}
