/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <string.h>
#include <stdlib.h>
#include <wwdebug.h>
#include "Viseme.h"

#define IS_VOWEL(x)			( x && (x=='a' || x=='e' || x=='i' || x=='o' || x=='u') )
#define IS_CONSONANT(x)		( x && !IS_VOWEL(x) )

struct VisemeTableItem
{
	char	*LetterCombination;
	char	Visemes[2];
};

static VisemeTableItem gsVisemeTable[] =
{
	{"air",		VISEME_IF, -1},
	{"ar",		VISEME_OX, -1},
	{"ch",		VISEME_CHURCH, -1},
	{"ea",		VISEME_EAT, -1},
	{"ee",		VISEME_EAT, -1},
	{"er",		VISEME_EARTH, -1},
	{"oa",		VISEME_OX, -1},
	{"oo",		VISEME_WET, -1},
	{"ou",		VISEME_OX, VISEME_WET},
	{"ow",		VISEME_WET},
	{"qu",		VISEME_WET, -1},
	{"sch",		VISEME_SIZE,	VISEME_CAGE},
	{"sh",		VISEME_CHURCH, -1},
	{"tch",		VISEME_CHURCH, -1},
	{"th",		VISEME_THOUGH, -1},
	{"gn",		VISEME_NEW, -1},
	{"kn",		VISEME_NEW, -1},
	{"eye",		VISEME_IF, -1},
	{"uy",		VISEME_IF, -1},
	{"ar",		VISEME_OX, -1},
	{"kw",		VISEME_WET, -1},
	{"and",		VISEME_NEW, VISEME_TOLD},
	{"ze",		VISEME_ROAR, VISEME_EAT},		// numbers: zero to nine
	{"ro",		VISEME_WET, VISEME_OX},
	{"one",		VISEME_WET, VISEME_CAT},		
	{"two",		VISEME_EAT, VISEME_WET},
	{"thr",		VISEME_THOUGH, VISEME_ROAR},
	{"fou",		VISEME_FAVE, VISEME_OX},
	{"ive",		VISEME_IF, VISEME_FAVE},
	{"six",		VISEME_UP, VISEME_IF},
	{"se",		VISEME_UP, VISEME_EAT},
	{"ven",		VISEME_FAVE, VISEME_EAT},
	{"eight",	VISEME_CAT, VISEME_EAT},
	{"ni",		VISEME_THOUGH, VISEME_IF},
	{"ne",		VISEME_THOUGH, -1},				// end numbers
	
};

struct VisemeTableReferenceItem
{
	int			StartIndex;
	int			Count;
};

#define NUM_VISEME_REFERENCES	26								// 'a' - 'z'
static VisemeTableReferenceItem VisemeReferenceTable[NUM_VISEME_REFERENCES];

//======================================================================================
static int CompareVisemeTableItems( const void *arg1, const void *arg2 )
{
	VisemeTableItem *p1 = (VisemeTableItem *)arg1;
	VisemeTableItem *p2 = (VisemeTableItem *)arg2;

	return strcmp(p1->LetterCombination, p2->LetterCombination);
}

//======================================================================================
VisemeManager::VisemeManager(void)
{
	// sort viseme lookup table
	int numVisemeTableItems = sizeof(gsVisemeTable) / sizeof(VisemeTableItem);
	qsort(gsVisemeTable, numVisemeTableItems, sizeof(VisemeTableItem), CompareVisemeTableItems);

	// build viseme lookup reference table
	memset(VisemeReferenceTable, 0, sizeof(VisemeReferenceTable));
	VisemeTableItem *pItem = gsVisemeTable;

	for (int i=0; i<numVisemeTableItems; i++,pItem++) {
		int index = (int)tolower(pItem->LetterCombination[0]) - 'a';

		if ( index >= 0 && index < NUM_VISEME_REFERENCES ) {
			VisemeTableReferenceItem *pR = &VisemeReferenceTable[index];
			if ( pR->Count == 0 ) {
				pR->StartIndex = i;
			}
			pR->Count++;
		}
	}
}

//======================================================================================
// GetVisemes(const char *word, int *visemelist, int maxvisemes)
// Input:	word		- word to parse
//			maxvisemes	- max. number of visemes on the output list visemelist
// Output:	visemelist	- contains viseme ID's
// Remark:	this routine examines word and identifies visemes and place them 
//			on the given output list(visemelist)
// Return:	number of visemes on the output list
//
int VisemeManager::Get_Visemes(const char *word, int *visemelist, int maxvisemes) const
{
	char prevchar = 0;
	int last_viseme = -1;
	int viseme[2];
	int i;
	int offset;
	int num_visemes = 0;
	char local_buf[128];

	// make a local copy of the word in lower case
	strncpy(local_buf, word, sizeof(local_buf)-1);
	local_buf[sizeof(local_buf)-1] = 0;
	_strlwr(local_buf);

	const char *pchar = local_buf;
	while ( *pchar ) {
		// check for tabled viseme combinations
		offset = Lookup(pchar, word, viseme);
		
		if ( offset == 0 ) {
			// analyse the current letter

			// init default viseme ID
			viseme[0] = VISEME_TOLD;
			viseme[1] = -1;
			offset = 1;

			switch (*pchar) {
			case 'a':
				offset = Do_Letter_a(pchar, prevchar, viseme);
				break;
			case 'b':
				viseme[0] = VISEME_BUMP;
				break;
			case 'c':
				viseme[0] = VISEME_CAGE;
				break;
			case 'd':
				viseme[0] = VISEME_TOLD;
				break;
			case 'e':
				offset = Do_Letter_e(pchar, prevchar, viseme);
				break;
			case 'f':
				viseme[0] = VISEME_FAVE;
				break;
			case 'g':
				viseme[0] = VISEME_CAGE;
				break;
			case 'h':
				// no sound
				break;
			case 'i':
				offset = Do_Letter_i(pchar, prevchar, viseme);
				break;
			case 'j':
				viseme[0] = VISEME_CHURCH;
				break;
			case 'k':
				viseme[0] = VISEME_CAGE;
				break;
			case 'l':
				viseme[0] = VISEME_THOUGH;
				break;
			case 'm':
				viseme[0] = VISEME_BUMP;
				break;
			case 'n':
				viseme[0] = VISEME_NEW;
				break;
			case 'o':
				offset = Do_Letter_o(pchar, prevchar, viseme);
				break;
			case 'p':
				viseme[0] = VISEME_BUMP;
				break;
			case 'q':
				viseme[0] = VISEME_CAGE;
				break;
			case 'r':
				viseme[0] = VISEME_ROAR;
				break;
			case 's':
				offset = Do_Letter_s(pchar, prevchar, viseme);
				break;
			case 't':
				offset = Do_Letter_t(pchar, prevchar, viseme);
				break;
			case 'u':
				offset = Do_Letter_u(pchar, prevchar, viseme);
				break;
			case 'v':
				viseme[0] = VISEME_FAVE;
				break;
			case 'w':
				viseme[0] = VISEME_WET;
				break;
			case 'x':
				viseme[0] = VISEME_CAGE;
				viseme[1] = VISEME_SIZE;
				break;
			case 'y':
				// geo-aug22/00 no viseme reference
				//viseme[0] = VISEME_WET;
				break;
			case 'z':
				viseme[0] = VISEME_SIZE;
				break;
			}
		}

		// add the newly found visemes to the list
		for (i=0; i<2; i++) {
			if ( viseme[i] >= 0 && viseme[i] != last_viseme ) {
				*visemelist++ = viseme[i];
				last_viseme = viseme[i];

				// check for list limitation
				if ( ++num_visemes >= maxvisemes ) {
					// limit reached
					return(num_visemes);
				}
			}
		}

		// next letter
		pchar += offset;
		prevchar = *(pchar - 1);
	}

	return(num_visemes);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Lookup(const char *pchar, const char * /*word*/, int viseme[]) const
{
	int length = 0;

	char ch = (char)tolower(*pchar);
	int index = ch - 'a';

	if ( index < 0 || index >= NUM_VISEME_REFERENCES )	{
		// out of range
		return 0;
	}

	VisemeTableReferenceItem *pR = &VisemeReferenceTable[index];
	// we search backwards so that we can find the max. match first
	VisemeTableItem *pI = &gsVisemeTable[pR->StartIndex + pR->Count - 1];
	for (int i=0; i<pR->Count; i++,pI--) {
		length = strlen(pI->LetterCombination);
		if ( strnicmp(pchar, pI->LetterCombination, length) == 0 )
		{
			// found!
			viseme[0] = pI->Visemes[0];
			viseme[1] = pI->Visemes[1];
			return length;
		}
	}

	return 0;
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_a(const char *pchar, char prevchar, int *viseme) const
{
	int offset = 1;								// default

	// default as in "a" or "about"
	viseme[0] = VISEME_TOLD;
	viseme[1] = -1;

	if ( IS_VOWEL(pchar[1]) )	{
		offset++;
		switch (pchar[1])	{
		case 'i':	// ai
			viseme[1] = VISEME_EAT;			// default: e.g. plaid, aim, maize, train
			if ( pchar[2] == 's' ) {
				// as in "aisle"
				viseme[0] = VISEME_CAT;
			}
			break;
		case 'o':	// ao
			// as in "gaol"
			viseme[1] = VISEME_EAT;
			break;
		case 'u':	// au
			switch (prevchar)	{
			case 'g':	// as in "gauge"
				viseme[1] = VISEME_EAT;
				break;
			case 'l':	// as in "laugh"
				viseme[0] = VISEME_CAT;
				break;
			case 's':	// as in "sauerkraut"
				viseme[0] = VISEME_CAT;
				viseme[1] = VISEME_WET;
				break;
			default:	// as in "daughter"
				viseme[0] = VISEME_CAT;
			}
			break;
		}
	}
	else	{
		if ( IS_VOWEL(pchar[2]) ) {
			// e.g. "ate", 12+2
			viseme[1] = VISEME_EAT;
		}
		else {
			// e.g. "add", 1
			viseme[0] = VISEME_CAT;
		}
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_e(const char *pchar, char prevchar, int *viseme) const
{
	int offset = 1;

	// default as in "e" or "be"
	viseme[0] = VISEME_EAT;						// default, as in "beach"
	viseme[1] = -1;

	if ( IS_VOWEL(pchar[1]) ) {
		offset++;
		//viseme[0] = VISEME_EAT;					// default, as in "beach"

		switch ( pchar[1] ) {
		case 'a':	// ea
			if ( pchar[2] == 'u' ) {
				// as in "beauty"
				viseme[0] = VISEME_EAT;
				viseme[1] = VISEME_WET;
				offset++;
			}
			break;
		case 'i':	// ei
			if ( prevchar == 'h' ) {
				// as in "height"
				viseme[0] = VISEME_CAT;
				viseme[1] = VISEME_EAT;
			}
			else if ( pchar[2] == 'g' ) {
				// as in "eight", "reign"
				viseme[0] = VISEME_TOLD;
				viseme[1] = VISEME_EAT;
			}
			else if ( pchar[2] == 'z' ) {
				// as in "seize"
				viseme[0] = VISEME_EAT;
			}
			else {// plain old "ei"
				// default:
				viseme[0] = VISEME_IF;
			}
			break;
		case 'o':	// eo
			if ( prevchar == 'p' ) {
				// as in "people"
				viseme[0] = VISEME_EAT;
			}
			else {
				// default:
				viseme[0] = VISEME_TOLD;
			}
			break;
		case 'u':	// eu
			// as in "maneuver", "eulogy", "queue"
			viseme[0] = VISEME_WET;
			break;
		}
	}
	else {
		if ( prevchar == 'b' || pchar[1] == 0 ) {
			// as in "be"
			viseme[0] = VISEME_EAT;
		}
		else {
			// default as in "pet",
			viseme[0] = VISEME_WET;
		}
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_i(const char *pchar, char prevchar, int *viseme) const
{
	int offset = 1;

	// default as in "I"
	viseme[0] = VISEME_IF;
	viseme[1] = -1;

	if ( IS_VOWEL(pchar[1]) ) {
		offset++;
		viseme[0] = VISEME_TOLD;					// default,
		viseme[1] = -1;

		switch ( pchar[1] ) {
		case 'e':	// ie
			if ( pchar[2] == 0 && prevchar == 'l' ) {
				// as in "lie"
				viseme[0] = VISEME_CAT;
				viseme[1] = VISEME_EAT;
			}
			else if ( pchar[2] == 'u' || pchar[2] == 'w' ) {
				viseme[0] = VISEME_WET;			// as in lieutenant or view
				offset++;
			}
			else {
				viseme[0] = VISEME_EAT;			// as in grief
			}
			break;
		case 'o':	// io
			viseme[0] = VISEME_EAT;				// as in onion
			break;
		}
	}
	else {
		viseme[0] = VISEME_IF;					// as in "pig"
		viseme[1] = -1;
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_o(const char *pchar, char /*prevchar*/, int *viseme) const
{
	int offset = 1;

	// default as in "no"
	viseme[0] = VISEME_OX;
	viseme[1] = -1;
	// geo-modified aug21/00
	//viseme[1] = VISEME_WET;

	if ( IS_VOWEL(pchar[1]) ) {
		offset++;
		viseme[0] = VISEME_UP;					// default, as in "took"
		viseme[1] = -1;

		switch ( pchar[1] ) {
		case 'a':	// oa
			if ( pchar[-2] == 'b' && pchar[-1] == 'r' ) {
				viseme[0] = VISEME_UP;				// as in "broad"
			}
			else {
				viseme[0] = VISEME_OX;				// as in "loan"
				viseme[1] = VISEME_WET;
			}
			break;
		case 'e':	// oe
			viseme[0] = VISEME_WET;					// default as in "canoe", "foe"
			if ( pchar[-1] == 'd' && pchar[2] == 's' ) {
				viseme[0] = VISEME_UP;				// as in "does"
			}
			else if ( pchar[-2] == 'p' && pchar[-1] == 'h' ) {
				viseme[0] = VISEME_EAT;				// as in "phoenix"
			}
			break;
		case 'i':	// oi
			viseme[0] = VISEME_UP;					// as in "noise"
			viseme[1] = VISEME_EAT;
			break;
		case 'o':	// oo
			// GEO-modified aug21/00
			viseme[0] = VISEME_WET;					// default as in "took", "look", "book"
			break;
		case 'u':	// ou
			viseme[0] = VISEME_WET;					// default as in "through", "though","croup"

			if ( pchar[-2] == 't' && pchar[-1] == 'r' ) {
				// as in "trouble", "trough"
				viseme[0] = VISEME_UP;
			}
			else if ( pchar[-2] == 't' && pchar[-1] == 'h' ) {
				if ( strncmp(&pchar[2], "ght", 3) == 0 ) {
					viseme[0] = VISEME_UP;			// as in "thought"
					offset += 3;
				}
			}
			else if ( pchar[2] == 'r' ) {
				viseme[0] = VISEME_TOLD;				// as in "journey"
				offset++;
			}
			break;
		}
	}
	else if ( pchar[1] ) {
		if ( IS_VOWEL(pchar[2]) ) {
			viseme[0] = VISEME_WET;					// as in "move"
			viseme[1] = -1;
		}
		else {
			viseme[0] = VISEME_OX;					// as in "comment"
			viseme[1] = -1;
		}
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_s(const char *pchar, char /*prevchar*/, int *viseme) const
{
	int offset = 1;

	// default as in "Say", "yourS"
	//viseme[0] = VISEME_TOLD;
	viseme[0] = VISEME_SIZE;
	viseme[1] = -1;

	// special cases
	if ( pchar[1] == 'h' || pchar[1] == 'u' )	{
		viseme[0] = VISEME_CHURCH;			// e.g. "ship", "sure", "shoot"
	}
	else if ( pchar[1] == 'c' ) {
		if ( pchar[2] == 'h' ) {
			viseme[0] = VISEME_CHURCH;		// e.g. "school"
			viseme[1] = VISEME_TOLD;
			offset = 3;
		}
		else {
			viseme[0] = VISEME_TOLD;		// e.g. "scent"
			offset = 2;
		}
	}
	else if ( strncmp(&pchar[1], "eou", 3) == 0 ) {
		viseme[0] = VISEME_CHURCH;			// e.g. "nauseous"
		offset = 4;
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_t(const char *pchar, char /*prevchar*/, int *viseme) const
{
	int offset = 1;

	// default as in "t"
	viseme[0] = VISEME_TOLD;

	// special cases
	if ( pchar[1] == 'c' ) {
		if ( pchar[2] == 'h' ) {
			viseme[0] = VISEME_CHURCH;		// e.g. "catch"
			offset = 3;
		}
	}
	else if ( pchar[1] == 'h' ) {
		viseme[0] = VISEME_THOUGH;		// e.g. "this","thin", "through", "then" 
		offset = 2;						// except for "thomas"
	}

	return(offset);
}

//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_u(const char *pchar, char /*prevchar*/, int *viseme) const
{
	int offset = 1;

	// default as in "u" or "use"
	//viseme[0] = VISEME_EAT;
	//viseme[1] = VISEME_WET;
	// geo-modified aug21/00
	viseme[0] = VISEME_UP;

	if ( IS_VOWEL(pchar[1]) ) {
		offset++;

		switch ( pchar[1] ) {
		case 'e':	// ue
		case 'i':	// ui
			viseme[0] = VISEME_WET;			// as in "blue" or "fruit"
			viseme[1] = -1;
			break;
		}
	}
	else {
		if ( IS_CONSONANT(pchar[2]) )	{
			viseme[0] = VISEME_UP;			// as in "utter"
			viseme[1] = -1;
		}
	}

	return(offset);
}

//======================================================================================
//======================================================================================
//======================================================================================
