/*
 * Andrew Lindh <andrew@netplex.net>
 *
 * Code based on app_festival
 * Code based on app by Will Orton <will@loopfree.net>
 */

/*! \file
 *
 * \brief Cepstral Swift TTS apps
 *
 * \author Andrew Lindh
 * 
 * \ingroup applications
 */

/*** MODULEINFO
        <defaultenabled>yes</defaultenabled>
        <depend>swift</depend>
 ***/

#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 121 $")

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#include <fcntl.h>
#include <sys/time.h>
#include <errno.h>

#include "asterisk/astobj.h"
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"
#include "asterisk/module.h"
#include "asterisk/translate.h"
#include "asterisk/options.h"
#include "asterisk/time.h"
#include "asterisk/utils.h"
#include "asterisk/endian.h"

#include "swift.h"

/* Pick the correct version of asterisk for which to compile */
#if defined(AST_FORMAT_SLINEAR16) && defined(AST_CLI_DEFINE)
#define ASTERISK16
#ifdef AST_PBX_GOTO_FAILED
#define ASTERISK180
#endif
#ifndef AST_FORMAT_T140RED
#define ASTERISK160
#endif
#else
#ifdef AST_FORMAT_H264
#define ASTERISK14
#else
#Error This might be an old version of Asterisk
#endif
#endif

#if __BYTE_ORDER == __LITTLE_ENDIAN
#define htoll(b) (b)
#define htols(b) (b)
#define ltohl(b) (b)
#define ltohs(b) (b)
#else
#if __BYTE_ORDER == __BIG_ENDIAN
#define htoll(b)  \
          (((((b)      ) & 0xFF) << 24) | \
	       ((((b) >>  8) & 0xFF) << 16) | \
		   ((((b) >> 16) & 0xFF) <<  8) | \
		   ((((b) >> 24) & 0xFF)      ))
#define htols(b) \
          (((((b)      ) & 0xFF) << 8) | \
		   ((((b) >> 8) & 0xFF)      ))
#define ltohl(b) htoll(b)
#define ltohs(b) htols(b)
#else
#error "Endianess not defined"
#endif
#endif


static char *swift_app = "Swift";
static char *swift_synopsis = "Speak text through Swift text-to-speech engine.";
static char *swift_descrip =
"  Swift(text) Speaks the given text through the Swift TTS engine.\n"
"  Returns -1 on hangup or 0 otherwise.\n";

static char *swiftbackground_app = "SwiftBackground";
static char *swiftbackground_synopsis = "Speak text through Swift text-to-speech engine.";
static char *swiftbackground_descrip =
"  SwiftBackground(text) Speaks the given text through the Swift TTS engine.\n"
"  DTMF keypress will exit and continue to an extension.\n"
"  Returns -1 on hangup or 0 otherwise.\n";

static char *swiftcache_app = "SwiftCache";
static char *swiftcache_synopsis = "Find or create audio file of spoken text using Swift text-to-speech engine.";
static char *swiftcache_descrip =
"  SwiftCache(text): Finds cached audio file of spoken text.\n"
"  Records speech of the text through the Swift TTS engine if needed.\n"
"  Sets channel variable SWIFTCACHE_FILE with audio file of spoken text.\n";

/* Framesize is 320 bytes at 20ms SLIN. 16khz doubles the frame size */
#define DEFAULT_FRAMESIZE 320
int framesize;

#define DEFAULT_VOICE "Allison-8kHz"
#define DEFAULT_BUFFERSIZE 64000
#define DEFAULT_DIR "/tmp"
#define DEFAULT_INFOFILE "speech.txt"
#define DEFAULT_INDIVIDUALINFO 1
#define DEFAULT_FORCEREBUILD 0
#define DEFAULT_CLEANUP 1
#define DEFAULT_REBUILDAGE 0
#define DEFAULT_USE16KHZ 0
#define DEFAULT_FORMAT "wav"

#define SWIFT_CONFIG_FILE "swift.conf"

static unsigned long cfg_buffer_size;
static char cfg_voice[32] = {(char)NULL};
static char cfg_format[8] = {(char)NULL};
static char cfg_dir[PATH_MAX] = {(char)NULL};
static char cfg_infofile[33] = {(char)NULL}; /* limited to 32 chars and add a NULL for fun */
static short  cfg_use16khz = 0;
static short  cfg_individualinfo = 1;
static short  cfg_forcerebuild = 0;
static short  cfg_cleanup = 1;
static time_t cfg_rebuildage = 0;

// Data shared between us and the swift generating process
struct stuff {
    ASTOBJ_COMPONENTS(struct stuff);
    int generating_done;
    char *q;	/* buffer */
    char *pq_r;	/* queue read position */
    char *pq_w;	/* queue write position */
    int qc;
    int immediate_exit;
};

#ifdef ASTERISK16
static char *handle_swift_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
static char *handle_swift_show_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
static char *handle_swift_show_concurrency(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
static char *handle_swift_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
static char *handle_swift_show_voices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
static struct ast_cli_entry cli_swift[] = {
        AST_CLI_DEFINE(handle_swift_reload, "Reload config"),
        AST_CLI_DEFINE(handle_swift_show_version, "Show Version"),
        AST_CLI_DEFINE(handle_swift_show_concurrency, "Show concurrency license"),
        AST_CLI_DEFINE(handle_swift_show_config, "Show config"),
        AST_CLI_DEFINE(handle_swift_show_voices, "List Voices")
};
#else
static char handle_swift_reload_usage[] =
"Usage: swift reload\n"
"       Reload config.\n";
static char handle_swift_show_version_usage[] =
"Usage: swift show version\n"
"       Show version.\n";
static char handle_swift_show_concurrency_usage[] =
"Usage: swift show concurrency\n"
"       Show concurrency license.\n";
static char handle_swift_show_config_usage[] =
"Usage: swift show config\n"
"       Lists config.\n";
static char handle_swift_show_voices_usage[] =
"Usage: swift show voices\n"
"       Lists voices.\n";
static int handle_swift_reload(int fd, int argc, char *argv[]);
static int handle_swift_show_version(int fd, int argc, char *argv[]);
static int handle_swift_show_concurrency(int fd, int argc, char *argv[]);
static int handle_swift_show_config(int fd, int argc, char *argv[]);
static int handle_swift_show_voices(int fd, int argc, char *argv[]);
static struct ast_cli_entry cli_swift[] = {
        { { "swift", "reload", NULL },
        handle_swift_reload, "Reload config",
        handle_swift_reload_usage },
        { { "swift", "show", "version", NULL },
        handle_swift_show_version, "Show Version",
        handle_swift_show_version_usage },
        { { "swift", "show", "concurrency", NULL },
        handle_swift_show_concurrency, "Show concurrency license",
        handle_swift_show_concurrency_usage },
        { { "swift", "show", "config", NULL },
        handle_swift_show_config, "Show config",
        handle_swift_show_config_usage },
        { { "swift", "show", "voices", NULL },
        handle_swift_show_voices, "List voices",
        handle_swift_show_voices_usage }
};
#endif

/* Code Starts Here */

static int load_config(int reload);

static int swift_reload(int fd)
{
	ast_cli(fd, "Reloading %s\n", SWIFT_CONFIG_FILE);
	return(load_config(1));
}

static int swift_show_version(int fd)
{
	ast_cli(fd, "Swift Engine Information:\n");
	ast_cli(fd, " Engine Name: %s\n", swift_engine_name);
	ast_cli(fd, " Engine Version: %s\n", swift_version);
	ast_cli(fd, " Engine Build Date: %s\n", swift_date);
	ast_cli(fd, " Your Platform: %s\n", swift_platform);

	return(0);
}

static int swift_show_concurrency(int fd)
{
	swift_engine *engine = NULL;
	int max_tokens;
	int tokens_in_use;
	int ret;

	ast_cli(fd, "Swift Engine Concurrency:\n");

	/* Open the Swift TTS Engine */
	if ( (engine = swift_engine_open(NULL)) == NULL ) {
		ast_cli(fd, "Failed to open Swift Engine\n");
		return(-1);
	}

	ret=swift_license_get_concurrency_info(engine,&max_tokens,&tokens_in_use);

	if (engine) swift_engine_close(engine);

	if (ret==SWIFT_SUCCESS) {
		if (max_tokens==-1) {
			ast_cli(fd, " Max TTS Port(s): Unlimited\n");
			ast_cli(fd, " Current TTS Port(s) In use: %d\n", tokens_in_use);
		}
		if ((tokens_in_use==1) && (tokens_in_use==-1)) {
			ast_cli(fd, " Max TTS Port(s): Single FIFO (no concurrency licenses)\n");
		} else {
			ast_cli(fd, " Max TTS Port(s): %d\n", max_tokens);
			ast_cli(fd, " Current TTS Port(s) In use: %d\n", tokens_in_use);
		}
	} else {
		ast_cli(fd, " Can not communicate with license server.\n");
		ast_cli(fd, " Max TTS Port(s): Single FIFO (no concurrency licenses)\n");
	}
	
	return(0);
}

static int swift_show_config(int fd)
{
	ast_cli(fd, "Swift TTS Config:\n");
	ast_cli(fd, " Config voice is \"%s\"\n", cfg_voice);
	ast_cli(fd, " Buffer size = %ld\n", cfg_buffer_size);
	ast_cli(fd, " Use 16khz is %s\n", (cfg_use16khz?"yes":"no"));
	ast_cli(fd, " Cache directory is %s\n", cfg_dir);
	ast_cli(fd, " Cache file audio format \"%s\"\n", cfg_format);
	ast_cli(fd, " Info file is \"%s\"\n", cfg_infofile);
	ast_cli(fd, " Individual info file is %s\n", (cfg_individualinfo?"yes":"no"));
	ast_cli(fd, " Force rebuild is %s\n", (cfg_forcerebuild?"yes":"no"));
	ast_cli(fd, " Cleanup files is %s\n", (cfg_cleanup?"yes":"no"));
	if (cfg_rebuildage)
		ast_cli(fd, " Rebuild age: %ld hours\n", (long)cfg_rebuildage);
	else	ast_cli(fd, " Rebuild age: never\n");

	return(0);
}

static int swift_show_voices(int fd)
{
	swift_engine *engine = NULL;
	swift_port *port = NULL;
	swift_voice *voice;
	const char *license_status;

	/* Open the Swift TTS Engine */
	if ( (engine = swift_engine_open(NULL)) == NULL ) {
		ast_cli(fd, "Failed to open Swift Engine\n");
		return(-1);
	}

	/* Open a Swift Port through which to find voices */
	if ( (port = swift_port_open(engine, NULL)) == NULL ) {
		if (engine!=NULL) swift_engine_close(engine);
		ast_cli(fd, "Failed to open Swift Port\n");
		return(-1);
	}

	/* Find the first voice on the system */
	if ( (voice = swift_port_find_first_voice(port, NULL, NULL)) == NULL) {
		if (port) swift_port_close(port);
		if (engine) swift_engine_close(engine);
		ast_cli(fd, "Failed to find any voices!\n");
		return(-1);
	}

	ast_cli(fd, "Available voices:\n");
	/* Go through all of the voices */
	for (; voice; voice = swift_port_find_next_voice(port)) {
		if (swift_voice_get_attribute(voice, "license/key"))
			license_status = "licensed";
		else	license_status = "unlicensed";
		ast_cli(fd, "%20s: %6s, age %2s, %15s, %5sHz, %10s\n",
			swift_voice_get_attribute(voice, "name"),
			swift_voice_get_attribute(voice, "speaker/gender"),
			swift_voice_get_attribute(voice, "speaker/age"),
			swift_voice_get_attribute(voice, "language/name"),
			swift_voice_get_attribute(voice, "sample-rate"),
			license_status);

	}
	if (port) swift_port_close(port);
	if (engine) swift_engine_close(engine);

	return(0);
}

#ifdef ASTERISK16
static char *handle_swift_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
		case CLI_INIT:
			e->command = "swift reload";
			e->usage = "Usage: swift reload";
			return NULL;
		case CLI_GENERATE:
			return NULL;
	}

	swift_reload(a->fd);

	return CLI_SUCCESS;
}

static char *handle_swift_show_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
		case CLI_INIT:
			e->command = "swift show version";
			e->usage = "Usage: swift show version";
			return NULL;
		case CLI_GENERATE:
			return NULL;
	}

	swift_show_version(a->fd);

	return CLI_SUCCESS;
}

static char *handle_swift_show_concurrency(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
		case CLI_INIT:
			e->command = "swift show concurrency";
			e->usage = "Usage: swift show concurrency";
			return NULL;
		case CLI_GENERATE:
			return NULL;
	}

	swift_show_concurrency(a->fd);

	return CLI_SUCCESS;
}

static char *handle_swift_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
		case CLI_INIT:
			e->command = "swift show config";
			e->usage = "Usage: swift show config";
			return NULL;
		case CLI_GENERATE:
			return NULL;
	}

	swift_show_config(a->fd);

	return CLI_SUCCESS;
}

static char *handle_swift_show_voices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
	switch (cmd) {
		case CLI_INIT:
			e->command = "swift show voices";
			e->usage = "Usage: swift show voices";
			return NULL;
		case CLI_GENERATE:
			return NULL;
	}

	swift_show_voices(a->fd);

	return CLI_SUCCESS;
}
#else
static int handle_swift_reload(int fd, int argc, char *argv[])
{
	swift_reload(fd);

        return RESULT_SUCCESS;
}

static int handle_swift_show_version(int fd, int argc, char *argv[])
{
	swift_show_version(fd);

        return RESULT_SUCCESS;
}

static int handle_swift_show_concurrency(int fd, int argc, char *argv[])
{
	swift_show_concurrency(fd);

        return RESULT_SUCCESS;
}

static int handle_swift_show_config(int fd, int argc, char *argv[])
{
	swift_show_config(fd);

        return RESULT_SUCCESS;
}

static int handle_swift_show_voices(int fd, int argc, char *argv[])
{
	swift_show_voices(fd);

        return RESULT_SUCCESS;
}
#endif

static void swift_init_stuff(struct stuff *ps)
{
    ASTOBJ_INIT(ps);
    ps->generating_done = 0;
    ps->q = malloc(cfg_buffer_size);
    ps->pq_r = ps->q;
    ps->pq_w = ps->q;
    ps->qc = 0;
    ps->immediate_exit = 0;
}

// Returns true if swift is generating speech or we still have some
// queued up.
static int swift_generator_running(struct stuff *ps)
{
    int r;
    ASTOBJ_RDLOCK(ps);
    r = !ps->immediate_exit && (!ps->generating_done || ps->qc);
    ASTOBJ_UNLOCK(ps);
    return r;
}

static int swift_bytes_available(struct stuff *ps)
{
    int r;
    ASTOBJ_RDLOCK(ps);
    r = ps->qc;
    ASTOBJ_UNLOCK(ps);
    return r;
}

static swift_result_t swift_cb(swift_event *event, swift_event_t type, void *udata)
{
    void *buf;
    int len, spacefree;
    unsigned long sleepfor;
    swift_event_t rv = SWIFT_SUCCESS;
    struct stuff *ps = udata;
    unsigned long us;

    if (cfg_use16khz) us=31; else us=62;

    if (type == SWIFT_EVENT_AUDIO) {
        rv = swift_event_get_audio(event, &buf, &len);
        if (!SWIFT_FAILED(rv) && len > 0) {

            ASTOBJ_WRLOCK(ps);

            // Sleep while waiting for some queue space to become available
            while (len + ps->qc > cfg_buffer_size && !ps->immediate_exit) {
		/* sleep some time for the buffer to empty so we don't spin too much */
                sleepfor = ((unsigned long)(len - (cfg_buffer_size - ps->qc)) * us) + (us * (unsigned long)framesize);
                ast_log(LOG_DEBUG, "generator: %d bytes to write but only %ld space avail, sleeping %ldus\n", len, cfg_buffer_size - ps->qc, sleepfor);
                ASTOBJ_UNLOCK(ps);
                usleep(sleepfor);
                ASTOBJ_WRLOCK(ps);
            }

            if (ps->immediate_exit)
                return SWIFT_SUCCESS;
            
            spacefree = cfg_buffer_size - ((unsigned int) ps->pq_w - (unsigned int)ps->q);
            if (len > spacefree) {
//                ast_log(LOG_DEBUG, "audio fancy write; %d bytes but only %d avail to end %d totalavail\n", len, spacefree, cfg_buffer_size - ps->qc);
                //write #1 to end of mem
                memcpy(ps->pq_w, buf, spacefree);
                ps->pq_w = ps->q;
                ps->qc += spacefree;

                //write #2 and beg of mem
                memcpy(ps->pq_w, buf + spacefree, len - spacefree);
                ps->pq_w += len - spacefree;
                ps->qc += len - spacefree;
      
            } else {
//                ast_log(LOG_DEBUG, "audio easy write, %d avail to end %d totalavail\n", spacefree, cfg_buffer_size - ps->qc);
                memcpy(ps->pq_w, buf, len);
                ps->pq_w += len;
                ps->qc += len;
            }
            ASTOBJ_UNLOCK(ps);
        } else {
            ast_log(LOG_DEBUG, "got audio callback but get_audio call failed\n");
        }
            
    } else if (type == SWIFT_EVENT_END) {
        ast_log(LOG_DEBUG, "got END callback; done generating audio\n");
        ASTOBJ_WRLOCK(ps);
        ps->generating_done = 1;
        ASTOBJ_UNLOCK(ps);
    } else {
        ast_log(LOG_WARNING, "UNKNOWN callback\n");
    }
    return rv;
}

#ifdef ASTERISK180
static int swiftnow_exec(struct ast_channel *chan, const char *data, int dtmf_enable)
#else
static int swiftnow_exec(struct ast_channel *chan, void *data, int dtmf_enable)
#endif
{
    int res = 0;
    struct ast_module_user *u;
    int old_writeformat = 0;
    int availatend;
    struct ast_frame *f;
    struct myframe {
        struct ast_frame f;
        unsigned char offset[AST_FRIENDLY_OFFSET];
        unsigned char frdata[DEFAULT_FRAMESIZE*4];
    } myf;
    struct timeval next;
    int ms, len;
    swift_background_t tts_stream;
    int samplerate;

    swift_engine *engine = NULL;
    swift_port *port = NULL;
    swift_voice *voice;
    swift_params *params;
    swift_result_t sresult;
    unsigned int event_mask;

    struct stuff *ps;
    
    if (ast_strlen_zero(data)) {
        ast_log(LOG_WARNING, "%s requires an argument\n", swift_app);
        return -1;
    }

    ps = malloc(sizeof(struct stuff));
    swift_init_stuff(ps);

    u = ast_module_user_add(chan);

    if((engine = swift_engine_open(NULL)) == NULL) {
        ast_log(LOG_ERROR, "Failed to open Swift Engine.\n");
        goto exception;
    }

    params = swift_params_new(NULL);
    swift_params_set_string(params, "audio/encoding", "pcm16");
    if (cfg_use16khz)
    	swift_params_set_string(params, "audio/sampling-rate", "16000");
    else
    	swift_params_set_string(params, "audio/sampling-rate", "8000");
    swift_params_set_string(params, "audio/output-format", "raw");
    swift_params_set_string(params, "tts/text-encoding", "utf-8");
/*
    swift_params_set_float(params, "speech/pitch/shift", 1.0);
    swift_params_set_int(params, "speech/rate", 150);
    swift_params_set_int(params, "audio/volume", 110);
    swift_params_set_int(params, "audio/deadair", 10);
*/

    if ((port = swift_port_open(engine, params)) == NULL) {
        ast_log(LOG_ERROR, "Failed to open Swift Port.\n");
        goto exception;
    }

    if ((voice = swift_port_set_voice_by_name(port, cfg_voice)) == NULL) {
        ast_log(LOG_ERROR, "Failed to set voice.\n");
        goto exception;
    }

    event_mask = SWIFT_EVENT_AUDIO | SWIFT_EVENT_END;
    swift_port_set_callback(port, &swift_cb, event_mask, ps);

    if(SWIFT_FAILED(swift_port_speak_text(port, data, 0, NULL, &tts_stream, NULL))) {
        ast_log(LOG_ERROR, "Failed to speak.\n");
        goto exception;
    }

    if(chan->_state!=AST_STATE_UP)
        ast_answer(chan);
    ast_stopstream(chan);

    old_writeformat = chan->writeformat;

#ifdef ASTERISK16
    if (cfg_use16khz) {
	samplerate = 16000;
	res=ast_set_write_format(chan, AST_FORMAT_SLINEAR16);
    } else
#endif
    {
	samplerate = 8000;
	res=ast_set_write_format(chan, AST_FORMAT_SLINEAR);
    }
    if (res < 0) {
        ast_log(LOG_WARNING, "Unable to set write format.\n");
        goto exception;
    }

    res = 0;
    /* Wait 50 ms first for synthesis to start
       we need to fill a frame with audio */
    next = ast_tvadd(ast_tvnow(), ast_tv(0, 50000));

    while (swift_generator_running(ps)) {
        ms = ast_tvdiff_ms(next, ast_tvnow());
        if (ms <= 0) {
            if (swift_bytes_available(ps) > 0) {
                ASTOBJ_WRLOCK(ps);
//                ast_log(LOG_DEBUG, "Queue %d bytes, writing a frame\n", ps->qc);

                len = fmin(framesize*2, ps->qc); /* 2 bytes per sample */
                availatend = cfg_buffer_size - (ps->pq_r - ps->q);
                if (len > availatend) {
//                    ast_log(LOG_DEBUG, "Fancy read; %d bytes but %d at end, %d free \n", len, availatend, cfg_buffer_size - ps->qc);

                    //read #1: to end of q buf
                    memcpy(myf.frdata, ps->pq_r, availatend);
                    ps->qc -= availatend;

                    //read #2: reset to start of q buf and get rest
                    ps->pq_r = ps->q;
                    memcpy(myf.frdata + availatend, ps->pq_r, len - availatend);
                    ps->qc -= len - availatend;
                    ps->pq_r += len - availatend;

                } else {
//                    ast_log(LOG_DEBUG, "Easy read; %d bytes and %d at end, %d free\n", len, availatend, cfg_buffer_size - ps->qc);
                    memcpy(myf.frdata, ps->pq_r, len);
                    ps->qc -= len;
                    ps->pq_r += len;
                }
                
		myf.f.frametype = AST_FRAME_VOICE;
#ifdef ASTERISK16
		if (cfg_use16khz)
#ifdef ASTERISK180
			myf.f.subclass.codec = AST_FORMAT_SLINEAR16;
#else
			myf.f.subclass = AST_FORMAT_SLINEAR16;
#endif
		else
#endif
#ifdef ASTERISK180
			myf.f.subclass.codec = AST_FORMAT_SLINEAR;
#else
			myf.f.subclass = AST_FORMAT_SLINEAR;
#endif
		myf.f.samples = len/2;
		myf.f.datalen = len;
#if defined(ASTERISK16) && !defined(ASTERISK160)
		myf.f.data.ptr = myf.frdata;
#else
		myf.f.data = myf.frdata;
#endif
		myf.f.mallocd = 0;
		myf.f.offset = AST_FRIENDLY_OFFSET;
		myf.f.src = __PRETTY_FUNCTION__;
		myf.f.delivery.tv_sec = 0;
		myf.f.delivery.tv_usec = 0;
		if(ast_write(chan, &myf.f) < 0) {
                    ast_log(LOG_DEBUG, "ast_write failed\n");
                }
//                ast_log(LOG_DEBUG, "wrote a frame of %d\n", len);
                if (ps->qc < 0)
                    ast_log(LOG_DEBUG, "queue claims to contain negative bytes. Huh? qc < 0\n");
                ASTOBJ_UNLOCK(ps);
                next = ast_tvadd(next, ast_samp2tv(myf.f.samples, samplerate));
                
            } else {
                next = ast_tvadd(next, ast_samp2tv(framesize/2, samplerate));
                ast_log(LOG_WARNING, "Whoops, writer starved for audio\n");
            }
                
        } else {
            ms = ast_waitfor(chan, ms);
            if (ms < 0) {
                ast_log(LOG_DEBUG, "Hangup detected\n");
                res = -1;
                ASTOBJ_WRLOCK(ps);
                ps->immediate_exit = 1;
                ASTOBJ_UNLOCK(ps);
                
            } else if (ms) {
                f = ast_read(chan);
                if(!f) {
                    ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
                    res = -1;
                    ASTOBJ_WRLOCK(ps);
                    ps->immediate_exit = 1;
                    ASTOBJ_UNLOCK(ps);
                    
                } else if ((dtmf_enable) && (f->frametype == AST_FRAME_DTMF)) {
#ifdef ASTERISK180
                    res = f->subclass.integer;
#else
                    res = f->subclass;
#endif
                    ASTOBJ_WRLOCK(ps);
                    ps->immediate_exit = 1;
                    ASTOBJ_UNLOCK(ps);
                    ast_frfree(f);
                } else { // ignore other frametypes
                    ast_frfree(f);
                }
            } 
        }

        ASTOBJ_RDLOCK(ps);
        if (ps->immediate_exit && !ps->generating_done) {
            if (SWIFT_FAILED(sresult = swift_port_stop(port, tts_stream, SWIFT_EVENT_NOW))) {
                ast_log(LOG_NOTICE, "Early top of swift port failed\n");
            } else {
                ast_log(LOG_DEBUG, "Early stop of swift port returned okay\n");
            }
        }
        ASTOBJ_UNLOCK(ps);
    }


exception:
    if(port) swift_port_close(port);
    if(engine) swift_engine_close(engine);

    if (ps && ps->q) {
        free(ps->q);
        ps->q = NULL;
    }
    if (ps) {
        free(ps);
        ps = NULL;
    }

    if (!res && old_writeformat)
        ast_set_write_format(chan, old_writeformat);
    
    ast_module_user_remove(u);
    return res;
}

#ifdef ASTERISK180
static int swiftbackground_exec(struct ast_channel *chan, const char *data)
#else
static int swiftbackground_exec(struct ast_channel *chan, void *data)
#endif
{
	return(swiftnow_exec(chan,data,1));
}

#ifdef ASTERISK180
static int swift_exec(struct ast_channel *chan, const char *data)
#else
static int swift_exec(struct ast_channel *chan, void *data)
#endif
{
	return(swiftnow_exec(chan,data,0));
}


static int update_wav_header(FILE *f)
{
	off_t cur,end;
	int datalen,filelen,bytes;
	
	cur = ftello(f);
	fseek(f, 0, SEEK_END);
	end = ftello(f);
	/* data starts 44 bytes in */
	bytes = end - 44;
	datalen = htoll(bytes);
	/* chunk size is bytes of data plus 36 bytes of header */
	filelen = htoll(36 + bytes);
	
	if (cur < 0) {
		ast_log(LOG_WARNING, "Unable to find our position\n");
		return -1;
	}
	if (fseek(f, 4, SEEK_SET)) {
		ast_log(LOG_WARNING, "Unable to set our position\n");
		return -1;
	}
	if (fwrite(&filelen, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to set write file size\n");
		return -1;
	}
	if (fseek(f, 40, SEEK_SET)) {
		ast_log(LOG_WARNING, "Unable to set our position\n");
		return -1;
	}
	if (fwrite(&datalen, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to set write datalen\n");
		return -1;
	}
	if (fseeko(f, cur, SEEK_SET)) {
		ast_log(LOG_WARNING, "Unable to return to position\n");
		return -1;
	}
	return 0;
}

static int write_wav_header(FILE *f, int freq)
{
	unsigned int hz = htoll(8000);
	unsigned int bhz = htoll(16000);
	unsigned int hs = htoll(16);
	unsigned short fmt = htols(1);
	unsigned short chans = htols(1);
	unsigned short bysam = htols(2);
	unsigned short bisam = htols(16);
	unsigned int size = htoll(0);

	hz = htoll(freq);
	bhz = htoll(freq*2);

	/* Write a wav header, ignoring sizes which will be filled in later */
	fseek(f,0,SEEK_SET);
	if (fwrite("RIFF", 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&size, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite("WAVEfmt ", 1, 8, f) != 8) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&hs, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&fmt, 1, 2, f) != 2) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&chans, 1, 2, f) != 2) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&hz, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&bhz, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&bysam, 1, 2, f) != 2) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&bisam, 1, 2, f) != 2) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite("data", 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	if (fwrite(&size, 1, 4, f) != 4) {
		ast_log(LOG_WARNING, "Unable to write header\n");
		return -1;
	}
	return 0;
}

static swift_result_t write_audio(swift_event *event, swift_event_t type, void *udata)
{
	void *buf;
	int len;
	FILE *audio_out = udata;
	swift_event_t rv = SWIFT_SUCCESS;

	rv = swift_event_get_audio(event, &buf, &len);
	if (!SWIFT_FAILED(rv)) fwrite(buf, len, 1, audio_out);

	return rv;
}

#ifdef ASTERISK180
static int swiftcache_exec(struct ast_channel *chan, const char *data)
#else
static int swiftcache_exec(struct ast_channel *chan, void *data)
#endif
{
	int res = -1;
	struct ast_module_user *u;

	swift_engine *engine = NULL;
	swift_port *port = NULL;
	swift_voice *voice;
	swift_params *params;
	unsigned int event_mask;

	FILE *fd;
	char *text,*cache_file;
	char mdx[33]; /* assumed to be 32+NULL */
	int ret;
	struct stat statbuf;
	int freq=8000;
	int doheader=0;

	char *known_ext[]={"wav","wav16","ulaw","alaw","sln","sln16",NULL};
	int x;

	text=NULL;
	cache_file=NULL;
	port=NULL;
	engine=NULL;
    

	/* assume that the data is the text only */
	if (ast_strlen_zero(data)) {
		ast_log(LOG_WARNING, "%s requires an argument\n", swiftcache_app);
		return(-1);
	}

	u = ast_module_user_add(chan);

	/* Generate MD5 of voice and text for cache filename */
	/* cfg_voice : data + NULL */
	if (!(text=malloc(strlen(cfg_voice)+strlen(data)+2))) {
		ast_log(LOG_ERROR, "Out of memory\n");
		goto all_done;
	}
	sprintf(text,"%s:%s",cfg_voice,(char *)data);
	ast_md5_hash(mdx, text);
	sprintf(text,"%s",(char *)data);

	/* config dir len + / + 32 char HEX + .sln16 + NULL */
	if (!(cache_file=malloc(strlen(cfg_dir)+42))) {
		ast_log(LOG_ERROR, "Out of memory\n");
		goto all_done;
	}

	/* clear old SWIFTCACHE_FILE if it was set from before -- in case we fail */
	pbx_builtin_setvar_helper(chan, "SWIFTCACHE_FILE", NULL);

	/* build filename for output */
	if (!strcasecmp(cfg_format, "ulaw"))
		sprintf(cache_file,"%s/%s.ulaw",cfg_dir,mdx);
	else if (!strcasecmp(cfg_format, "alaw"))
		sprintf(cache_file,"%s/%s.alaw",cfg_dir,mdx);
	else if (!strcasecmp(cfg_format, "sln"))
		sprintf(cache_file,"%s/%s.sln",cfg_dir,mdx);
	else if (!strcasecmp(cfg_format, "sln16"))
		sprintf(cache_file,"%s/%s.sln16",cfg_dir,mdx);
	else if (!strcasecmp(cfg_format, "wav16"))
		sprintf(cache_file,"%s/%s.wav16",cfg_dir,mdx);
	else if (!strcasecmp(cfg_format, "wav"))
		sprintf(cache_file,"%s/%s.wav",cfg_dir,mdx);
	else {
		ast_log(LOG_ERROR, "Unknown format type: %s\n",cfg_format);
		res=(-1);
		goto all_done;
	}

	/* Check for file in cache directory */
	ret=stat(cache_file,&statbuf);
	if ((!ret) && (statbuf.st_size>0) && (S_ISREG(statbuf.st_mode)) && (!cfg_forcerebuild)) {
		if ((!cfg_rebuildage) || (statbuf.st_ctime+(cfg_rebuildage*3600))>=(time((time_t *)NULL))) {
			/* it's a file and has data, so it must be OK, we're done */
			/* build filename for dial plan variable - NO EXTENSION */
			sprintf(cache_file,"%s/%s",cfg_dir,mdx);
			pbx_builtin_setvar_helper(chan, "SWIFTCACHE_FILE", cache_file);
			res=0;
			goto all_done;
		}
	}
	if ((!ret) && (!(S_ISREG(statbuf.st_mode)))) {
		/* there's something in the way that's not a file */
		ast_log(LOG_ERROR, "This file is not normal %s\n",cache_file);
		goto all_done;
	}

	if ((engine = swift_engine_open(NULL)) == NULL) {
		ast_log(LOG_ERROR, "Failed to open Swift Engine.\n");
		goto all_done;
	}

	params = swift_params_new(NULL);
	swift_params_set_int(params, "tts/no-blocking", 0);
	swift_params_set_int(params, "events/immediate", 0);
	swift_params_set_string(params, "tts/text-encoding", "utf-8");

	if (!strcasecmp(cfg_format, "ulaw")) {
		swift_params_set_string(params, "audio/output-format", "raw");
		swift_params_set_string(params, "audio/encoding", "ulaw");
		swift_params_set_string(params, "audio/sampling-rate", "8000");
		freq=8000; doheader=0;
	} else if (!strcasecmp(cfg_format, "alaw")) {
		swift_params_set_string(params, "audio/output-format", "raw");
		swift_params_set_string(params, "audio/encoding", "alaw");
		swift_params_set_string(params, "audio/sampling-rate", "8000");
		freq=8000; doheader=0;
	} else if (!strcasecmp(cfg_format, "wav16")) {
		swift_params_set_string(params, "audio/output-format", "riff");
		swift_params_set_string(params, "audio/encoding", "pcm16");
		swift_params_set_string(params, "audio/sampling-rate", "16000");
		freq=16000; doheader=1;
	} else if (!strcasecmp(cfg_format, "sln16")) {
		swift_params_set_string(params, "audio/output-format", "raw");
		swift_params_set_string(params, "audio/encoding", "pcm16");
		swift_params_set_string(params, "audio/sampling-rate", "16000");
		freq=16000; doheader=0;
	} else if (!strcasecmp(cfg_format, "sln")) {
		swift_params_set_string(params, "audio/output-format", "raw");
		swift_params_set_string(params, "audio/encoding", "pcm16");
		swift_params_set_string(params, "audio/sampling-rate", "8000");
		freq=8000; doheader=0;
	} else if (!strcasecmp(cfg_format, "wav")) {
		swift_params_set_string(params, "audio/output-format", "riff");
		swift_params_set_string(params, "audio/encoding", "pcm16");
		swift_params_set_string(params, "audio/sampling-rate", "8000");
		freq=8000; doheader=1;
	}
	swift_params_set_string(params, "audio/channels", "1");

	/* use default speech settings */
/*
	swift_params_set_float(params, "speech/pitch/shift", 1.0);
	swift_params_set_int(params, "speech/rate", 170);
	swift_params_set_int(params, "audio/volume", 100);
	swift_params_set_int(params, "audio/deadair", 10);
*/

	if((port = swift_port_open(engine, params)) == NULL) {
		ast_log(LOG_ERROR, "Failed to open Swift Port.\n");
		goto all_done;
	}

	if ((voice = swift_port_set_voice_by_name(port, cfg_voice)) == NULL) {
		ast_log(LOG_ERROR, "Failed to set voice %s.\n", cfg_voice);
		goto all_done;
	}


	if ((fd=fopen(cache_file,"wb"))==NULL) {
		ast_log(LOG_ERROR, "Failed to open wav file for writing.\n");
		goto all_done;
	}
	if (doheader)
		if (write_wav_header(fd,freq)) {
			ast_log(LOG_ERROR, "Error writing header.\n");
			if (fd) fclose(fd);
			goto all_done;
		}

	/* Set audio_callback as a callback, with the output file as its param */
	event_mask = SWIFT_EVENT_AUDIO; /* only audio events, please */
	swift_port_set_callback(port, &write_audio, event_mask, fd);

	if (SWIFT_FAILED(swift_port_speak_text(port, text, 0, NULL, NULL, NULL)) ) {
		ast_log(LOG_ERROR, "Failed to speak and save file.\n");
		if (fd) fclose(fd);
		goto all_done;
	}

	if (doheader)
		if (update_wav_header(fd)) {
			ast_log(LOG_ERROR, "Error writing header.\n");
			if (fd) fclose(fd);
			goto all_done;
		}

	fclose(fd);


	res=0;

	/* write new info to text file */
	if (cfg_individualinfo) {
		sprintf(cache_file,"%s/%s.txt",cfg_dir,mdx);
		if ((fd=fopen(cache_file,"w"))) {
			fprintf(fd,"%s:%s:%s\n",mdx,cfg_voice,text);
			fclose(fd);
		}
	}

	/* append new info to text file */
	if (cfg_infofile[0]) {
		sprintf(cache_file,"%s/%s",cfg_dir,cfg_infofile);
		if ((fd=fopen(cache_file,"a+"))) {
			fprintf(fd,"%s:%s:%s\n",mdx,cfg_voice,text);
			fclose(fd);
		}
	}

	/* remove old files of all known types, execpt the new one */
	if (cfg_cleanup) {
		for (x=0;known_ext[x];x++) {
			if (strcasecmp(cfg_format, known_ext[x])) {
				sprintf(cache_file,"%s/%s.%s",cfg_dir,mdx,known_ext[x]);
				unlink(cache_file);
			}
		}
	}

	/* build filename for dial plan variable - NO EXTENSION */
	sprintf(cache_file,"%s/%s",cfg_dir,mdx);
	pbx_builtin_setvar_helper(chan, "SWIFTCACHE_FILE", cache_file);

all_done:
	if (port) swift_port_close(port);
	if (engine) swift_engine_close(engine);

	if (text) free(text);
	if (cache_file) free(cache_file);

	ast_module_user_remove(u);

	return(res);
}


static int load_config(int reload)
{
	const char *t = NULL;
	struct ast_config *cfg;
	struct stat statbuf;
	int ret;
#ifdef  ASTERISK16
	struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
#endif
	swift_engine *engine = NULL;
	int max_tokens;
	int tokens_in_use;

	/* tickle the engine */
	/* Open the Swift TTS Engine */
	if ( (engine = swift_engine_open(NULL)) == NULL ) {
		ast_log(LOG_NOTICE, "Failed to open Swift Engine\n");
	} else {
		swift_license_get_concurrency_info(engine,&max_tokens,&tokens_in_use);
		if (engine) swift_engine_close(engine);
	}

	/* set defaults */
	ast_copy_string(cfg_voice, DEFAULT_VOICE, sizeof(cfg_voice));
	ast_copy_string(cfg_dir, DEFAULT_DIR, sizeof(cfg_dir));
	ast_copy_string(cfg_infofile, DEFAULT_INFOFILE, sizeof(cfg_infofile));
	ast_copy_string(cfg_format, DEFAULT_FORMAT, sizeof(cfg_format));
	cfg_individualinfo = DEFAULT_INDIVIDUALINFO;
	cfg_forcerebuild = DEFAULT_FORCEREBUILD;
	cfg_cleanup = DEFAULT_CLEANUP;
	cfg_rebuildage = DEFAULT_REBUILDAGE;
	cfg_buffer_size = DEFAULT_BUFFERSIZE;
	cfg_use16khz = DEFAULT_USE16KHZ;

#ifdef  ASTERISK16
	cfg = ast_config_load(SWIFT_CONFIG_FILE, config_flags);
#else
	cfg = ast_config_load(SWIFT_CONFIG_FILE);
#endif

	if (cfg) {
		if ((t = ast_variable_retrieve(cfg, "general", "buffer_size"))) {
			cfg_buffer_size = atoi(t);
			ast_log(LOG_DEBUG, "Config buffer_size is %ld\n", cfg_buffer_size);
		}
		if ((t = ast_variable_retrieve(cfg, "general", "voice"))) {
                        ast_copy_string(cfg_voice,t,sizeof(cfg_voice));
			ast_log(LOG_DEBUG, "Config voice is %s\n", cfg_voice);
		}
                if ((t = ast_variable_retrieve(cfg, "general", "use_16khz"))) {
                        cfg_use16khz=ast_true(t);
			ast_log(LOG_DEBUG, "Config use 16Khz is %d\n", cfg_use16khz);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "general_infofile"))) {
                        ast_copy_string(cfg_infofile,t,sizeof(cfg_infofile));
			ast_log(LOG_DEBUG, "Config cache general_infofile is %s\n", cfg_infofile);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "audio_format"))) {
                        ast_copy_string(cfg_format,t,sizeof(cfg_format));
			ast_log(LOG_DEBUG, "Config cache audio_format is %s\n", cfg_format);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "directory"))) {
                        ast_copy_string(cfg_dir,t,sizeof(cfg_dir));
			ast_log(LOG_DEBUG, "Config cache dir is %s\n", cfg_dir);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "individual_infofile"))) {
                        cfg_individualinfo=ast_true(t);
			ast_log(LOG_DEBUG, "Config cache individual_infofile is %d\n", cfg_individualinfo);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "force_rebuild"))) {
                        cfg_forcerebuild=ast_true(t);
			ast_log(LOG_DEBUG, "Config cache force_rebuild is %d\n", cfg_forcerebuild);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "cleanup_files"))) {
                        cfg_cleanup=ast_true(t);
			ast_log(LOG_DEBUG, "Config cache cleanup files is %d\n", cfg_cleanup);
		}
                if ((t = ast_variable_retrieve(cfg, "cache", "auto_rebuild"))) {
                        cfg_rebuildage=atol(t);
			ast_log(LOG_DEBUG, "Config cache rebuild_age is %ld\n", (long)cfg_rebuildage);
		}

		ast_config_destroy(cfg);

	} else {
		ast_log(LOG_NOTICE, "Failed to load config\n");
	}

#ifndef  ASTERISK16
	cfg_use16khz=0;
#endif

	if (cfg_buffer_size < 16000)  cfg_buffer_size =  16000;
	if (cfg_buffer_size > 1024000) cfg_buffer_size = 1024000;
	if ((cfg_buffer_size < 32000) && (cfg_use16khz))
		cfg_buffer_size =  32000;

	if (cfg_use16khz)
		framesize=DEFAULT_FRAMESIZE*2;
	else	framesize=DEFAULT_FRAMESIZE;

	/* check for existance of directory */
	ret=stat(cfg_dir,&statbuf);
	if ((ret) || !(S_ISDIR(statbuf.st_mode))) {
		ast_log(LOG_ERROR, "Problem with cache directory %s\n",cfg_dir);
		ast_copy_string(cfg_dir,"/tmp",sizeof(cfg_dir));
	}

	return(0);
}

static int reload(void)
{
	ast_log(LOG_NOTICE, "Reloading %s\n", SWIFT_CONFIG_FILE);
	return(load_config(1));
}


static int unload_module(void)
{
	int res;

	res = ast_unregister_application(swift_app);
	res |= ast_unregister_application(swiftbackground_app);
	res |= ast_unregister_application(swiftcache_app);

	ast_cli_unregister_multiple(cli_swift, sizeof(cli_swift) / sizeof(struct ast_cli_entry));

	return res;
}

static int load_module(void)
{
	int res;

	res = ast_register_application(swift_app, swift_exec, swift_synopsis, swift_descrip);
	res |= ast_register_application(swiftbackground_app, swiftbackground_exec, swiftbackground_synopsis, swiftbackground_descrip);
	res |= ast_register_application(swiftcache_app, swiftcache_exec, swiftcache_synopsis, swiftcache_descrip);

	/* load/reload the config file */
	if (!res) res=load_config(0);

	ast_cli_register_multiple(cli_swift, sizeof(cli_swift) / sizeof(struct ast_cli_entry));

	return res;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Cepstral Swift TTS Application",
		.load = load_module,
		.unload = unload_module,
		.reload = reload,
		);

