/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2024 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

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

#include "log.h"
#include "parse.h"

typedef enum {
	PARM_PATH,
	PARM_PUNCTLIST,
	PARM_VOICE,
	PARM_MAXRATE
} DriverParameter;
#define SPKPARMS "path", "punctlist", "voice", "maxrate"

#include "spk_driver.h"

#include <espeak-ng/speak_lib.h>

static int maxrate = espeakRATE_MAXIMUM;

static void
spk_say(SpeechSynthesizer *spk, const unsigned char *buffer, size_t length, size_t count, const unsigned char *attributes)
{
	int result;

	/* add 1 to the length in order to pass along the trailing zero */
	result = espeak_Synth(buffer, length+1, 0, POS_CHARACTER, 0,
			espeakCHARS_UTF8, NULL, (void *)spk);
	if (result != EE_OK)
		logMessage(LOG_ERR, "eSpeak-NG: Synth() returned error %d", result);
}

static void
spk_mute(SpeechSynthesizer *spk)
{
	espeak_Cancel();
}

static int SynthCallback(short *audio, int numsamples, espeak_EVENT *events)
{
	SpeechSynthesizer *spk = events->user_data;

	while (events->type != espeakEVENT_LIST_TERMINATED) {
		if (events->type == espeakEVENT_WORD)
			tellSpeechLocation(spk, events->text_position - 1);
		if (events->type == espeakEVENT_MSG_TERMINATED)
			tellSpeechFinished(spk);
		events++;
	}
	return 0;
}

static void
spk_drain(SpeechSynthesizer *spk)
{
	espeak_Synchronize();
}

static void
spk_setVolume(SpeechSynthesizer *spk, unsigned char setting)
{
	int volume = getIntegerSpeechVolume(setting, 50);
	espeak_SetParameter(espeakVOLUME, volume, 0);
}

static void
spk_setRate(SpeechSynthesizer *spk, unsigned char setting)
{
	int h_range = (maxrate - espeakRATE_MINIMUM)/2;
	int rate = getIntegerSpeechRate(setting, h_range) + espeakRATE_MINIMUM;
	espeak_SetParameter(espeakRATE, rate, 0);
}

static void
spk_setPitch(SpeechSynthesizer *spk, unsigned char setting)
{
	int pitch = getIntegerSpeechPitch(setting, 50);
	espeak_SetParameter(espeakPITCH, pitch, 0);
}

static void
spk_setPunctuation(SpeechSynthesizer *spk, SpeechPunctuation setting)
{
	espeak_PUNCT_TYPE punct;
	if (setting <= SPK_PUNCTUATION_NONE)
		punct = espeakPUNCT_NONE;
	else if (setting >= SPK_PUNCTUATION_ALL)
		punct = espeakPUNCT_ALL;
	else
		punct = espeakPUNCT_SOME;
	espeak_SetParameter(espeakPUNCTUATION, punct, 0);
}

static int spk_construct(SpeechSynthesizer *spk, char **parameters)
{
	const char *data_path, *voicename, *punctlist;
	int result;

	spk->setVolume = spk_setVolume;
	spk->setRate = spk_setRate;
	spk->setPitch = spk_setPitch;
	spk->setPunctuation = spk_setPunctuation;
	spk->drain = spk_drain;

	logMessage(LOG_INFO, "eSpeak-NG version %s", espeak_Info(NULL));

	data_path = parameters[PARM_PATH];
	if (data_path && !*data_path)
		data_path = NULL;
	result = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0);
	if (result < 0) {
		logMessage(LOG_ERR, "eSpeak-NG: initialization failed");
		return 0;
	}

	voicename = parameters[PARM_VOICE];
	if(!voicename || !*voicename)
		voicename = "en";
	result = espeak_SetVoiceByName(voicename);
	if (result != EE_OK) {
		espeak_VOICE voice_select;
		memset(&voice_select, 0, sizeof(voice_select));
		voice_select.languages = voicename;
		result = espeak_SetVoiceByProperties(&voice_select);
	}
	if (result != EE_OK) {
		logMessage(LOG_ERR, "eSpeak-NG: unable to load voice '%s'", voicename);
		return 0;
	}

	punctlist = parameters[PARM_PUNCTLIST];
	if (punctlist && *punctlist) {
		wchar_t w_punctlist[strlen(punctlist) + 1];
		int i = 0;
		while ((w_punctlist[i] = punctlist[i]) != 0) i++;
		espeak_SetPunctuationList(w_punctlist);
	}

	if (parameters[PARM_MAXRATE]) {
		int val = atoi(parameters[PARM_MAXRATE]);
		if (val > espeakRATE_MINIMUM) maxrate = val;
	}

	espeak_SetSynthCallback(SynthCallback);

	return 1;
}

static void spk_destruct(SpeechSynthesizer *spk)
{
	espeak_Cancel();
	espeak_Terminate();
}
