/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	7DRL Development
 *
 * NAME:        msg.cpp ( Live once Library, C++ )
 *
 * COMMENTS:
 */

#include "msg.h"

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

#ifdef WIN32
#include <sapi.h>
#include <string.h>
extern ISpVoice *glbVoice;
// I really hate windows.
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif

#endif

#include "panel.h"
#include "grammar.h"
#include "mob.h"
#include "item.h"
#include "buf.h"

#include "thread.h"

#include "config.h"

PANEL *glbPanel = 0;
LOCK	glbMsgLock;

ATOMIC_INT32	glbMsgDisable;

bool glbBlankNewTurn = false;
bool glbSuppressPreempt = false;
BUF  glbAwaitingTTS;

int
msg_enable()
{
    return glbMsgDisable.add(-1);
}

int
msg_disable()
{
    return glbMsgDisable.add(1);
}

bool
msg_isdisabled()
{
    return glbMsgDisable ? true : false;
}

void
msg_speak(const char *buf, bool preempt = true)
{
    if (glbMsgDisable)
	return;

#ifdef WIN32
    if (!glbVoice || !buf || !*buf)
        return;
    wstring	text;
    string ntext;
    ntext.assign(buf);
    text.assign(ntext.begin(), ntext.end());

    int flags = SPF_ASYNC | SPF_PERSIST_XML;
    if (preempt)
        flags |= SPF_PURGEBEFORESPEAK;
    flags |= SPF_IS_XML;

    glbVoice->Speak(text.data(), flags, nullptr);
#endif
}

void
msg_speak(BUF buf, bool preempt = true) 
{ msg_speak(buf.buffer(), preempt); }


#define MSG_HISTORY 100
BUF glbMsgStack[MSG_HISTORY];
int glbMsgTop = 0;
int glbMsgCur = -1;

void
msg_replay_message()
{
    AUTOLOCK	a(glbMsgLock);
    msg_speak(glbMsgStack[glbMsgCur]);
}

void
msg_play_previous_message()
{
    AUTOLOCK	a(glbMsgLock);
    glbMsgCur--;
    if (glbMsgCur < 0)
    {
        glbMsgCur = 0;
        msg_speak("At latest saved messsage.");
    }
    else
    {
        msg_replay_message();
    }
}

void
msg_play_next_message()
{
    AUTOLOCK	a(glbMsgLock);
    glbMsgCur++;
    if (glbMsgCur >= glbMsgTop)
    {
        glbMsgCur = glbMsgTop-1;
        msg_speak("At most recent messsage.");
    }
    else
    {
        msg_replay_message();
    }
}

void
msg_post_message(BUF msg)
{
    AUTOLOCK	a(glbMsgLock);
    if (glbMsgTop >= MSG_HISTORY)
    {
        for (int i = 1; i < MSG_HISTORY; i++)
        {
            glbMsgStack[i-1] = glbMsgStack[i];
        }
        glbMsgTop = MSG_HISTORY-1;
    }
    glbMsgStack[glbMsgTop] = msg;
    glbMsgCur = glbMsgTop;
    glbMsgTop++;
}

void
msg_preempt_suppress()
{
    glbSuppressPreempt = true;
}

void
msg_update()
{
    glbPanel->redraw();
}

void 
msg_registerpanel(PANEL *panel)
{
    glbPanel = panel;
    glbPanel->setWaitForKey(false);
}

int
msg_gethistoryline()
{
    return glbPanel->getHistoryLine();
}

void
msg_clearhistory()
{
    glbPanel->clearHistory();
}

void
msg_scrolltohistory(int line)
{
    AUTOLOCK	a(glbMsgLock);
    glbPanel->scrollToHistoryLine(line);

    // This is a hack for save scummer.  We know our last save point
    // was a > prompt, so we need to restore said prompt.
    glbPanel->setCurrentLine("> ");
    glbBlankNewTurn = true;
}

void
msg_report(const char *msg)
{
    if (glbMsgDisable)
	return;

    AUTOLOCK	a(glbMsgLock);
    BUF		buf;
    buf.reference(msg);

    if (!msg || !*msg)
    {
	// Not an actual message, so don't append and don't
	// trigger a new turn
	return;
    }

    // Don't want to have to worry about appending all the time!
    if (gram_isendsentence(buf.lastchar()))
	buf.strcat("  ");

    // Echo to the terminal
    if (glbConfig->myConsoleConnect)
    {
        std::cout << buf.buffer() << endl;
    }

#ifdef WIN32
    // Speak it.
    if (glbVoice && glbConfig->myTTSEnable)
    {
	BUF	markedup;
	markedup.strcat(buf);
	// Collapse all spaces
	while (markedup.replaceAll("  ", " "));

	// XML correction:
	markedup.replaceAll("&", "&amp;");
	markedup.replaceAll("<", "&lt;");
	markedup.replaceAll(">", "&gt;");
	// Spell out isolated punctiontion
	markedup.replaceAll("[", "<spell>");
	markedup.replaceAll("]", "</spell>");

	// Pronounceation fixes
	markedup.replaceAll("Lait", "Late");  // Yes I am this vain!
	markedup.replaceAll("tridude", "try-dude");
	markedup.replaceAll("Tridude", "try-dude");
	markedup.replaceAllWord("bow", "bo");
	markedup.replaceAllWord("Bow", "Bo");

	// Hard code silences.  Grab pending space to not catch [] embedded
	// literals.
	BUF	phrasedelay, sentencedelay, paragraphdelay;
	phrasedelay.sprintf("<silence msec=\"%d\"/>", glbConfig->myTTSPhraseDelay);
	sentencedelay.sprintf("<silence msec=\"%d\"/>", glbConfig->myTTSSentenceDelay);
	paragraphdelay.sprintf("<silence msec=\"%d\"/>", glbConfig->myTTSParagraphDelay);

	markedup.replaceAll(".", sentencedelay.buffer());
	markedup.replaceAll("! ", sentencedelay.buffer());
	markedup.replaceAll("? ", sentencedelay.buffer());
	markedup.replaceAll(",", phrasedelay.buffer());
	markedup.replaceAll("\n", paragraphdelay.buffer());

	glbAwaitingTTS.strcat(markedup);
    }
#endif

    
    glbPanel->appendText(buf);
    glbBlankNewTurn = false;
}

void
msg_getString(const char *prompt, char *buf, int len)
{
    glbPanel->getString(prompt, buf, len);
}

void
msg_quote(const char *msg)
{
    if (glbMsgDisable)
	return;

    AUTOLOCK	a(glbMsgLock);
    glbPanel->setIndent(1);
    glbPanel->newLine();
    msg_report(msg);
    glbPanel->setIndent(0);
}

void
msg_newturn()
{
    if (glbMsgDisable)
	return;

    AUTOLOCK	a(glbMsgLock);
    if (!glbBlankNewTurn)
    {
	glbBlankNewTurn = true;
	// We do not want double new lines!
	if (!glbPanel->atNewLine())
	    glbPanel->newLine();

	glbPanel->appendText("> ");

	if (glbAwaitingTTS.isstring())
	{
            msg_speak(glbAwaitingTTS, !glbSuppressPreempt);
            msg_post_message(glbAwaitingTTS);
	    glbSuppressPreempt = false;
	    glbAwaitingTTS.clear();
	}
    }
}

//
// Builder functions that respect the triple possibilities.
//
VERB_PERSON
msg_getPerson(const MOB *m, const ITEM *i, const char *s)
{
    VERB_PERSON		person = VERB_IT;

    if (m)
    {
	person = m->getPerson();
    }
    else if (i)
    {
	person = i->getPerson();
    }
    else if (s)
    {
	person = VERB_IT;
	if (gram_isnameplural(s))
	    person = VERB_THEY;
    }

    return person;
}


BUF
msg_buildVerb(const char *verb, const MOB *m_subject, const ITEM *i_subject, const char *s_subject, bool pasttense = false)
{
    VERB_PERSON		person;

    person = msg_getPerson(m_subject, i_subject, s_subject);

    return gram_conjugate(verb, person, pasttense);
}

BUF
msg_buildReflexive(const MOB *m, const ITEM *i, const char *s)
{
    VERB_PERSON		p;
    BUF			buf;

    p = msg_getPerson(m, i, s);

    buf.reference(gram_getreflexive(p));
    return buf;
}

BUF
msg_buildPossessive(const MOB *m, const ITEM *i, const char *s)
{
    VERB_PERSON		p;
    BUF			buf;

    p = msg_getPerson(m, i, s);

    buf.reference(gram_getpossessive(p));
    return buf;
}

BUF
msg_buildPronoun(const MOB *m, const ITEM *i, const char *s, bool forcesingle)
{
    VERB_PERSON		p;
    BUF			buf;

    p = msg_getPerson(m, i, s);
    if (forcesingle)
    {
	p = gram_singlepronoun(p);
    }

    buf.reference(gram_getpronoun(p));
    return buf;
}

BUF
msg_buildFullName(const MOB *m, const ITEM *i, const char *s, bool usearticle, bool forcesingle, bool avoidyou)
{
    BUF			 rawname, buf;
    BUF			 result;

    rawname.reference("no tea");
    if (m)
    {
	if (avoidyou)
	    rawname = m->getRawName();
	else
	    rawname = m->getName();
    }
    else if (i)
    {
	if (forcesingle)
	    rawname = i->getSingleName();
	else
	    rawname = i->getName();
    }
    else if (s)
	rawname.reference(s);
    
    const char		*art;

    if (usearticle)
	art = gram_getarticle(rawname);
    else
	art = "";

    buf.sprintf("%s%s", art, rawname.buffer());

    return buf;
}

//
// This is the universal formatter
//
void
msg_format(const char *msg, const MOB *m_subject, const ITEM *i_subject, const char *s_subject, const char *verb, const MOB *m_object, const ITEM *i_object, const char *s_object)
{
    AUTOLOCK	a(glbMsgLock);
    BUF			 buf;
    BUF			 newtext;
    bool		forcesingle = false;
    bool		avoidyou = false;

    while (*msg)
    {
	if (*msg == '%')
	{
	    newtext.reference("");
	    forcesingle = false;
	    if (msg[1] && msg[2] == '1')
		forcesingle = true;
	    if (msg[1] && msg[2] == '0')
		avoidyou = true;

	    switch (msg[1])
	    {
		case '%':
		    // Pure %.
		    newtext.reference("%");
		    break;

		case '<':
		    // Escapped <
		    newtext.reference("<");
		    break;

		case 'E':
		    // Element...
		    if (!i_object)
		    {
			newtext.reference("nothing");
		    }
		    else
		    {
			newtext.reference(GAMEDEF::elementdef(i_object->element())->name);
		    }
		    break;
		    
		case 'v':
		    // Conjugate the given verb & append.
		    J_ASSERT(verb);
		    newtext = msg_buildVerb(verb, m_subject, i_subject, s_subject);
		    break;
		case 'V':
		    // Conjugate the given verb & append.
		    J_ASSERT(verb);
		    newtext = msg_buildVerb(verb, m_subject, i_subject, s_subject, true);
		    break;

		case 'S':
		    newtext = msg_buildFullName(m_subject, i_subject, s_subject, true, forcesingle, avoidyou);
		    break;

		case 'P':
		    newtext = msg_buildPronoun(m_subject, i_subject, s_subject, forcesingle);
		    break;

		case 'r':
		    newtext = msg_buildPossessive(m_subject, i_subject, s_subject);
		    break;

		case 'O':
		    if (m_subject && (m_subject == m_object) ||
			i_subject && (i_subject == i_object))
		    {
			// Reflexive case!
			newtext = msg_buildReflexive(m_object, i_object, s_object);
		    }
		    else
			newtext = msg_buildFullName(m_object, i_object, s_object, true, forcesingle, avoidyou);
		    break;
		case 'o':
		    if (m_subject && (m_subject == m_object) ||
			i_subject && (i_subject == i_object))
		    {
			// Reflexive case!
			newtext = msg_buildReflexive(m_object, i_object, s_object);
		    }
		    else
			newtext = msg_buildFullName(m_object, i_object, s_object, false, forcesingle, avoidyou);
		    break;
	    }

	    if (forcesingle || avoidyou)
		msg++;
	    msg += 2;
	    // Append the new text
	    buf.strcat(newtext);
	}
	else if (*msg == '<')
	{
	    char *v = glb_strdup(&msg[1]);
	    char *startv = v;
	    
	    msg++;
	    while (*v && *v != '>')
	    {
		msg++;
		v++;
	    }
	    *v = 0;
	    // Must be closed!
	    J_ASSERT(*msg == '>');
	    if (*msg == '>')
		msg++;

	    newtext = msg_buildVerb(startv, m_subject, i_subject, s_subject);

	    buf.strcat(newtext);

	    free(startv);
	}
	else
	{
	    // Normal character!
	    buf.append(*msg++);
	}
    }

    // If it ends with puntuation, add spaces.
    if (buf.isstring() && gram_isendsentence(buf.lastchar()))
    {
	buf.strcat("  ");
    }

    // Formatted into buf.  Capitalize & print.
    newtext = gram_capitalize(buf);

    msg_report(newtext);
}

//
// These are the specific instantiations.
//
void
msg_format(const char *msg, const MOB *subject)
{
    msg_format(msg, subject, 0, 0, 0, 0, 0, 0);
}

void
msg_format(const char *msg, const ITEM *subject)
{
    msg_format(msg, 0, subject, 0, 0, 0, 0, 0);
}

void
msg_format(const char *msg, const MOB *subject, const MOB *object)
{
    msg_format(msg, subject, 0, 0, 0, object, 0, 0);
}

void
msg_format(const char *msg, const MOB *subject, const ITEM *object)
{
    msg_format(msg, subject, 0, 0, 0, 0, object, 0);
}

void
msg_format(const char *msg, const MOB *subject, const char *verb, const MOB *object)
{
    msg_format(msg, subject, 0, 0, verb, object, 0, 0);
}

void
msg_format(const char *msg, const MOB *subject, const char *object)
{
    msg_format(msg, subject, 0, 0, 0, 0, 0, object);
}
