/*******************************************************************************
;*******************************************************************************
;**                                                                           **
;**                    COPYRIGHT 2004-2012 NUANCE COMMUNICATIONS              **
;**                                                                           **
;**               NUANCE COMMUNICATIONS PROPRIETARY INFORMATION               **
;**                                                                           **
;**     This software is supplied under the terms of a license agreement      **
;**     or non-disclosure agreement with Nuance Communications and may not    **
;**     be copied or disclosed except in accordance with the terms of that    **
;**     agreement.                                                            **
;**                                                                           **
;**                                                                           **
;*******************************************************************************
;**                                                                           **
;**     FileName: et9cptone.c                                                 **
;**                                                                           **
;**  Description: Chinese Phrase Text Input phonetic tone module.             **
;**               Conforming to the development version of Chinese XT9.       **
;**                                                                           **
;*******************************************************************************
;******************************************************************************/

#include "et9api.h"
#include "et9cpsys.h"
#include "et9cpkey.h"
#include "et9cpinit.h"
#include "et9cprdb.h"
#include "et9cpspel.h"
#include "et9cptone.h"
#include "et9cpsys.h"
#include "et9cpname.h"
#include "et9cpwdls.h"
#include "et9cppbuf.h"
#include "et9cpmisc.h"
#include "et9cpldb.h"
#include "et9cstrie.h"
#include "et9cptrace.h"


#include "et9imu.h"




/*---------------------------------------------------------------------------
 *
 *   Function: ET9_CP_GetTones
 *
 *   Synopsis: This function gets the valid tones for the given single-syllable spell.
 *
 *     Input:  pET9CPLingInfo    = Pointer to Chinese XT9 LingInfo structure.
 *             pSpell            = pointer to the given single-syllable spell
 *
 *     Return:  valid tone bit mask.
 *                                 Its output format is defined as follows:
 *
 *              x       x       x       x       x       x       x       x
 *                                   tone 5  tone 4  tone 3  tone 2  tone 1
 *
 *---------------------------------------------------------------------------*/
ET9U8 ET9FARCALL ET9_CP_GetTones(ET9CPLingInfo * pET9CPLingInfo, ET9_CP_Spell * pSpell)
{
    ET9_CP_CommonInfo *pCommon;
    ET9U16 wPID, wExactStart, wExactEnd;
    ET9U8 bSylCount, bTone, i;

    pCommon = &(pET9CPLingInfo->CommonInfo);

    /* set PID ranges */
    bSylCount = ET9_CP_SpellingToPidRanges(pET9CPLingInfo, pSpell->pbChars, pSpell->bLen);
    if ( bSylCount != 1 )
    {
        return 0; /* Rejects: mute char / S# but partial spelling off / non-single-syllable spellings */
    }
    bTone = 0;
    /* loop thru each PID range for this syllable */
    for (i = 0; i < pCommon->pbRangeEnd[0] && bTone != ET9_CP_ALL_TONES_BIT_MASK; i += ET9_CP_ID_RANGE_SIZE) {
        wExactStart = pCommon->pwRange[i];
        wExactEnd = pCommon->pwRange[i + 1];
        /* loop thru each PID in range and combine their available tones */
        for (wPID = wExactStart; wPID < wExactEnd && bTone != ET9_CP_ALL_TONES_BIT_MASK; wPID++) {
            bTone = (ET9U8)(bTone | (ET9_CP_LookupTone(pET9CPLingInfo, wPID) & ET9_CP_ALL_TONES_BIT_MASK));
        }
    }
    return bTone;
} /* END ET9_CP_GetTones() */

static void Get_Last_Syllable(const ET9_CP_Spell * pSrc, ET9_CP_Spell * pTgt)
{
    ET9INT i;
    pTgt->bLen = 0;
    if (pSrc->bLen > 0)
    {
        /* Last letter can be ET9CPSYLLABLEDELIMITER or tone or Internal BPMF letter or PinYin Letter */
        ET9Assert( pSrc->pbChars[pSrc->bLen - 1] != ET9CP_SEGMENT_DELIMITER );
        for ( i = pSrc->bLen - 1; i >= 0; i-- )
        {
            if ( ET9_CP_IsUpperCase(pSrc->pbChars[i]) )
                break;
        }
        ET9Assert( i >= 0 );
        for ( ; i < pSrc->bLen; i++ )
        {
            pTgt->pbChars[pTgt->bLen++] = pSrc->pbChars[i];
        }
    }
}

 /* pSpell stores real-tone, not tone-mask */
static ET9BOOL ET9LOCALCALL ValidForToneCycling(const ET9_CP_Spell *pSpell)
{
    ET9U8 b, bSkip;
    for ( b = 1; b < pSpell->bLen; b++ )
    {
        if ( ET9_CP_IsUpperCase(pSpell->pbChars[b]) )
        {
            bSkip = 0;
            if ( ET9CP_SEGMENT_DELIMITER == pSpell->pbChars[b - 1] )
                bSkip = 1;
            if ( ET9CPSymToCPTone(pSpell->pbChars[b - 1 - bSkip]) == 0 )
                return 0;     /* multiple-syllable spell and tone is not before the Upper case letter */
        }
    }
    return 1;
}

/** Retrieve all the valid tones of the last syllable of the active spelling for tone cycling
 *
 *   Function: ET9CPGetToneOptions
 *
 *  This function gets all valid tones for last syllable of the active spelling
 *  the current selected(active). The least significant 5 bits in
 *  pbToneBitMask indicate the 5 tone options.
 *
 * @param pET9CPLingInfo     pointer to chinese information structure:
 * @param pbToneBitMask      returns the value indicating which tone marks are valid:
 *                         - Tone 1 (the even tone) is valid if the least significant bit is set to 1.
 *                         - Tone 2 (the rising tone) is valid if the second least significant bit is set to 1.
 *                         - Tone 3 (the dipping tone) is valid if the third least significant bit is set to 1.
 *                         - Tone 4 (the falling tone) is valid if the fourth least significant bit is set to 1.
 *                         - Tone 5 (the soft or muted tone) is valid if the fifth least significant bit is set to 1.
 *
 *
 * @return ET9STATUS_NONE               Succeeded, *pbToneBitMask == 0 means not a valid position to cycle tone.
 * @return ET9STATUS_NO_INIT            pET9CPLingInfo is not properly initialized
 * @return ET9STATUS_NEED_SELLIST_BUILD Should call ET9CPBuildSelectionList() before call this function
 * @return ET9STATUS_BAD_PARAM          Some argument pointer is NULL
 * @return ET9STATUS_INVALID_MODE       Not phonetic mode
 * @return ET9STATUS_NO_OPERATION         Nothing is performed by the core
 *
 */
ET9STATUS ET9FARCALL ET9CPGetToneOptions(ET9CPLingInfo *pET9CPLingInfo,
                                         ET9U8 *pbToneBitMask)
{
    ET9_CP_CHECK_LINGINFO(pET9CPLingInfo);
    ET9_CP_CHECK_UDB_UP_TO_DATE(pET9CPLingInfo);
    ET9_CP_CHECK_BUILD_UP_TO_DATE(pET9CPLingInfo);

    if (!pbToneBitMask) {
        return ET9STATUS_BAD_PARAM;
    }
    if (!(ET9CPIsModePinyin(pET9CPLingInfo) || ET9CPIsModeBpmf(pET9CPLingInfo))) {
        return ET9STATUS_INVALID_MODE;
    }

    *pbToneBitMask = 0;
    /* no input => no tone */
    if ( pET9CPLingInfo->Base.pWordSymbInfo->bNumSymbs == 0 ) {
        return ET9STATUS_NONE;
    }
    ET9Assert(ET9_CP_HasActiveSpell(pET9CPLingInfo)); /* should have been set by build selection list */

    /* Multi tone support: spell is the last Syllable of pCommon->sActiveSpell */
    if ( ValidForToneCycling(&pET9CPLingInfo->CommonInfo.sActiveSpell) )
    {
        ET9_CP_Spell spell;
        Get_Last_Syllable(&pET9CPLingInfo->CommonInfo.sActiveSpell, &spell);
        *pbToneBitMask = ET9_CP_GetTones(pET9CPLingInfo, &spell);
    }

    return ET9STATUS_NONE;
}   /* end of ET9CPGetToneOptions() */

/** Appends a tone mark to the end of the WordSymbInfo and locks the spelling.

In order to add a tone to ET9WordSymbInfo, application should call this function where bTone is one of
- ET9CPTONE1  -- Even tone.
- ET9CPTONE2  -- Rising tone.
- ET9CPTONE3  -- Dipping tone (falling then rising).
- ET9CPTONE4  -- Falling tone.
- ET9CPTONE5  -- Soft or muted tone.

and psSpell is the spelling the tone will be added. Every letter in psSpell must occur in the corresponding position of pWordSymbInfo.

@param pWordSymbInfo  pointer to word symbol information structure.
@param psSpell        A spell the tone will be added to.
@param bTones         Combination of bit-flag ( 1 << (tone - ET9CPTONE1) ) where ET9CPTONE1 <= tone <= ET9CPTONE5. It must be non-zero.

@return ET9STATUS_NONE                 Succeeded
@return ET9STATUS_INVALID_MEMORY       NULL pointer is passed in
@return ET9STATUS_INVALID_INPUT        bTone is not a tone OR tone can not be added at this position OR psSpell and pWordSymbInfo have different length OR
                                       some char in psSpell does not occur in the corresponding position of pWordSymbInfo
@return ET9STATUS_FULL                 The active word is already the maximum size allowed (ET9MAXWORDSIZE).
*/
ET9STATUS ET9FARCALL ET9CPAddToneSymb(ET9WordSymbInfo *pWordSymbInfo, const ET9CPSpell* psSpell, ET9U8 bTones)
{
    ET9STATUS  status;
    ET9SYMB    sLastSymb;
    ET9SymbInfo *pSI;
    ET9U8      bInputCount, i;
    ET9BOOL    bPrevIsToneDelim;
    ET9SimpleWord wrd;
    ET9CPSpell spell;
    ET9U8 bUnselectedStart, bNumSymbs;
    ET9CPLingInfo * pET9CPLingInfo;


    if (psSpell == 0 || pWordSymbInfo == 0) {
        return ET9STATUS_INVALID_MEMORY;
    }
    bTones = (ET9U8)(bTones & ET9_CP_ALL_TONES_BIT_MASK);

    if (psSpell->bLen == 0 || bTones == 0) {
        return ET9STATUS_INVALID_INPUT;
    }

    sLastSymb = psSpell->pSymbs[psSpell->bLen - 1];
    if ( ET9_CP_LetterIsToneOrDelim(sLastSymb) || ET9_CP_LOCKED_IS_TONE(sLastSymb) ) {
        return ET9STATUS_INVALID_INPUT;
    }

    pET9CPLingInfo = (ET9CPLingInfo *)pWordSymbInfo->Private.ppEditionsList[ET9EDITION_CP];
    bUnselectedStart = ET9_CP_SelectionHistUnselectedStart(&pET9CPLingInfo->SelHistory);
    pSI = pWordSymbInfo->SymbsInfo + bUnselectedStart;

    spell.bLen = 0;
    for (i = 0; i < psSpell->bLen; i++) {
        if (ET9CP_SEGMENT_DELIMITER == psSpell->pSymbs[i]) {
            if (!ET9CPIsUpperCaseSymbol(psSpell->pSymbs[i + 1]) ) {
                /* non-upper case after segment delimiter ==> invalid */
                return ET9STATUS_INVALID_INPUT;
            }
            continue; /* skip segment delimiter */
        }
        else if ( ET9CPIsPhoneticSymbol(psSpell->pSymbs[i]) ) {
            spell.pSymbs[spell.bLen] = psSpell->pSymbs[i];
            spell.bLen++;
        }
        else if ( ET9_CP_LOCKED_IS_TONE(psSpell->pSymbs[i]) ) {
            if ( psSpell->pSymbs[i] != pSI[spell.bLen].sLockedSymb )  /* Allow tone bit mask in psSpell */
                return ET9STATUS_INVALID_INPUT;
            spell.pSymbs[spell.bLen] = pSI[spell.bLen].sLockedSymb;
            spell.bLen++;
        }
        else if ( ET9CPSymToCPTone(psSpell->pSymbs[i]) ) {
            if ( ET9_CP_IsSymbInSymbInfo(psSpell->pSymbs[i], pSI + spell.bLen) && pSI[spell.bLen].sLockedSymb != 0 ) { /* Allow real tone in psSpell */
                spell.pSymbs[spell.bLen] = pSI[spell.bLen].sLockedSymb;                                                /* Change real tone to tone mask */
                spell.bLen++;
            }
            else {
                return ET9STATUS_INVALID_INPUT;
            }
        }
        else {
            return ET9STATUS_INVALID_INPUT;   /* psSpell can not contain ET9CPSYLLABLEDELIMITER since we are adding tone */
        }
    }
#ifdef ET9_KDB_TRACE_MODULE
    if (_ET9HasTraceInfo(pWordSymbInfo)) {
        status = ET9_CP_Trace_ReplaceByExplicitSymb(pWordSymbInfo, pWordSymbInfo->bNumSymbs, spell.pSymbs, spell.bLen);
        if(ET9STATUS_NONE != status) {
            return ET9STATUS_INVALID_INPUT;
        }
    }
#endif
    bNumSymbs = (ET9U8)(pWordSymbInfo->bNumSymbs - bUnselectedStart);
    if (spell.bLen != bNumSymbs) {
        return ET9STATUS_INVALID_INPUT;
    }

    bPrevIsToneDelim = 0;
    bInputCount = 0;
    for (i = 0; i < spell.bLen; i++) {
        bInputCount++;
        if ( spell.pSymbs[i] != pSI[i].sLockedSymb && !ET9_CP_IsSymbInSymbInfo(spell.pSymbs[i], pSI + i) ) {
            return ET9STATUS_INVALID_INPUT;
        }
        if (bPrevIsToneDelim && !ET9CPIsUpperCaseSymbol(spell.pSymbs[i])) {
            /* non-upper case after tone/delimiter ==> invalid */
            return ET9STATUS_INVALID_INPUT;
        }

        if ( ET9_CP_LOCKED_IS_TONE(spell.pSymbs[i]) ) {
            bPrevIsToneDelim = 1;
        }
        else {
            bPrevIsToneDelim = 0;
        }
    }
    if (bInputCount != bNumSymbs) {
        return ET9STATUS_INVALID_INPUT;
    }
    for ( i = 0; i < bUnselectedStart; i++ )
    {
        if ( pWordSymbInfo->SymbsInfo[i].sLockedSymb == 0 )
            return ET9STATUS_INVALID_INPUT;
    }
    if ( bUnselectedStart > 0 && !ET9_CP_SymbIsTone(&pWordSymbInfo->SymbsInfo[bUnselectedStart-1]) )
        return ET9STATUS_INVALID_INPUT;

    wrd.wCompLen = 0;
    wrd.wLen = 0;
    for (i = 0;  i < (ET9U16)bUnselectedStart; i++) {
        wrd.sString[i] = (ET9SYMB)pWordSymbInfo->SymbsInfo[i].sLockedSymb;
        wrd.wLen++;
    }
    for (i = 0;  i < (ET9U16)spell.bLen; i++) {
        wrd.sString[bUnselectedStart + i] = (ET9SYMB)spell.pSymbs[i];
        wrd.wLen++;
    }

    {
        ET9SYMB aSymbs[5];
        ET9U8 aSymbProbs[5];
        ET9U8 bTone, bToneCount = 0;
        ET9SYMB sInternalTone;
        for ( bTone = 0, sInternalTone = (ET9SYMB)ET9CPTONE1; bTone < 5; bTone++, sInternalTone++ )
        {
            if ( (1 << bTone) & bTones )
            {
                aSymbs[bToneCount] = sInternalTone;
                aSymbProbs[bToneCount] = 100;
                bToneCount++;
            }
        }

        status = ET9AddCustomSymbolSet(pWordSymbInfo, aSymbs, aSymbProbs, bToneCount, 0, ET9NOSHIFT, 0);
        if (ET9STATUS_NONE == status) {
            ET9U8 bSave = pWordSymbInfo->Private.bPreventWhiteSpaceInput;
            pWordSymbInfo->Private.bPreventWhiteSpaceInput = 0;
            wrd.sString[wrd.wLen++] = (ET9SYMB)bTones;
            status = ET9LockWord(pWordSymbInfo, &wrd);
            if (ET9STATUS_NONE != status) {
                ET9ClearOneSymb(pWordSymbInfo);
            }
            pWordSymbInfo->Private.bPreventWhiteSpaceInput = bSave;
        }
        else if (ET9STATUS_INVALID_TEXT == status) {
            status = ET9STATUS_INVALID_INPUT; /* replace INVALID_TEXT with INVALID_INPUT status, but pass along other status */
        }
    }

    return status;
}

ET9U8 ET9FARCALL ET9_CP_GetSpellTones(const ET9U8 *pbSpell,
                                      ET9U8 bLen,
                                      ET9U8 *pbTones)
{
    ET9U8 b, bToneCount = 0, bSyls = 0;
    ET9Assert(bLen);
    ET9Assert(ET9_CP_IsUpperCase(pbSpell[0]) );

    for(b = 0; b < bLen; b++) {
        if (ET9_CP_IsUpperCase(pbSpell[b]) ) {
            bSyls++;
            pbTones[bToneCount] = 0;
        }
        else if (ET9_CP_LOCKED_IS_TONE(pbSpell[b]) ) {
            pbTones[bToneCount++] = pbSpell[b]; /* copy the tone bit-mask from input */
        }
    }
    ET9Assert(bToneCount <= ET9CPMAXPHRASESIZE);
    ET9Assert(0 == bToneCount || bToneCount == bSyls || bToneCount == bSyls - 1); /* no tone OR tone in every syl OR tone in every but last syl */
    return bToneCount;
}

/* Copy a spelling string (which has tone masks) to an output spelling structure.
   Meanwhile, apply the given array of tone matches to replace the tone masks.
*/
void ET9FARCALL ET9_CP_ApplyToneMatch(const ET9U8 *pbSpell,
                                      ET9U8 bSpellLen,
                                      const ET9U8 *pbTones,
                                      ET9U8 bSylCount,
                                      ET9_CP_Spell *pOutSpell)
{
    ET9U8 b, bToneCount = 0;
    ET9Assert(pbSpell && ET9_CP_IsUpperCase(pbSpell[0]) && bSpellLen && pbTones && bSylCount && pOutSpell);

#ifndef ET9_DEBUG
    ET9_UNUSED(bSylCount);
#endif

    pOutSpell->bLen = 0;
    for(b = 0; b < bSpellLen; b++) {
        if (ET9_CP_LOCKED_IS_TONE(pbSpell[b]) ) {
            /* apply tone match to replace tone mask in spell */
            ET9Assert(pOutSpell->bLen < ET9CPMAXSPELLSIZE);
            pOutSpell->pbChars[pOutSpell->bLen++] = pbTones[bToneCount++];
            continue;
        }
        /* copy the letter */
        ET9Assert(pOutSpell->bLen < ET9CPMAXSPELLSIZE);
        pOutSpell->pbChars[pOutSpell->bLen++] = pbSpell[b];
    }
    ET9Assert(bToneCount == bSylCount || bToneCount == bSylCount - 1);
}

/*
   if has multiple tone matches and the most common is included, give most common tone match
   otherwise give the single tone match.

   bInTone = 000ttttt, the input tone mask where 't' are the bits of the input tones with LSB as Tone1.
   bLdbToneData = mmmttttt, where 'm' is the value of most common tone,
                                  't' are the bits of the available tones with LSB as Tone1.

   Return: a value from ET9CPTONE1 to ET9CPTONE5, 0 for no match
*/
ET9U8 ET9FARCALL ET9_CP_GetBestToneMatch(ET9U8 bInTone,
                                         ET9U8 bLdbToneData)
{
    /* this array gives the ON bit counts when the index is the 5-bit tone mask */
              /* index(hex)(lo nibble): 0 1 2 3  4 5 6 7  8 9 A B  C D E F */
    static const ET9U8 pbBIT_COUNT[] = {0,1,1,2, 1,2,2,3, 1,2,2,3, 2,3,3,4, /* high bit = 0 */
                                        1,2,2,3, 2,3,3,4, 2,3,3,4, 3,4,4,5};/* high bit = 1 */

    /* this array gives the tone match when the index is the 5-bit tone mask */
    static const ET9U8 pbTONE_MATCH[] = {         0,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1, /* high bit = 0 */
                                         ET9CPTONE3,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1,
                                         ET9CPTONE4,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1,
                                         ET9CPTONE3,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1,

                                         ET9CPTONE5,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1, /* high bit = 1 */
                                         ET9CPTONE3,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1,
                                         ET9CPTONE4,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1,
                                         ET9CPTONE3,ET9CPTONE1,ET9CPTONE2,ET9CPTONE1
                                        };

    ET9U8 bMatchCount, bMostCommonToneMask;

    ET9Assert(bInTone == (bInTone & 0x1F) );

    bMatchCount = pbBIT_COUNT[bInTone & bLdbToneData];
    switch (bMatchCount) {
    case 0: /* no match */
        return 0;
    case 1: /* 1 match */
        return pbTONE_MATCH[bInTone & bLdbToneData];
    default: /* multiple match */
        ET9Assert( (bLdbToneData >> 5) & 0x7 );
        bMostCommonToneMask = (ET9U8)(1 << ( ( (bLdbToneData >> 5) & 0x7) - 1) );
        if (bInTone & bMostCommonToneMask) { /* match most common tone, return it */
            return pbTONE_MATCH[bMostCommonToneMask];
        }
        else { /* not match most common tone, use lowest ON bit in both input & Ldb tones */
            return pbTONE_MATCH[bInTone & bLdbToneData];
        }
    }
}
/* ----------------------------------< eof >--------------------------------- */
