#include <stdlib.h>
#include "common.h"
#include "score.h"
#include "runnet.h"
#include "jaws.h"
#include "fugu.h"
#include "sole.h"
#include "nnet.h"

extern LOCRUN_INFO		g_locRunInfo;

// validates the header of the Jaws net
BOOL CheckJawsNetHeader (void *pData)
{
	NNET_HEADER		*pHeader	=	(NNET_HEADER *)pData;

	// wrong magic number
	ASSERT (pHeader->dwFileType == JAWS_FILE_TYPE);

	if (pHeader->dwFileType != JAWS_FILE_TYPE)
	{
		return FALSE;
	}

	// check version
	ASSERT(pHeader->iFileVer >= JAWS_OLD_FILE_VERSION);
    ASSERT(pHeader->iMinCodeVer <= JAWS_CUR_FILE_VERSION);

	ASSERT	(	!memcmp (	pHeader->adwSignature, 
							g_locRunInfo.adwSignature, 
							sizeof (pHeader->adwSignature)
						)
			);

	ASSERT (pHeader->cSpace == 1);

    if	(	pHeader->iFileVer >= JAWS_OLD_FILE_VERSION &&
			pHeader->iMinCodeVer <= JAWS_CUR_FILE_VERSION &&
			!memcmp (	pHeader->adwSignature, 
						g_locRunInfo.adwSignature, 
						sizeof (pHeader->adwSignature)
					) &&
			pHeader->cSpace == 1
		)
    {
        return TRUE;
    }
	else
	{
		return FALSE;
	}
}

BOOL JawsLoadPointer(JAWS_LOAD_INFO *pJaws)
{
    NNET_SPACE_HEADER	*pSpaceHeader;

	if (!CheckJawsNetHeader (pJaws->info.pbMapping))
		return FALSE;

	// point to the one and only space header
	pSpaceHeader = (NNET_SPACE_HEADER *)(pJaws->info.pbMapping + sizeof (NNET_HEADER));

    if	(	restoreLocalConnectNet	(	pJaws->info.pbMapping + pSpaceHeader->iDataOffset, 
										0, &pJaws->net
									) == NULL
		)
    {
        return FALSE;
    }

    pJaws->iNetSize = 
		getRunTimeNetMemoryRequirements (pJaws->info.pbMapping + pSpaceHeader->iDataOffset);

    if (pJaws->iNetSize <= 0)
    {
        return FALSE;
    }

    return TRUE;
}

///////////////////////////////////////
//
// JawsLoadFile
//
// Load otter/fugu/sole combiner from file
//
// Parameters:
//      pInfo: [out] Information about the mapped file
//      wszPath: [in] Path to load from
//
// Return values:
//      TRUE on successful, FALSE otherwise.
//
//////////////////////////////////////
BOOL JawsLoadFile(JAWS_LOAD_INFO *pJaws, wchar_t *wszPath)
{
	wchar_t	wszFile[_MAX_PATH];

	// Generate path to file.
	FormatPath(wszFile, wszPath, (wchar_t *)0, (wchar_t *)0, (wchar_t *)0, L"jaws.bin");

    if (!DoOpenFile(&pJaws->info, wszFile)) 
    {
        return FALSE;
    }
    return JawsLoadPointer(pJaws);
}

///////////////////////////////////////
//
// JawsUnloadFile
//
// Load otter/fugu/sole combiner from file
//
// Parameters:
//      pInfo: [out] File to unmap
//
// Return values:
//      TRUE on successful, FALSE otherwise.
//
//////////////////////////////////////
BOOL JawsUnloadFile(JAWS_LOAD_INFO *pJaws)
{
    return DoCloseFile(&pJaws->info);
}

BOOL JawsLoadRes(JAWS_LOAD_INFO *pJaws, HINSTANCE hInst, int nResID, int nType)
{
    if (DoLoadResource(&pJaws->info, hInst, nResID, nType) == NULL)
    {
        return FALSE;
    }
    return JawsLoadPointer(pJaws);
}

// describe the codepoint
RREAL *CodePointFlags(ALC alc, RREAL *pFeat)
{
    *(pFeat++) = ((alc & ALC_NUMERIC) ? 65535 : 0);
    *(pFeat++) = ((alc & ALC_ALPHA) ? 65535 : 0);
    *(pFeat++) = ((alc & (ALC_PUNC | ALC_NUMERIC_PUNC | ALC_OTHER)) ? 65535 : 0);
    *(pFeat++) = ((alc & (ALC_HIRAGANA | ALC_JAMO | ALC_BOPOMOFO)) ? 65535 : 0);
    *(pFeat++) = ((alc & (ALC_KATAKANA | ALC_HANGUL_ALL)) ? 65535 : 0);
    *(pFeat++) = ((alc & (ALC_KANJI_ALL)) ? 65535 : 0);
    return pFeat;
}

// Given an alt list with dense and possibly folded codes in it, run through it
// and expand the folded lists.  The unfolded alt list is returned in place.
// This function assumes that the list begins with better alternates, as those
// later in the list will get dropped if we run out of space.
static void UnfoldCodes(ALT_LIST *pAltList, LOCRUN_INFO *pLocRunInfo, CHARSET *cs)
{
	int i, cOut=0;
	ALT_LIST newAltList;	// This will be where the new alt list is constructed.

	// For each alternate in the input list and while we have space in the output list
	for (i=0; i<(int)pAltList->cAlt && (int)cOut<MAX_ALT_LIST; i++) {

		// Check if the alternate is a folded coded
		if (LocRunIsFoldedCode(pLocRunInfo,pAltList->awchList[i])) {
			int kndex;
			// If it is a folded code, look up the folding set
			wchar_t *pFoldingSet = LocRunFolded2FoldingSet(pLocRunInfo, pAltList->awchList[i]);

			// Run through the folding set, adding non-NUL items to the output list
			// (until the output list is full)
			for (kndex = 0; 
				kndex < LOCRUN_FOLD_MAX_ALTERNATES && pFoldingSet[kndex] != 0 && (int)cOut<MAX_ALT_LIST; 
				kndex++) {
				if (IsAllowedChar(pLocRunInfo, cs, pFoldingSet[kndex])) 
				{
					newAltList.awchList[cOut]=pFoldingSet[kndex];
					newAltList.aeScore[cOut]=pAltList->aeScore[i];
					cOut++;
#ifdef DISABLE_UNFOLDING
					// If unfolding is disabled, then stop after producing one unfolded code.
					// This way we don't push results later in the alt list out of the alt
					// list, while still allowing the recognizer to return unicodes for each
					// alternate.  
					break;
#endif
				}
			}
		} else {
			// Dense codes that are not folded get added directly
			newAltList.awchList[cOut]=pAltList->awchList[i];
			newAltList.aeScore[cOut]=pAltList->aeScore[i];
			cOut++;
		}
	}	
	// Store the length of the output list
	newAltList.cAlt=cOut;

	// Copy the output list over the input.
	*pAltList=newAltList;
}

int JawsFeaturize(FUGU_LOAD_INFO *pFugu, SOLE_LOAD_INFO *pSole, LOCRUN_INFO *pLocRunInfo, 
                  GLYPH *pGlyph, RECT *pGuide,
                  CHARSET *pCharSet, RREAL *pFeat, ALT_LIST *pAltList,
                  BOOL *pfAgree)
{
	int i;
	ALT_LIST altListFugu;
	ALT_LIST altListSole;

    wchar_t wchSoleTop1;
    float flSoleTop1;
	
    if (FuguMatch(&pFugu->fugu, &altListFugu, MAX_ALT_LIST, pGlyph, NULL, pCharSet, pLocRunInfo) < 0)
    {
        return -1;
    }

    UnfoldCodes(&altListFugu, pLocRunInfo, pCharSet);
    altListSole = altListFugu;

    if (SoleMatchRescore(pSole, &wchSoleTop1, &flSoleTop1, &altListSole, MAX_ALT_LIST, 
                         pGlyph, pGuide, pCharSet, pLocRunInfo) < 0)
    {
        return -1;
    }

    *pAltList = altListSole;

    if (altListFugu.cAlt > 0 && wchSoleTop1 == altListFugu.awchList[0])
    {
        *pfAgree = TRUE;
    }
    else
    {
        *pfAgree = FALSE;
        if (altListFugu.cAlt > JAWS_NUM_ALTERNATES)
        {
            pAltList->cAlt = JAWS_NUM_ALTERNATES;
        }
        for (i = 0; i < JAWS_NUM_ALTERNATES; i++) 
        {
            extern UNIGRAM_INFO g_unigramInfo;
            if (i < (int) altListFugu.cAlt) 
            {
                *(pFeat++) = (int) (altListFugu.aeScore[i] * 65535);
                *(pFeat++) = (int) (altListSole.aeScore[i] * 65535);
                *(pFeat++) = (int) (-UnigramCost(&g_unigramInfo, altListFugu.awchList[i]) * 100 * 256);
                pFeat = CodePointFlags(LocRun2ALC(pLocRunInfo, altListFugu.awchList[i]), pFeat);
            }
            else
            {
                *(pFeat++) = 0;
                *(pFeat++) = 0;
                *(pFeat++) = (int) (-UnigramCost(&g_unigramInfo, 0xFFFE) * 100 * 256);
                pFeat = CodePointFlags(0, pFeat);
            }
        }
        *(pFeat++) = (CframeGLYPH(pGlyph) - 1) * 65535;
    }
    return pAltList->cAlt;
}

///////////////////////////////////////
//
// JawsMatch
//
// Invoke Fugu/Otter/Sole combiner on a character.  
//
// Parameters:
//      pFugu:          [in]  Fugu database to use
//      pAltList:       [in/out] Alt list to rewrite the scores of
//      cAlt:           [in]  The maximum number of alternates to return
//      pGlyph:         [in]  The ink to recognize
//      pGuide:         [in]  Guide to scale ink to
//      pCharSet:       [in]  Filter for the characters to be returned
//      pLocRunInfo:    [in]  Pointer to the locale database
//
// Return values:
//      Returned the number of items in the alt list, or -1 if there is an error
//
//////////////////////////////////////
int JawsMatch(JAWS_LOAD_INFO *pJaws, FUGU_LOAD_INFO *pFugu, SOLE_LOAD_INFO *pSole,
              ALT_LIST *pAltList, int cAlt, GLYPH *pGlyph, RECT *pGuide, 
              CHARSET *pCharSet, LOCRUN_INFO *pLocRunInfo)
{
	int i;
    RREAL *pNetOut;
    int iWinner, cOut;
    BOOL fAgree;
    RREAL *pFeat = (RREAL *) ExternAlloc(pJaws->iNetSize * sizeof(RREAL));
    if (pFeat == NULL)
    {
        return -1;
    }

    if (JawsFeaturize(pFugu, pSole, pLocRunInfo, pGlyph, pGuide, pCharSet, pFeat, pAltList, &fAgree) < 0)
    {
        ExternFree(pFeat);
        return -1;
    }

    if (!fAgree)
    {
        pNetOut = runLocalConnectNet(&pJaws->net, pFeat, &iWinner, &cOut);
        if (cOut < (int) pAltList->cAlt)
        {
            pAltList->cAlt = cOut;
        }
        for (i = 0; i < (int) pAltList->cAlt; i++) 
        {
            pAltList->aeScore[i] = (float) *(pNetOut++) / (float) SOFT_MAX_UNITY;
        }
    }

    for (i = 0; i < (int) pAltList->cAlt; i++) 
    {
        pAltList->aeScore[i] = ((float) -ProbToScore(pAltList->aeScore[i])) / (float) 256.0;
    }

    SortAltList(pAltList);

    ExternFree(pFeat);
    return pAltList->cAlt;
}