// FTS.CPP : implementation file
//
// by DougO
//
#include "header.h"
#include "stdio.h"
#include "string.h"
#include "TCHAR.h"
#ifdef HHCTRL
#include "parserhh.h"
#else
#include "parser.h"
#endif
#include "collect.h"
#include "hhtypes.h"
#include "toc.h"

#include "highlite.h"
#include "lockout.h"
#include "userwait.h"

#include "fts.h"
#include "hhfinder.h"
#include "csubset.h"
#include "subset.h"
#ifdef _DEBUG
#undef THIS_FILE
static const char THIS_FILE[] = __FILE__;
#endif

#include "ftsnames.cpp"

#define CENTAUR_TERMS 1

/*************************************************************************
 *
 *              SINGLE TO DOUBLE-WIDTH KATAKANA MAPPING ARRAY
 *
 *************************************************************************/

// Single-Width to Double-Width Mapping Array
//

static const int mtable[][2]={
   {129,66},{129,117},{129,118},{129,65},{129,69},{131,146},{131,64},
   {131,66},{131,68},{131,70},{131,72},{131,131},{131,133},{131,135},
   {131,98},{129,91},{131,65},{131,67},{131,69},{131,71},{131,73},
   {131,74},{131,76},{131,78},{131,80},{131,82},{131,84},{131,86},
   {131,88},{131,90},{131,92},{131,94},{131,96},{131,99},{131,101},
   {131,103},{131,105},{131,106},{131,107},{131,108},{131,109},
   {131,110},{131,113},{131,116},{131,119},{131,122},{131,125},
   {131,126},{131,128},{131,129},{131,130},{131,132},{131,134},
   {131,136},{131,137},{131,138},{131,139},{131,140},{131,141},
   {131,143},{131,147},{129,74},{129,75} };

// note, cannot put in .text since the pointers themselves are uninitialized
static const char* pJOperatorList[] =   {"","????","???","????","??????","?m?d?`?q","?n?q","?`?m?c","?m?n?s",""};
static const char* pEnglishOperator[] = {"","and "  ,"or " ,"not "  ,"near "   ,"NEAR "   ,"OR " ,"AND "  ,"NOT "  ,""};

int FASTCALL CompareVolumeOrder( const void* p1, const void* p2 )
{
  int iReturn;

  TITLE_ENTRY* pEntry1= (TITLE_ENTRY*) p1;
  TITLE_ENTRY* pEntry2= (TITLE_ENTRY*) p2;

  if( pEntry1->uiVolumeOrder < pEntry2->uiVolumeOrder )
    iReturn = -1;
  else if ( pEntry1->uiVolumeOrder > pEntry2->uiVolumeOrder )
    iReturn = 1;
  else
    iReturn = 0;

  return iReturn;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch Class
//
// This class provides full-text search functionality to HTML Help.  This
// class encapsulates multi-title searches and combined indexes.
//

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch class constructor
//
CFullTextSearch::CFullTextSearch(CExCollection *pTitleCollection)
{
    m_bInit  = FALSE;
    m_bTitleArrayInit = FALSE;
    m_SearchActive = FALSE;
    m_InitFailed = FALSE;
    m_pTitleCollection = pTitleCollection;
    m_pTitleArray = NULL;
	m_bMergedChmSetWithCHQ = FALSE;
	m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID());
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch class destructor
//
CFullTextSearch::~CFullTextSearch()
{
    TermListRemoveAll();

    INT iTitle, iTitle2;

    // Delete the CCombinedFTS objects
    //
    if(m_pTitleArray)
    {
        for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
        {
            if(m_pTitleArray[iTitle].pCombinedFTI)
            {
                CCombinedFTS *pObject = m_pTitleArray[iTitle].pCombinedFTI;

                for(iTitle2 = 0; iTitle2 < m_TitleArraySize; ++iTitle2)
                {
                    if(m_pTitleArray[iTitle2].pCombinedFTI == pObject)
                        m_pTitleArray[iTitle2].pCombinedFTI = NULL;
                }

                delete pObject;
            }

           // cleanup strings in title array
           CHECK_AND_FREE( m_pTitleArray[iTitle].pszQueryName );
           CHECK_AND_FREE( m_pTitleArray[iTitle].pszIndexName );
           CHECK_AND_FREE( m_pTitleArray[iTitle].pszShortName );

        }
    }
    if(m_pTitleArray)
        lcFree(m_pTitleArray);
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch initialization
//
BOOL CFullTextSearch::Initialize()
{
    if(m_InitFailed)
        return FALSE;

    if(m_bInit)
        return TRUE;

    m_InitFailed = TRUE;
    // init here

    m_bInit = TRUE;
    m_InitFailed = FALSE;

    ZeroMemory(m_HLTermArray, sizeof(m_HLTermArray));
    m_iHLIndex = 0;

    m_lMaxRowCount = 500;
    m_wQueryProximity = 8;

    // DOUGO - insert code here to init system language member

    return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch simple query
//
//   pszQuery           pointer to null terminated query string.
//
//   pcResultCount      pointer to variable to receive results count.
//
//   ppSearchResults    pointer to a SEARCH_RESULT pointer to receive
//                      results list.  Upon return, this pointer will
//                      point to an array of SEARCH_RESULTS structures
//                      of size pcResultCount.
//
HRESULT CFullTextSearch::SimpleQuery(WCHAR *pszQuery, int *pcResultCount, SEARCH_RESULT **ppSearchResults)
{
    if(!m_bInit)
        return FTS_NOT_INITIALIZED;

    return ComplexQuery(pszQuery, FTS_ENABLE_STEMMING, pcResultCount, ppSearchResults, NULL);
}
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch complex query
//
HRESULT CFullTextSearch::ComplexQuery(WCHAR *pszQuery, DWORD dwFlags, int *pcResultCount, SEARCH_RESULT **ppSearchResults, CSubSet *pSubSet)
{
    int wcb = 0, cb = 0;
    HRESULT hr;

    *ppSearchResults = NULL;
    *pcResultCount = 0;

    if(!m_bInit)
        return E_FAIL;

    if(!pszQuery)
        return E_FAIL;

    CUWait cWaitDlg(GetActiveWindow());

    // no chars in string
    //
    if(!*pszQuery)
        return S_OK;

    // Initialize the title array
    //
    if(!m_bTitleArrayInit) {
        InitTitleArray();
    }

    // Make sure title array exists
    //
    if(!m_pTitleArray)
        return E_FAIL;

    INT iTitle;

    // reset the results flags (this flag is set when a title has results to collect)
   //
    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        m_pTitleArray[iTitle].bHasResults = FALSE;
        m_pTitleArray[iTitle].bAlreadyQueried = FALSE;
    }

    BOOL bDbcsQuery = FALSE;
    char *pszDbcsQuery = NULL;

    WCHAR *pwsBuffer = NULL; 
	
    pwsBuffer = lcStrDupW(pszQuery);
	
	if(!pwsBuffer)
	    return E_FAIL;

    TermListRemoveAll();

    AddQueryToTermList(pwsBuffer);

    // Add field identifier to query (VFLD 0 = full content, VFLD 1 = title only)
	//
    wcb = lstrlenW(pwsBuffer)*2;
    WCHAR *pwsField = (WCHAR *) lcMalloc(wcb+22);

    if(!pwsField)
        return E_FAIL;

    // check for title only search
    //
    wcscpy(pwsField,L"(");
    if(dwFlags & FTS_TITLE_ONLY)
        wcscat(pwsField,L"VFLD 1 ");
	else
        wcscat(pwsField,L"VFLD 0 ");

    wcscat(pwsField,pwsBuffer);
    wcscat(pwsField,L")");
    lcFree(pwsBuffer);
    pwsBuffer = pwsField;

    // Append the subset filter query to the user query
    //
    // Construct the subset query based on the current FTS subset.
    //
    WCHAR szSubsetFilter[65534];
    DWORD dwSubsetTitleCount = 0;
    CStructuralSubset *pSubset;

	// pre-load combined index for merged chm sets (NT5)
	//
	if(m_bMergedChmSetWithCHQ)
		LoadCombinedIndex(0);

    // copy the user query into buffer larger enough for subset add-on query
    //
    wcscpy(szSubsetFilter, pwsBuffer);

	// Create subset for titles in current merged chm set when running with 
	// combined index outside of collection (NT5 feature).
	//
	if(!(dwFlags & FTS_SEARCH_PREVIOUS) && m_bMergedChmSetWithCHQ)
	{
		wcscat(szSubsetFilter,L" AND (VFLD 1 ");
		
		for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
		{
			if(!m_pTitleArray[iTitle].bCombinedIndex)
				continue;

			if(dwSubsetTitleCount != 0)
				wcscat(szSubsetFilter,L" OR ");

			// Convert title name to Unicode
			//
			char szTempIdentifier[256], szTempLang[20];
			WCHAR wszTempIdentifier[256];
			// generate the magic title identifier
			//
			strcpy(szTempIdentifier,"HHTitleID");
			strcat(szTempIdentifier,m_pTitleArray[iTitle].pszShortName);
			szTempLang[0] = 0;
			Itoa(m_pTitleArray[iTitle].language, szTempLang);
			strcat(szTempIdentifier,szTempLang);
			
			MultiByteToWideChar(CP_ACP, 0, szTempIdentifier, -1, wszTempIdentifier, sizeof(wszTempIdentifier));
			
			// Append the title identifier to the subset query
			//
			wcscat(szSubsetFilter,wszTempIdentifier);
			dwSubsetTitleCount++;
		}
		// Close the expression
		//
		wcscat(szSubsetFilter,L")");
	}
	else
    // Get the current CStructuralSubset and check if "search previous" is active.
    // When "search previous" is on, we don't use subsetting (we query within the previous results)
    //
    if(m_pTitleCollection && m_pTitleCollection->m_pSSList)
    {
        if( (pSubset = m_pTitleCollection->m_pSSList->GetFTS()) && !(dwFlags & FTS_SEARCH_PREVIOUS) && !pSubset->IsEntire() )
        {
           wcscat(szSubsetFilter,L" AND (VFLD 1 ");

            for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
            {
                // Check if this title is part of the current subset
                //
                if(pSubset->IsTitleInSubset(m_pTitleArray[iTitle].pExTitle))
                {
                    if(dwSubsetTitleCount != 0)
                        wcscat(szSubsetFilter,L" OR ");

                    // Convert title name to Unicode
                    //
					char szTempIdentifier[256], szTempLang[20];
                    WCHAR wszTempIdentifier[256];
					// generate the magic title identifier
                    //
					strcpy(szTempIdentifier,"HHTitleID");
					strcat(szTempIdentifier,m_pTitleArray[iTitle].pszShortName);
					szTempLang[0] = 0;
                    Itoa(m_pTitleArray[iTitle].language, szTempLang);
					strcat(szTempIdentifier,szTempLang);
					
                    MultiByteToWideChar(CP_ACP, 0, szTempIdentifier, -1, wszTempIdentifier, sizeof(wszTempIdentifier));

                    // Append the title identifier to the subset query
                    //
                    wcscat(szSubsetFilter,wszTempIdentifier);
                    dwSubsetTitleCount++;
                }
            }
            // Close the expression
            //
             wcscat(szSubsetFilter,L")");
        }
    }

    IITResultSet* pITRS = NULL;
    LONG CombinedResultCount = 0;
    SEARCH_RESULT *pNextResult;
    BOOL bNeverPrompt;
    int QueryPhase;

    // Query Phase #1 - submit queries to any titles currently available (in CD drive, or on HD)
    // Query Phase #2 - submit queries to all titles in CD order
    //
    for(QueryPhase = 0; QueryPhase < 2; QueryPhase++)
    {
        for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
        {
          if(QueryPhase)
              bNeverPrompt = FALSE;
         else
             bNeverPrompt = TRUE;

            if(m_pTitleArray[iTitle].bSearch && !m_pTitleArray[iTitle].bAlreadyQueried)
            {
                if(m_pTitleArray[iTitle].bCombinedIndex)
                {
                    // Query combined index
                    //
                    CExTitle* pTitle = m_pTitleArray[iTitle].pExTitle;

                    // skip title if bad pTitle or we already have results for this query
               //
                    if(!pTitle | m_pTitleArray[iTitle].bHasResults | m_pTitleArray[iTitle].bAlreadyQueried)
                   continue;

                    // Make sure the storage is available
                    //
					if(!m_bMergedChmSetWithCHQ && m_pTitleCollection && !(m_pTitleCollection->IsSingleTitle()))
						if( FAILED(hr = EnsureStorageAvailability( pTitle, HHRMS_TYPE_COMBINED_QUERY, FALSE, FALSE, bNeverPrompt ) ) )
							continue;

                    // create combined index object if it doesn't exist
                    //
                    if(!m_pTitleArray[iTitle].pCombinedFTI)
                    {
                        // [paulti] must re-init query location before calling LoadCombinedIndex if dirty
                        if(m_pTitleArray[iTitle].pExTitle->m_pCollection->m_Collection.IsDirty())
                       {
                            lcFree( m_pTitleArray[iTitle].pszQueryName );
                            m_pTitleArray[iTitle].pszQueryName = lcStrDup(
                                m_pTitleArray[iTitle].pExTitle->GetUsedLocation()->QueryFileName );
                       }

						if(!m_bMergedChmSetWithCHQ)
						{
	                        if(!LoadCombinedIndex(iTitle))
		                    {
			                    // Load failed - not part of specified combined index.
				                // This title is now disabled for this session.
					            //
						        m_pTitleArray[iTitle].bSearch = FALSE;
							    continue;
						   }
						}
                    }

                    // All is happy - set options and query the combined index
                    //
                    m_pTitleArray[iTitle].pCombinedFTI->UpdateOptions(m_wQueryProximity,m_lMaxRowCount);

                    if(FAILED(hr = m_pTitleArray[iTitle].pCombinedFTI->Query(szSubsetFilter, dwFlags, &pITRS, this, &cWaitDlg, iTitle)))
                    {
                        if(hr == FTS_INVALID_SYNTAX || hr == FTS_CANCELED)
                        {
                            m_iLastResultCount = 0;
                           return hr;
                        }

                        // error accessing title
                        m_pTitleArray[iTitle].bSearch = FALSE;
                        continue;
                    }
                    m_pTitleArray[iTitle].bHasResults = TRUE;
                    m_pTitleArray[iTitle].bAlreadyQueried = TRUE;
                    CombinedResultCount+=ComputeResultCount(pITRS);

                    // Mark all other titles that use this combined index as having been queried
                    //
                    INT iTempTitle;
                    for(iTempTitle = 0; iTempTitle < m_TitleArraySize; ++iTempTitle)
                        if(m_pTitleArray[iTempTitle].pCombinedFTI == m_pTitleArray[iTitle].pCombinedFTI)
                            m_pTitleArray[iTempTitle].bAlreadyQueried = TRUE;

                }
                else
                {
                    // Query index in individual title
                    //
                    CExTitle* pTitle = m_pTitleArray[iTitle].pExTitle;

                    // skip title if bad pTitle or we already have results for this query
                    //
                    if(!pTitle || m_pTitleArray[iTitle].bHasResults || m_pTitleArray[iTitle].bAlreadyQueried )
                       continue;

                    CTitleInformation *pTitleInfo = m_pTitleArray[iTitle].pExTitle->GetInfo();
                    if(!(pTitleInfo && pTitleInfo->IsFullTextSearch()))
                    {
                        // This title turned out to not contain a FTI.  We delay this check until now
                        // for performance reasons.
                        //
                        m_pTitleArray[iTitle].bSearch = FALSE;
                        continue;
                    }
					if(!m_bMergedChmSetWithCHQ && m_pTitleCollection && !(m_pTitleCollection->IsSingleTitle()))
	                    if( FAILED(hr = EnsureStorageAvailability( pTitle, HHRMS_TYPE_TITLE, TRUE, FALSE, bNeverPrompt ) ) )
		                    continue;

                    m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->UpdateOptions(m_wQueryProximity,m_lMaxRowCount);

                    if(FAILED(hr = m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->Query(pwsBuffer, dwFlags, &pITRS, this, &cWaitDlg, iTitle)))
                    {
                        m_pTitleArray[iTitle].bAlreadyQueried = TRUE;
                        if(hr == FTS_INVALID_SYNTAX || hr == FTS_CANCELED)
                        {
                            m_iLastResultCount = 0;
                            return hr;
                        }
                        continue;
                    }

                    m_pTitleArray[iTitle].bHasResults = TRUE;
                    m_pTitleArray[iTitle].bAlreadyQueried = TRUE;
                    CombinedResultCount+=ComputeResultCount(pITRS);
                }

            }
        }
    }
    lcFree(pwsBuffer);

    // check for zero results
    //
    if(!CombinedResultCount)
    {
        *ppSearchResults = NULL;
        m_iLastResultCount = 0;
        return S_OK;
    }

    // compute the max size of the results structure
    //
    cb = ((CombinedResultCount) * sizeof(SEARCH_RESULT));

    // allocate the results structure
    //
    *ppSearchResults = pNextResult = (SEARCH_RESULT *) lcMalloc(cb);

    // Clear the structure
    //
    memset(pNextResult,0,cb);

    int cFilteredResultCount = 0;

    long lRowCount;

    // Query Phase #3 - collect the query results from each search object
    //
    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        if(m_pTitleArray[iTitle].bHasResults)
        {
            if(m_pTitleArray[iTitle].bCombinedIndex)
                pITRS = m_pTitleArray[iTitle].pCombinedFTI->GetResultsSet();
            else if( m_pTitleArray[iTitle].pExTitle->m_pTitleFTS )
                pITRS = m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->GetResultsSet();
            else
              continue;

            if( !pITRS )
              continue;

            // Get the results row count
            //
            pITRS->GetRowCount(lRowCount);

            // Make sure we have results
            //
            if(lRowCount)
            {
                int i,rank = 1;
                CProperty Prop;
                DWORD dwLastTopic = 0xffffffff;

                // walk through the results
                //
                for (i = 0; i < lRowCount; i++)
                {
                    pITRS->Get(i, 0, Prop);

                    if(Prop.dwValue != dwLastTopic)
                    {
                        if(m_pTitleArray[iTitle].bCombinedIndex)
                        {
                            pNextResult->dwTopicNumber = TOPIC_NUM(Prop.dwValue);
                            pNextResult->pTitle = LookupTitle(m_pTitleArray[iTitle].pCombinedFTI,CHM_ID(Prop.dwValue));
                            if(!pNextResult->pTitle)
                                    continue; // skip results from title that are out of date or not loaded

                            if(!pSubSet || pSubSet->m_bIsEntireCollection )  // no subset specified, add the topic
                            {
                                    pNextResult->dwRank = rank++;
                                    pNextResult++;
                                    ++cFilteredResultCount;
                                    dwLastTopic = Prop.dwValue;

                            }
                            else
                                if(pNextResult->pTitle->InfoTypeFilter(pSubSet,pNextResult->dwTopicNumber))
                                {
                                    pNextResult->dwRank = rank++;
                                    pNextResult++;
                                    ++cFilteredResultCount;
                                    dwLastTopic = Prop.dwValue;
                                }
                        }
                        else
                        {
                            pNextResult->dwTopicNumber = Prop.dwValue;
                            pNextResult->pTitle = m_pTitleArray[iTitle].pExTitle;
                            if(!pSubSet || pSubSet->m_bIsEntireCollection )
                            {
                                pNextResult->dwRank = rank++;
                                pNextResult++;
                                ++cFilteredResultCount;
                                dwLastTopic = Prop.dwValue;
                            }
                            else
                                if(pNextResult->pTitle->InfoTypeFilter(pSubSet,pNextResult->dwTopicNumber))
                                {
                                    pNextResult->dwRank = rank++;
                                    pNextResult++;
                                    ++cFilteredResultCount;
                                    dwLastTopic = Prop.dwValue;
                                }
                        }

                    }
                }
            }
        }
    }

    // Save the result count for client
    //
    if(cFilteredResultCount > m_lMaxRowCount)
        *pcResultCount = m_lMaxRowCount;
    else
        *pcResultCount = cFilteredResultCount;


    m_iLastResultCount = *pcResultCount;

#ifdef _DEBUG

    OutputDebugString("FTS: Full-Text Index Query Array\n\n");
    OutputDebugString("Entry      Title          Enabled   CHQ  Results\n");
    OutputDebugString("================================================\n");
    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        char szTemp[500];
        wsprintf(szTemp,"%03d     %16s     %1d       %1d     %1d\n",iTitle, m_pTitleArray[iTitle].pszShortName,m_pTitleArray[iTitle].bSearch,m_pTitleArray[iTitle].bCombinedIndex,m_pTitleArray[iTitle].bHasResults);
        OutputDebugString(szTemp);
    }
    OutputDebugString("================================================\n\n");

#endif

//  Below is some test code that dumps the results list to the debug window
//
//  int x;
//  char szTemp[100];
//  for(x=0;x<cFilteredResultCount;++x)
//  {
//      wsprintf(szTemp,"Title=%0x Rank=%03d\n",(*ppSearchResults)[x].pTitle,(*ppSearchResults)[x].dwRank);
//      OutputDebugString(szTemp);
//  }

    QSort(*ppSearchResults,cFilteredResultCount,sizeof(SEARCH_RESULT),CompareIntValues);

    return S_OK;
}

int FASTCALL CompareIntValues(const void *pval1, const void *pval2)
{
    return ((SEARCH_RESULT *)pval1)->dwRank - ((SEARCH_RESULT *)pval2)->dwRank;
}

long CFullTextSearch::ComputeResultCount(IITResultSet *pResultSet)
{
    long count = 0, uniqueCount = 0;

    if(!pResultSet)
        return 0;

    if(FAILED(pResultSet->GetRowCount(count)))
        return 0;

    if(count)
    {
        int i;
        CProperty Prop;
        DWORD dwLastTopic = 0xffffffff;

        // walk through the results
        //
        for (i = 0; i < count; i++)
        {
            pResultSet->Get(i, 0, Prop);
            if(Prop.dwValue != dwLastTopic)
            {
                ++uniqueCount;
                dwLastTopic = Prop.dwValue;
            }
        }
    }
    return uniqueCount;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch lookup title
//
CExTitle *CFullTextSearch::LookupTitle(CCombinedFTS *pCombinedFTS, DWORD dwValue)
{
    static CCombinedFTS *pCacheObject= NULL;
    static DWORD pCacheValue = NULL;
    static CExTitle *pPreviousResult;

    // see if we are looking up the same title again
    //
    if(pCacheObject == pCombinedFTS && dwValue == pCacheValue)
        return pPreviousResult;

    INT iTitle;

    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        if(m_pTitleArray[iTitle].pCombinedFTI == pCombinedFTS &&
            m_pTitleArray[iTitle].iTitleIndex == dwValue && m_pTitleArray[iTitle].bCombinedIndex)
        {
            pCacheObject = pCombinedFTS;
            pCacheValue = dwValue;
            pPreviousResult = m_pTitleArray[iTitle].pExTitle;
            return m_pTitleArray[iTitle].pExTitle;
        }
    }

    return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch Initialize title array
//
void CFullTextSearch::InitTitleArray()
{
    if(m_bTitleArrayInit)
        return;

    m_TitleArraySize = 0;

    // Iterate through the titles
    //
    CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();

    // Count the entries in the title list
    //
    while(pTitle)
    {
        pTitle = pTitle->GetNext();
        ++m_TitleArraySize;
    }

    int cb = m_TitleArraySize * sizeof(TITLE_ENTRY);

    // allocate the results structure
    //
    m_pTitleArray = (TITLE_ENTRY *) lcMalloc(cb);

    if(!m_pTitleArray)
        return;

    // Clear the structure
    //
    memset(m_pTitleArray,0,cb);

    // Iterate through the titles and init members
    //
    pTitle = m_pTitleCollection->GetFirstTitle();

    INT iTitle;

    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        if(pTitle == NULL)
        {
            lcFree(m_pTitleArray);
            m_pTitleArray = NULL;
            return;
        }
        m_pTitleArray[iTitle].pExTitle = pTitle;
        char *pszQueryName = (char *) pTitle->GetQueryName();
        if(pszQueryName && *pszQueryName)
        {
            m_pTitleArray[iTitle].pszQueryName = lcStrDup(pszQueryName);
            m_pTitleArray[iTitle].bCombinedIndex = TRUE;
        }
        else
            m_pTitleArray[iTitle].bCombinedIndex = FALSE;

        // Check if combined index for master chm exists
		// This feature was added for NT5
		//
        if(iTitle == 0 && m_pTitleCollection->IsSingleTitle())
		{
            char *pszFilePath = lcStrDup((char *) pTitle->GetIndexFileName());
			if(pszFilePath)
			{
			    _strupr(pszFilePath);
                char *pszTemp = strstr(pszFilePath,".CHM");
				if(pszTemp)
				{
				    pszTemp[3] = 'Q';    
					// Check if combined index exists
					//
					if(IsFile(pszFilePath))
					{
			            m_pTitleArray[iTitle].pszQueryName = lcStrDup(pszFilePath);
						m_pTitleArray[iTitle].bCombinedIndex = TRUE;
						m_bMergedChmSetWithCHQ = TRUE;  // This disables CD swapping
					}
				}
                if(pszFilePath)
    				lcFree(pszFilePath);
			}
		}

        if(!m_pTitleCollection->IsSingleTitle())
    		m_bMergedChmSetWithCHQ = FALSE;
			
        // Get version and name info from the index file
        //
        m_pTitleArray[iTitle].pszIndexName = lcStrDup((char *) pTitle->GetIndexFileName());
        CTitleInformation2 Info2(m_pTitleArray[iTitle].pszIndexName);
        m_pTitleArray[iTitle].pszShortName = lcStrDup(Info2.GetShortName());
        m_pTitleArray[iTitle].versioninfo  = Info2.GetFileTime();
        m_pTitleArray[iTitle].language = Info2.GetLanguage();


        // Mark title as having a internal FTI for now.  If the title is not part of a combined index
        // I'll check for FTI before querying (this prevents opening all the titles during this
        // initialization (which saves about 80% on init time).
        //
        m_pTitleArray[iTitle].bSearch = TRUE;

        WORD wTitlePrimaryLang = PRIMARYLANGID(LANGIDFROMLCID(m_pTitleArray[iTitle].language));

        if(m_SystemLangID == LANG_JAPANESE || m_SystemLangID == LANG_KOREAN ||
            m_SystemLangID == LANG_CHINESE)
      {
          // Disable a DBCS title not running on a corresponding DBCS OS
         //
          if(m_SystemLangID != wTitlePrimaryLang && (wTitlePrimaryLang == LANG_JAPANESE
            || wTitlePrimaryLang == LANG_KOREAN || wTitlePrimaryLang == LANG_CHINESE))
                m_pTitleArray[iTitle].bSearch = FALSE;
      }

        // get the volume order
        pTitle->GetVolumeOrder( &(m_pTitleArray[iTitle].uiVolumeOrder),
                    pszQueryName ? HHRMS_TYPE_COMBINED_QUERY : HHRMS_TYPE_TITLE );

        pTitle = pTitle->GetNext();
    }

    // sort the title list based on volume order
	if(!m_bMergedChmSetWithCHQ)
	    QSort( m_pTitleArray, m_TitleArraySize, sizeof(TITLE_ENTRY), CompareVolumeOrder );

    m_bTitleArrayInit = TRUE;

}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch GetPreviousInstance
//
CCombinedFTS *CFullTextSearch::GetPreviousInstance(char *pszQueryName)
{
    static char *pCacheName = NULL;
    static CCombinedFTS *pCacheObject = NULL;

    if(pCacheName)
        if(!strcmp(pszQueryName,pCacheName))
            return pCacheObject;

    INT iTitle;

    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        if(!strcmp(m_pTitleArray[iTitle].pszQueryName,pszQueryName) && m_pTitleArray[iTitle].pCombinedFTI)
        {
            pCacheName = pszQueryName;
            pCacheObject = m_pTitleArray[iTitle].pCombinedFTI;
            return m_pTitleArray[iTitle].pCombinedFTI;
        }

    }
    return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch LoadCombinedIndex
//
BOOL CFullTextSearch::LoadCombinedIndex(DWORD iCurTitle)
{
    HRESULT hr;

    CCombinedFTS *pCombinedFTS = NULL;

    CFileSystem* pDatabase = new CFileSystem;

    char *pszQueryName = m_pTitleArray[iCurTitle].pszQueryName;

    if( SUCCEEDED(hr = pDatabase->Init()) && SUCCEEDED(hr = pDatabase->Open(pszQueryName)))
    {
        CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabase );
        if( SUCCEEDED(hr = pTitleMap->OpenSub( "$TitleMap" ) ) )
        {
            ULONG cbRead = 0;
            WORD wCount = 0;
            pTitleMap->ReadSub( (void*) &wCount, sizeof(wCount), &cbRead );

            for( int iCount = 0; iCount < (int) wCount; iCount++ )
            {
                CHM_MAP_ENTRY mapEntry;

                pTitleMap->ReadSub( (void*) &mapEntry, sizeof(mapEntry), &cbRead );

                if(cbRead != sizeof(mapEntry))
                    return FALSE;

                INT iTitle;

                // walk through the title array and set title index for matching titles
                //
                for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
                {
                    // if a combined index wasn't specified for the title, skip it
                    //
                    if(!m_pTitleArray[iTitle].pszQueryName && !m_bMergedChmSetWithCHQ)
                        continue;

                    FILETIME titleTime = m_pTitleArray[iTitle].versioninfo;
                    char *pszShortName = m_pTitleArray[iTitle].pszShortName;
                    if(!strcmpi(mapEntry.szChmName,pszShortName) && !m_pTitleArray[iTitle].pCombinedFTI &&
                        (!strcmpi(pszQueryName,m_pTitleArray[iTitle].pszQueryName) || m_bMergedChmSetWithCHQ) &&
                        mapEntry.language == m_pTitleArray[iTitle].language &&
                        (mapEntry.versioninfo.dwLowDateTime == titleTime.dwLowDateTime &&
                        mapEntry.versioninfo.dwHighDateTime == titleTime.dwHighDateTime ))
                    {
                        if(!pCombinedFTS)
                        {
                             pCombinedFTS = m_pTitleArray[iTitle].pCombinedFTI =
                             new CCombinedFTS(m_pTitleArray[iTitle].pExTitle,
                             m_pTitleArray[iTitle].pExTitle->GetInfo2()->GetLanguage(), this);
                             // This is the master title for this combined index
                             //
                             m_pTitleArray[iTitle].bSearch = TRUE;
                        }
                        else
                        {
                            m_pTitleArray[iTitle].pCombinedFTI = pCombinedFTS;
                            m_pTitleArray[iTitle].bSearch = TRUE;
                        }
                        m_pTitleArray[iTitle].bCombinedIndex = TRUE;
                        m_pTitleArray[iTitle].versioninfo = mapEntry.versioninfo;
                        m_pTitleArray[iTitle].iTitleIndex = mapEntry.iIndex;
                        m_pTitleArray[iTitle].dwTopicCount = mapEntry.dwTopicCount;
                    }
                }
            }
        }
        delete pTitleMap;
    }

    delete pDatabase;

	// special case where the master chm is out of date in respects to combined index (NT5)
	//
	if(m_bMergedChmSetWithCHQ && m_pTitleArray[0].bCombinedIndex && !m_pTitleArray[0].pCombinedFTI)
		m_pTitleArray[0].bCombinedIndex = 0;


#ifdef _DEBUG
/*
    INT iTitle;

    OutputDebugString("Full-Text Search Query Map Array:\n");
    for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle)
    {
        char szTemp[500];
        wsprintf(szTemp,"  %d bSearch=%d bCombinedIndex=%d iIndex=%02d pExTitle=%d pCombinedFTI=%d\n",iTitle,
            m_pTitleArray[iTitle].bSearch,m_pTitleArray[iTitle].bCombinedIndex,m_pTitleArray[iTitle].iTitleIndex,
            m_pTitleArray[iTitle].pExTitle,m_pTitleArray[iTitle].pCombinedFTI);
        OutputDebugString(szTemp);
    }
*/
#endif


    // Check to see if the title that initiated the loading of this combined index
    // was validated for this index.
    if(m_pTitleArray[iCurTitle].pCombinedFTI)
        return TRUE;
    else
        return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch abort query function
//
HRESULT CFullTextSearch::AbortQuery()
{
    if(!m_bInit)
        return E_FAIL;

    CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();

    while(pTitle)
    {
        // BUGBUG: If this is the first title in a collection, it will
        // reread the system data.
        CTitleInformation *pTitleInfo = pTitle->GetInfo();
        if(pTitleInfo && pTitleInfo->IsFullTextSearch())
        {
            pTitle->m_pTitleFTS->AbortQuery();
        }
        pTitle = pTitle->GetNext();
    }

    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set query options
//
// dwFlag can be one or more of the following:
//
// IMPLICIT_AND
//   Search terms are AND'd if no operator is specified
//
// IMPLICIT_OR
//   Search terms are OR'd if no operator is specified
//
// COMPOUNDWORD_PHRASE
//   Use PHRASE operator for compound words
//
// QUERYRESULT_RANK
//   Results are returned in ranked order
//
// QUERYRESULT_UIDSORT
//   Results are returned in UID order
//
// QUERYRESULT_SKIPOCCINFO
//   Only topic-level hit information is returned
//
// STEMMED_SEARCH
//   The search returns stemmed results
//
HRESULT CFullTextSearch::SetOptions(DWORD dwFlag)
{
    if(!m_bInit)
        return E_FAIL;

    m_dwQueryFlags = dwFlag;

    CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();

    while(pTitle)
    {
        // BUGBUG: If this is the first title in a collection, it will
        // reread the system data.
        CTitleInformation *pTitleInfo = pTitle->GetInfo();
        if(pTitleInfo && pTitleInfo->IsFullTextSearch())
        {
            pTitle->m_pTitleFTS->SetOptions(dwFlag);
        }
        pTitle = pTitle->GetNext();
    }

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set proximity
//
HRESULT CFullTextSearch::SetProximity(WORD wNear)
{
    if(!m_bInit)
        return E_FAIL;

    CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();

    while(pTitle)
    {
        // BUGBUG: If this is the first title in a collection, it will
        // reread the system data.
        CTitleInformation *pTitleInfo = pTitle->GetInfo();
        if(pTitleInfo && pTitleInfo->IsFullTextSearch())
        {
            pTitle->m_pTitleFTS->SetProximity(wNear);
        }
        pTitle = pTitle->GetNext();
    }

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set result count
//   This sets max number of rows to be returned by query
//
HRESULT CFullTextSearch::SetResultCount(LONG cRows)
{
    if(!m_bInit)
        return E_FAIL;

    CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();

    while(pTitle)
    {
        // BUGBUG: If this is the first title in a collection, it will
        // reread the system data.
        CTitleInformation *pTitleInfo = pTitle->GetInfo();
        if(pTitleInfo && pTitleInfo->IsFullTextSearch())
        {
            pTitle->m_pTitleFTS->SetResultCount(cRows);
        }
        pTitle = pTitle->GetNext();
    }

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch free results data structure
//
VOID CFullTextSearch::FreeResults(SEARCH_RESULT *pResults)
{
    if(!m_bInit)
        return;

    if(pResults)
        lcFree(pResults);
}
/*
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
//      Add a term to the highlight list
//
HRESULT CFullTextSearch::AddHLTerm(WCHAR *pwcTerm)
{
    if(!pwcTerm || !*pwcTerm)
        return E_FAIL;

    if(m_iHLIndex < MAX_HIGHLIGHT_TERMS)
        return E_FAIL;

    int len = (wcslen(pwcTerm) + 1) * sizeof(WCHAR);

    m_HLTermArray[m_iHLIndex] = (WCHAR *) lcMalloc(len);

    wcscpy(m_HLTermArray[m_iHLIndex],pwcTerm);

    m_iHLIndex++;

    return S_OK;
}
*/
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
//      Add a term to the highlight list
//
HRESULT CFullTextSearch::AddHLTerm(WCHAR *pwcTerm, int cTermLength)
{
    if(!pwcTerm || !*pwcTerm || !cTermLength)
        return E_FAIL;


    if(*pwcTerm == L')' || *pwcTerm == L'(' || *pwcTerm == L'\"')
        return S_OK;

    if(m_iHLIndex >= MAX_HIGHLIGHT_TERMS)
        return E_FAIL;

    if(!wcsnicmp(pwcTerm,L"HHTitleID",9))
        return S_OK;
    
    int i;

    // do not accept duplicates
    //
    for(i=0;i<m_iHLIndex;++i)
    {
        int cLen = wcslen(m_HLTermArray[i]);
    
        if(cTermLength == cLen && !wcsnicmp(pwcTerm, m_HLTermArray[i], cLen))
            return S_OK;
    }

    int len = (cTermLength + 1) * sizeof(WCHAR);

    m_HLTermArray[m_iHLIndex] = (WCHAR *) lcMalloc(len);

    CopyMemory(m_HLTermArray[m_iHLIndex],pwcTerm,len-sizeof(WCHAR));

    *(m_HLTermArray[m_iHLIndex]+cTermLength) = NULL;

    m_iHLIndex++;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
//      Add a term to the highlight list
//
HRESULT CFullTextSearch::AddQueryToTermList(WCHAR *pwsBuffer)
{
    WCHAR *pwsStart, *pwsTemp = pwsBuffer;

    if(!pwsBuffer || !*pwsBuffer)
	    return E_FAIL;

    while(*pwsTemp)
    {
        // Remove white space
        //
        while(*pwsTemp && (*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')'))
            ++pwsTemp;

		if(!*pwsTemp)
			continue;

        pwsStart = pwsTemp;

        if(*pwsTemp == L'"')
        {
            pwsStart++;
            pwsTemp++;

            while(*pwsTemp && *pwsTemp != L'"')
                ++pwsTemp;
        }
        else
        {
            while(*pwsTemp && !(*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')'))
                ++pwsTemp;
        }

		if(!*pwsTemp)
			continue;

        if(pwsStart != pwsTemp)
        {
              if(wcsnicmp(pwsStart,L"and",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"or",(int)(pwsTemp-pwsStart))
                   && wcsnicmp(pwsStart,L"near",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"not",(int)(pwsTemp-pwsStart))
                   && wcsnicmp(pwsStart,L"VFLD 0",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"VFLD 1",(int)(pwsTemp-pwsStart))
                   && wcsnicmp(pwsStart,L"HHTitleID",9))
                    AddHLTerm(pwsStart,(int)(pwsTemp-pwsStart));
        }

        if(!wcsnicmp(pwsStart,L"HHTitleID",(int)(pwsTemp-pwsStart)))
        {
            while(*pwsTemp && !(*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')'))
                ++pwsTemp;
        }

        if(!wcsnicmp(pwsStart,L"VFLD 0",(int)(pwsTemp-pwsStart)) && !wcsnicmp(pwsStart,L"VFLD 1",(int)(pwsTemp-pwsStart)))
            pwsTemp+=2;



        if(*pwsTemp == L'"')
            ++pwsTemp;
    }
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch TermListRemoveAll
//      Delete all entries in the highlight term list
//
HRESULT  CFullTextSearch::TermListRemoveAll(void)
{
    int i;

    for(i=0;i<m_iHLIndex;++i)
    {
        if(m_HLTermArray[i])
        {
            lcFree(m_HLTermArray[i]);
            m_HLTermArray[i] = NULL;
        }
    }

    m_iHLIndex = 0;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch  GetHLTerm
//      Retrieve a term from the highlight term list
//
WCHAR *  CFullTextSearch::GetHLTermAt(int index)
{
    if(index >= m_iHLIndex)
        return NULL;

    return m_HLTermArray[index];
}

/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch GetHLTermCount
//      Get the count of entries in the highlight term list
//
INT  CFullTextSearch::GetHLTermCount(void)
{
    return m_iHLIndex;
}


/////////////////////////////////////////////////////////////////////////////
// CTitleFTS Class
//
// This class provides full-text search functionality for a single open
// title.  In a multi-title environment, there will be an instance of this
// class for each open title.
//
// This class is intended only for use by the CFullTextSearch class which
// provides full-text search services for both single and multi-title
// configurations.


/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class constructor
//
CTitleFTS::CTitleFTS( PCSTR pszTitlePath, LCID lcid, CExTitle *pTitle)
{
    MultiByteToWideChar(CP_ACP, 0, pszTitlePath, -1, m_tcTitlePath, sizeof(m_tcTitlePath) );
    m_lcid = lcid;
	m_langid = LANGIDFROMLCID(lcid);
	m_codepage = CodePageFromLCID(lcid);
    m_bInit  = FALSE;
    m_SearchActive = FALSE;
    m_pIndex = NULL;
    m_pQuery = NULL;
    m_pITResultSet = NULL;
    m_pITDB = NULL;
    m_InitFailed = FALSE;
    m_InitError = E_FAIL;
    m_pTitle = pTitle;
    m_pPrevQuery = NULL;

    m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID());
    m_fDBCS = FALSE;

    m_lMaxRowCount = 500;
    m_wQueryProximity = 8;
    m_iLastResultCount = 0;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class destructor
//
CTitleFTS::~CTitleFTS()
{
    ReleaseObjects();
    if(m_pPrevQuery)
        lcFree(m_pPrevQuery);
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS release objects
//
void CTitleFTS::ReleaseObjects()
{
    if(!m_bInit)
        return;

    if (m_pITResultSet)
    {
        m_pITResultSet->Clear();
        m_pITResultSet->Release();
    }

    if(m_pQuery)
        m_pQuery->Release();

    if(m_pIndex)
    {
        m_pIndex->Close();
        m_pIndex->Release();
    }

    if(m_pITDB)
    {
        m_pITDB->Close();
        m_pITDB->Release();
    }

   m_pITResultSet = NULL;
   m_pQuery = NULL;
   m_pIndex = NULL;
   m_pITDB = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class initialization
//
HRESULT CTitleFTS::Initialize()
{
    if(m_InitFailed)
        return E_FAIL;

    if(m_bInit)
        return S_OK;

    m_InitFailed = TRUE;

    WORD PrimaryLang = PRIMARYLANGID(m_langid);
  
    if(PrimaryLang == LANG_JAPANESE || PrimaryLang == LANG_CHINESE || PrimaryLang == LANG_KOREAN)
	    m_fDBCS = TRUE;
	else
	    m_fDBCS = FALSE;  

    if(m_lcid == 1033)
        m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS | STEMMED_SEARCH; // QUERYRESULT_RANK
    else
        m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS;

    //  char szLangID[20];
    //GetLocaleInfo(m_lcid,LOCALE_ILANGUAGE,szLangID,sizeof(szLangID));

    // Make sure we have a path
    //
    if(!*m_tcTitlePath)
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    // DOUGO - insert code here to initialize language information

    // Get IITIndex pointer
    //
    HRESULT hr = CoCreateInstance(CLSID_IITIndexLocal, NULL, CLSCTX_INPROC_SERVER,
                     IID_IITIndex, (VOID**)&m_pIndex);
    if (FAILED(hr))
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    // Get IITDatabase pointer
    //
    hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER,
                     IID_IITDatabase, (VOID**)&m_pITDB);
    if (FAILED(hr))
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    // Open the storage system
    //
    hr = m_pITDB->Open(NULL, m_tcTitlePath, NULL);
    if (FAILED(hr))
    {
        m_InitError = FTS_NO_INDEX;
        return E_FAIL;
    }

    // open the index.
    //
    hr = m_pIndex->Open(m_pITDB, txtwFtiMain, TRUE);
    if (FAILED(hr))
    {
        m_InitError = FTS_NO_INDEX;
        return E_FAIL;
    }

    // Create query instance
    //
    hr = m_pIndex->CreateQueryInstance(&m_pQuery);
    if (FAILED(hr))
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    // set search options
    //
    hr = m_pQuery->SetOptions(m_dwQueryFlags);
    if (FAILED(hr))
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    // Create Result Set object
    //
    hr = CoCreateInstance(CLSID_IITResultSet, NULL, CLSCTX_INPROC_SERVER,
                          IID_IITResultSet, (VOID**) &m_pITResultSet);
    if (FAILED(hr))
    {
        m_InitError = FTS_NOT_INITIALIZED;
        return E_FAIL;
    }

    m_bInit = TRUE;
    m_InitFailed = FALSE;
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS query function
//
HRESULT CTitleFTS::Query(WCHAR *pwcQuery, DWORD dwFlags, IITResultSet **ppITRS, CFullTextSearch *pFullTextSearch, CUWait *pWaitDlg, int iTitle)
{
    HRESULT hr;

    WCHAR *pNewQuery = NULL;
	
    if(!Init())
        return m_InitError;

    // Reinit the object if the dirty bit is set
   //
    if(m_pTitle->m_pCollection->m_Collection.IsDirty())
    {
       ReleaseObjects();
        MultiByteToWideChar(CP_ACP, 0, m_pTitle->GetContentFileName(), -1, m_tcTitlePath, sizeof(m_tcTitlePath) );
        m_InitFailed = FALSE;
        m_bInit = FALSE;
        m_InitError = E_FAIL;
        if(!Init())
            return m_InitError;
    }

    WCHAR *pszQuery = pwcQuery;
	
    pszQuery = PreProcessQuery(pwcQuery, m_codepage);

    if(!pszQuery)
	    return E_FAIL;

    *ppITRS = NULL;

    // return if search previous set, but no previous query
    //
    if((dwFlags & FTS_SEARCH_PREVIOUS) && !m_pPrevQuery)
        return S_OK;

    // If this title resulted in no hits last query, but the global result count was non-zero, then 
    // return no hits.  The only time we want to query this title using the last known good query is
    // when the global query count (combined results) was also zero.  In that case, we want to 
    // revert all titles and combined indexes back to the last known good query.  Otherwise, we skip
    // this title because the last query (which resulted in some results globally) is still valid.
    //
    if((dwFlags & FTS_SEARCH_PREVIOUS) && pFullTextSearch->m_iLastResultCount && !m_iLastResultCount)
    {
        lcFree(m_pPrevQuery);
		m_pPrevQuery = NULL;
        return S_OK;
    }

    WCHAR *pPrevQuerySaved = NULL;
	
    if(m_pPrevQuery)
	{
        pPrevQuerySaved = (WCHAR *) lcMalloc((wcslen(m_pPrevQuery)+ 2) * sizeof(WCHAR));
        wcscpy(pPrevQuerySaved,m_pPrevQuery);		
	}

    CStructuralSubset *pSubset;

    // Check if this title is part of the current subset
    //
    if(m_pTitle->m_pCollection && m_pTitle->m_pCollection->m_pSSList)
        if( (pSubset = m_pTitle->m_pCollection->m_pSSList->GetFTS()) && !pSubset->IsEntire() )
            if(!pSubset->IsTitleInSubset(m_pTitle) && !(dwFlags & FTS_SEARCH_PREVIOUS) )
            {
                // clear the previous results
                //
                hr = m_pITResultSet->Clear();
                m_iLastResultCount = 0;
                if(m_pPrevQuery)
                    lcFree(m_pPrevQuery);
           		m_pPrevQuery = NULL;
               if(pPrevQuerySaved)
	              lcFree(pPrevQuerySaved);
                return S_OK;
            }
    if(dwFlags & FTS_ENABLE_STEMMING)
        m_dwQueryFlags |= STEMMED_SEARCH;
     else
        m_dwQueryFlags &= (~STEMMED_SEARCH);

    // Set previous options
    //
    m_pQuery->ReInit();
    m_pQuery->SetOptions(m_dwQueryFlags);
    m_pQuery->SetResultCount(m_lMaxRowCount);
    m_pQuery->SetProximity(m_wQueryProximity);

    FCALLBACK_MSG fCallBackMsg;

    fCallBackMsg.MessageFunc = SearchMessageFunc;
    fCallBackMsg.pUserData = (PVOID)pWaitDlg;
    fCallBackMsg.dwFlags = 0;  // not recommended for use
    m_pQuery->SetResultCallback(&fCallBackMsg);

    // Search Previous
	//
    if(dwFlags & FTS_SEARCH_PREVIOUS)
    {
        // append new query onto old query for "search previous"
        //
        int cQuery = wcslen(pszQuery);
        int cPrevQuery = wcslen(m_pPrevQuery);
        pNewQuery = (WCHAR *) lcMalloc((cQuery+cPrevQuery+20) * sizeof(WCHAR));
        if(!pNewQuery)
            return E_FAIL;

        *pNewQuery = 0;
        wcscat(pNewQuery,m_pPrevQuery);
        wcscat(pNewQuery,L" and ");
        wcscat(pNewQuery,pszQuery);
        wcscat(pNewQuery,L" ");
    }

    // free the previous prev query
    //
    if(m_pPrevQuery)
	{
        lcFree(m_pPrevQuery);
		m_pPrevQuery = NULL;
	}
    // Save the new query for next time
    //
    if(pNewQuery)
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pNewQuery)+ 2) * sizeof(WCHAR));
    else
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pszQuery)+ 2) * sizeof(WCHAR));

    if(pNewQuery)
        wcscpy(m_pPrevQuery,pNewQuery);
    else
        wcscpy(m_pPrevQuery,pszQuery);

    // clear the previous results
    //
    hr = m_pITResultSet->Clear();
    if (FAILED(hr))
    {
        // Error clearing results set
        if(pPrevQuerySaved)
	        lcFree(pPrevQuerySaved);
        return E_FAIL;
    }

    // we want topic numbers back
    //
    hr = m_pITResultSet->Add(STDPROP_UID, (DWORD) 0, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        m_InitError = FTS_NOT_INITIALIZED;
       if(pPrevQuerySaved)
	      lcFree(pPrevQuerySaved);
        return E_FAIL;
    }

    hr = m_pITResultSet->Add(STDPROP_TERM_UNICODE_ST, (DWORD)NULL, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        m_InitError = FTS_NOT_INITIALIZED;
        if(pPrevQuerySaved)
	      lcFree(pPrevQuerySaved);
        return E_FAIL;

    }

    hr = m_pITResultSet->Add(STDPROP_COUNT, (DWORD)NULL, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        m_InitError = FTS_NOT_INITIALIZED;
        if(pPrevQuerySaved)
	        lcFree(pPrevQuerySaved);
        return E_FAIL;
    }

    // Set the query
    //
    if(pNewQuery)
	{
        pFullTextSearch->AddQueryToTermList(pNewQuery);
        hr = m_pQuery->SetCommand(pNewQuery);
//		MessageBoxW(NULL,pNewQuery,L"Query",MB_OK);
	}
    else
	{
        pFullTextSearch->AddQueryToTermList(pszQuery);
        hr = m_pQuery->SetCommand(pszQuery);
//		MessageBoxW(NULL,pwcQuery,L"Query",MB_OK);		
	}

    if (FAILED(hr))
    {
        // Error setting query
       if(pPrevQuerySaved)
	      lcFree(pPrevQuerySaved);
        return E_FAIL;
    }

    if(pNewQuery)
        lcFree(pNewQuery);

    // Execute the query
    //
    hr = m_pIndex->Search(m_pQuery, m_pITResultSet);

    // if we receive a no stemmer error, re-query with stemming turned off
    //
    if(hr == E_NOSTEMMER)
    {
        m_dwQueryFlags &= (~STEMMED_SEARCH);
        m_pQuery->SetOptions(m_dwQueryFlags);
        m_pQuery->SetCommand(pszQuery);
        hr = m_pIndex->Search(m_pQuery, m_pITResultSet);
    }

    long lRowCount;

    m_pITResultSet->GetRowCount(lRowCount);

    m_iLastResultCount = lRowCount;

    // If query failed, then restore the previous query (for next search previous)
	//
    if((FAILED(hr) || !lRowCount) && pPrevQuerySaved)
	{
        if(m_pPrevQuery)
	        lcFree(m_pPrevQuery);
			
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pPrevQuerySaved)+ 2) * sizeof(WCHAR));
        wcscpy(m_pPrevQuery,pPrevQuerySaved);		
	}

    if(pPrevQuerySaved)
        lcFree(pPrevQuerySaved);

    if (hr ==E_NULLQUERY || hr == E_MISSQUOTE || hr == E_EXPECTEDTERM || hr == E_MISSLPAREN
        || hr == E_MISSRPAREN || hr == E_ALL_WILD)
    {
        // Error invalid syntax
        //
        return FTS_INVALID_SYNTAX;
    }

    if(hr == FTS_CANCELED)
        return FTS_CANCELED;

    // Generic error
    //
    if (FAILED(hr))
        return E_FAIL;

    // Add search terms to term list
    //

    // Make sure we have results
    //
    if(lRowCount)
    {
        WCHAR wcBuffer[2048];

        int i;
        CProperty Prop, Prop2, Prop3;
        DWORD dwLastTopic = 0xffffffff, dwNextWordCount;

        WCHAR *pwcTemp = wcBuffer;

        *pwcTemp = 0;

        // prime the topic number
        //
        m_pITResultSet->Get(0,0, Prop3);
        dwLastTopic = Prop3.dwValue;

        // walk through the results
        //
        for (i = 0; i < lRowCount; i++)
        {
            m_pITResultSet->Get(i,0, Prop3);
            if(i<lRowCount)
            {
                m_pITResultSet->Get(i+1,2, Prop2);
                dwNextWordCount = Prop2.dwValue;
            }
            else
                dwNextWordCount = 0;

            m_pITResultSet->Get(i,1, Prop);
            m_pITResultSet->Get(i,2, Prop2);

         if(!Prop.dwValue)
            continue;

         WORD wFlags[2];
		 wFlags[0]=0;

		 // Convert term from Unicode to ANSI because GetStringTypeW is not supported on Win95.
		 //
         WORD dwStrLen = (WORD)(wcslen(&Prop.lpszwData[1]) * sizeof(WCHAR));
         if(dwStrLen)
         {
             char *pAnsiString = (char *) lcMalloc(dwStrLen);
			 DWORD dwTermLen = 1;

             //UNICODE WORK:  will have to use title cp when doing this conversion to ANSI (Damn Win98 and no Unicode support!!).

             WideCharToMultiByte(m_codepage, 0, Prop.lpszwData + 1, -1, pAnsiString, dwStrLen, NULL, NULL);
             
			 if(IsDBCSLeadByteEx(m_codepage, *pAnsiString))
				dwTermLen=2;

             GetStringTypeA(m_lcid, CT_CTYPE3, pAnsiString, dwTermLen, wFlags);

             lcFree(pAnsiString);
         }		 
         // skip DB chars (DB words were already added)
         //
         if(wFlags[0] & C3_FULLWIDTH || wFlags[0] & C3_KATAKANA || wFlags[0] & C3_HIRAGANA)
         {
            if(*pwcTemp)
            {
                   pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer));
                  pwcTemp = wcBuffer;
               *pwcTemp = 0;
            }
            dwLastTopic = Prop3.dwValue;
            continue;
         }

            CopyMemory(pwcTemp, Prop.lpszwData + 1, (*((WORD *)Prop.lpszwData) * sizeof(WCHAR)));
            pwcTemp+=*((WORD *)Prop.lpszwData);
            *pwcTemp = 0;

            if(dwNextWordCount != (Prop2.dwValue+1) || (wcslen(wcBuffer) > 500))
            {
                pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer));
                pwcTemp = wcBuffer;
            *pwcTemp = 0;
            }
            else
            {
                *pwcTemp++ = L' ';
                *pwcTemp = 0;
            }
            dwLastTopic = Prop3.dwValue;
        }
    }

    // Send the results set back
    //
    *ppITRS = m_pITResultSet;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS abort current query
//
HRESULT CTitleFTS::AbortQuery()
{
    if(!Init())
        return E_FAIL;

    // DOUGO - insert abort code here when Centaur support is available

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS update FTS options without calling into Centaur
//
HRESULT CTitleFTS::UpdateOptions(WORD wNear, LONG cRows)
{
    m_wQueryProximity = wNear;
    m_lMaxRowCount = cRows;
    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set title FTS options
//
HRESULT CTitleFTS::SetOptions(DWORD dwFlag)
{
    HRESULT hr;

    if(!Init())
        return E_FAIL;

    m_dwQueryFlags = dwFlag;

    hr = m_pQuery->SetOptions(dwFlag);

    if (FAILED(hr))
        return E_FAIL;
    else
        return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set proximity for title
//
HRESULT CTitleFTS::SetProximity(WORD wNear)
{
    if(!Init())
        return E_FAIL;

    m_wQueryProximity = wNear;
    return m_pQuery->SetProximity(wNear);
}

/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set max result count for title query
//
HRESULT CTitleFTS::SetResultCount(LONG cRows)
{
    if(!Init())
        return E_FAIL;

    m_lMaxRowCount = cRows;

    return m_pQuery->SetResultCount(cRows);
}

// Han2Zen
//
// This function converts half-width katakana character to their
// full-width equivalents while taking into account the nigori
// and maru marks.
//
DWORD Han2Zen(unsigned char *lpInBuffer, unsigned char *lpOutBuffer, UINT codepage )
{
   // Note: The basic algorithm (including the mapping table) used here to
   // convert half-width Katakana characters to full-width Katakana appears
   // in the book "Understanding Japanese Information Systems" by
   // O'Reily & Associates.

    while(*lpInBuffer)
    {
        if(*lpInBuffer >= 161 && *lpInBuffer <= 223)
        {
            // We have a half-width Katakana character. Now compute the equivalent
            // full-width character via the mapping table.
            //
            *lpOutBuffer     = (unsigned char)mtable[*lpInBuffer-161][0];
            *(lpOutBuffer+1) = (unsigned char)mtable[*lpInBuffer-161][1];

            lpInBuffer++;

            // check if the second character is nigori mark.
            //
            if(*lpInBuffer == 222)
            {
                // see if we have a half-width katakana that can be modified by nigori.
                //
                if((*(lpInBuffer-1) >= 182 && *(lpInBuffer-1) <= 196) ||
                   (*(lpInBuffer-1) >= 202 && *(lpInBuffer-1) <= 206) || (*(lpInBuffer-1) == 179))
                {
                    // transform kana into kana with maru
                    //
                    if((*(lpOutBuffer+1) >= 74   && *(lpOutBuffer+1) <= 103) ||
                     (*(lpOutBuffer+1) >= 110 && *(lpOutBuffer+1) <= 122))
                    {
                         (*(lpOutBuffer+1))++;
                         ++lpInBuffer;
                    }
                    else if(*lpOutBuffer == 131 && *(lpOutBuffer+1) == 69)
                    {
                        *(lpOutBuffer+1) = 148;
                        ++lpInBuffer;
                    }
                }
            }
            else if(*lpInBuffer==223) // check if following character is maru mark
            {
                // see if we have a half-width katakana that can be modified by maru.
                //
                if((*(lpInBuffer-1) >= 202 && *(lpInBuffer-1) <= 206))
                {
                    // transform kana into kana with nigori
                    //
                    if(*(lpOutBuffer+1) >= 110 && *(lpOutBuffer+1) <= 122)
                    {
                        *(lpOutBuffer+1)+=2;
                        ++lpInBuffer;
                    }
                }
            }

            lpOutBuffer+=2;
        }
        else
        {
            if(IsDBCSLeadByteEx(codepage, *lpInBuffer))
            {
                *lpOutBuffer++ = *lpInBuffer++;
                if(*lpInBuffer)
                    *lpOutBuffer++ = *lpInBuffer++;
            }
            else
                *lpOutBuffer++ = *lpInBuffer++;
        }
    }

    *lpOutBuffer = 0;
    return TRUE;
}


WCHAR* PreProcessQuery(WCHAR *pwcQuery, UINT codepage)
{
    if(!pwcQuery)
	    return NULL;
    
    char *pszQuery = NULL;

    // compute max length for ANSI/DBCS conversion buffer
	//
	DWORD dwTempLen = ((wcslen(pwcQuery)*2)+4);
	
    // allocate buffer for ANSI/DBCS version of query string
    //	
    char *pszTempQuery1 = (char *) lcMalloc(dwTempLen);

    // return on fail
	//
    if(!pszTempQuery1)
	    return NULL;

    // Convert our Unicode query to ANSI/DBCS
    //    
    int ret = WideCharToMultiByte(codepage, 0, pwcQuery, -1, pszTempQuery1, dwTempLen, "%", NULL);

    // return on fail
    //	
	if(!ret || !pszTempQuery1)
	    return NULL;

    int cUnmappedChars = 0; 
    char *pszTempQuery5 = pszTempQuery1;

    // Count the number of unmappable characters
    //	
	while(*pszTempQuery5)
	{
        if(*pszTempQuery5 == '%')
            ++cUnmappedChars;
            
        if(IsDBCSLeadByteEx(codepage, *pszTempQuery5))
	    {
		    pszTempQuery5++;
			if(*pszTempQuery5)
			    pszTempQuery5++;
		}
		else
	        ++pszTempQuery5;
	}

    // allocate a new buffer large enough for unmapped character place holders plus original query
    //    	
    DWORD dwTranslatedLen = (DWORD)strlen(pszTempQuery1) + (cUnmappedChars * 4) + 16;	

    char *pszTempQuery6 = (char *)lcMalloc(dwTranslatedLen);
    char *pszTempQuery7 = pszTempQuery6;
	
	if(!pszTempQuery6)
	    return NULL;
	
    pszTempQuery5 = pszTempQuery1;
    
    // construct the new query string (inserting unmappable character place holders)
	//
	while(*pszTempQuery5)
	{
	    if(*pszTempQuery5 == '%')
		{
		    ++pszTempQuery5;
		    *pszTempQuery7++='D';
		    *pszTempQuery7++='X';
		    *pszTempQuery7++='O';		
		    continue;
		}

        if(IsDBCSLeadByteEx(codepage, *pszTempQuery5))
	    {
		    *pszTempQuery7++ = *pszTempQuery5++;
			if(*pszTempQuery5)
    		    *pszTempQuery7++ = *pszTempQuery5++;
		}
		else
            *pszTempQuery7++ = *pszTempQuery5++;
	}

    *pszTempQuery7 = 0;

    lcFree(pszTempQuery1);
		
    char *pszTempQuery2 = pszTempQuery6;
       		
    // If we are running a Japanese title then we nomalize Katakana characters
    // by converting half-width Katakana characters to full-width Katakana.
    // This allows the user to receive hits for both the full and half-width
    // versions of the character regardless of which version they type in the
    // query string.
    //
    if(codepage == 932)
    {
        int cb = (int)strlen(pszTempQuery2)+1;

        // allocate new buffer for converted query
        //
        char *pszTempQuery3 = (char *) lcMalloc(cb*2);

        // convert half-width katakana to full-width
        //
        Han2Zen((unsigned char *)pszTempQuery2,(unsigned char *)pszTempQuery3, codepage);
		
		if(pszTempQuery2)
		    lcFree(pszTempQuery2);
			
		pszTempQuery2 = pszTempQuery3;
    }
    // done half-width normalization
	
    // For Japanese queries, convert all double-byte quotes into single byte quotes
    //
    if(codepage == 932)
    {
        char *pszTemp = pszTempQuery2;

        while(*pszTemp)
        {
            if(*pszTemp == '' && (*(pszTemp+1) == 'h' || *(pszTemp+1) == 'g' || *(pszTemp+1) == 'J') )
            {
                *pszTemp = ' ';
                *(pszTemp+1) = '\"';
            }
            pszTemp = CharNext(pszTemp);
        }
    }
    // done convert quotes

    // This section converts contigious blocks of DBCS characters into phrases (enclosed in double quotes).
    // Converting DBCS words into phrases is required with the character based DBCS indexer we use.
    //
    int i, cb = (int)strlen(pszTempQuery2);

    // allocate new buffer for processed query
    //
    char *pszDest, *pszTemp;

    char *pszTempQuery4  = (char *) lcMalloc(cb*8);

    if(!pszTempQuery4)
        return NULL;

    pszTemp = pszTempQuery2;
    pszDest = pszTempQuery4;

    while(*pszTemp)
    {
        // check for quoted string - if found, copy it
        if(*pszTemp == '"')
        {
            *pszDest++=*pszTemp++;
            while(*pszTemp && *pszTemp != '"')
            {
                if(IsDBCSLeadByteEx(codepage, *pszTemp))
                {
                    *pszDest++=*pszTemp++;
                    *pszDest++=*pszTemp++;
                }
                else
                    *pszDest++=*pszTemp++;
            }
            if(*pszTemp == '"')
                    *pszDest++=*pszTemp++;
            continue;
        }

    	// Convert Japanese operators to English operators
	    //
        if(IsDBCSLeadByteEx(codepage, *pszTemp))
        {
            // check for full-width operator, if found, convert to ANSI
            if(i = IsJOperator(pszTemp))
            {
                strcpy(pszDest,pEnglishOperator[i]);
                pszDest+=strlen(pEnglishOperator[i]);
                pszTemp+=strlen(pJOperatorList[i]);
                continue;
            }

            *pszDest++=' ';
            *pszDest++='"';
            while(*pszTemp && *pszTemp !='"' && IsDBCSLeadByteEx(codepage, *pszTemp))
            {
                *pszDest++=*pszTemp++;
                *pszDest++=*pszTemp++;
            }
            *pszDest++='"';
            *pszDest++=' ';
            continue;
        }

        *pszDest++=*pszTemp++;
    }
    *pszDest = 0;

    if(pszTempQuery2)
        lcFree(pszTempQuery2);

    // compute size of Unicode buffer;

    int cbUnicodeSize = ((MultiByteToWideChar(codepage, 0, pszTempQuery4, -1, NULL, 0) + 2) *2);

    WCHAR *pszUnicodeBuffer = (WCHAR *) lcMalloc(cbUnicodeSize);

    ret = MultiByteToWideChar(codepage, 0, pszTempQuery4, -1, pszUnicodeBuffer, cbUnicodeSize);

    if(!ret)
        return NULL;

    if(pszTempQuery4)
        lcFree(pszTempQuery4);

    return (WCHAR *) pszUnicodeBuffer;
}

// This function computes if pszQuery is a FTS operator in full-width alphanumeric.
//
// return value
//
//      0 = not operator
//      n = index into pEnglishOperator array of translated English operator
//
int IsJOperator(char *pszQuery)
{
    if((PRIMARYLANGID(GetSystemDefaultLangID())) != LANG_JAPANESE)
        return FALSE;

    if(!pszQuery)
        return 0;

    int i = 1;
    char *pTerm = (char*)pJOperatorList[i];

    while(*pTerm)
    {
        if(compareOperator(pszQuery,pTerm))
            return i;

        pTerm = (char*)pJOperatorList[++i];
    }

    return 0;
}

// Compare operator to query.  This is similar to a stricmp.
//
BOOL compareOperator(char *pszQuery, char *pszTerm)
{
    if(!*pszQuery || !*pszTerm)
        return FALSE;

    while(*pszQuery && *pszTerm)
    {
        if(*pszQuery != *pszTerm)
            return FALSE;

        ++pszQuery;
        ++pszTerm;
    }

    if(*pszTerm)
        return FALSE;

    return TRUE;
}


/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS Class
//
// This class provides full-text search functionality for multiple titles.
//
// This class is intended only for use by the CFullTextSearch class which
// provides full-text search services for both single and multi-title
// configurations.


/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class constructor
//
CCombinedFTS::CCombinedFTS(CExTitle *pExTitle, LCID lcid, CFullTextSearch *pFTS)
{
    m_lcid = lcid;
	m_langid = LANGIDFROMLCID(lcid);	
	m_codepage = CodePageFromLCID(lcid);
    m_pTitle = pExTitle;
    m_SearchActive = FALSE;
    m_pIndex = NULL;
    m_pQuery = NULL;
    m_pITResultSet = NULL;
    m_pITDB = NULL;
    m_pFullTextSearch = pFTS;
    m_pPrevQuery = NULL;
    m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID());
    m_fDBCS = FALSE;
    m_lMaxRowCount = 500;
    m_wQueryProximity = 8;
    m_iLastResultCount = 0;
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class destructor
//
CCombinedFTS::~CCombinedFTS()
{
    if (m_pITResultSet)
    {
        m_pITResultSet->Clear();
        m_pITResultSet->Release();
    }
    if(m_pPrevQuery)
        lcFree(m_pPrevQuery);

}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class destructor
//
void CCombinedFTS::ReleaseObjects()
{

    if(m_pQuery)
    {
        m_pQuery->Release();
        m_pQuery = NULL;
    }

    if(m_pIndex)
    {
        m_pIndex->Close();
        m_pIndex->Release();
        m_pIndex = NULL;
    }

    if(m_pITDB)
    {
        m_pITDB->Close();
        m_pITDB->Release();
        m_pITDB = NULL;
    }
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class initialization
//
HRESULT CCombinedFTS::Initialize()
{
	const char *pszQueryName;

	if(m_pFullTextSearch && m_pFullTextSearch->m_bMergedChmSetWithCHQ)
		pszQueryName = m_pFullTextSearch->m_pTitleArray[0].pszQueryName;
	else
		pszQueryName = m_pTitle->GetQueryName();

	if(!pszQueryName)
		return E_FAIL;

	MultiByteToWideChar(CP_ACP, 0, pszQueryName, -1, m_tcTitlePath, sizeof(m_tcTitlePath) );

    WORD PrimaryLang = PRIMARYLANGID(m_langid);
  
    if(PrimaryLang == LANG_JAPANESE || PrimaryLang == LANG_CHINESE || PrimaryLang == LANG_KOREAN)
	    m_fDBCS = TRUE;
	else
	    m_fDBCS = FALSE;  

    if(m_lcid == 1033)
        m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS | STEMMED_SEARCH;
    else
        m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS;

    //  char szLangID[20];
    //GetLocaleInfo(m_lcid,LOCALE_ILANGUAGE,szLangID,sizeof(szLangID));

    // Make sure we have a path
    //
    if(!*m_tcTitlePath)
    {
        return E_FAIL;
    }

    // Get IITIndex pointer
    //
    HRESULT hr = CoCreateInstance(CLSID_IITIndexLocal, NULL, CLSCTX_INPROC_SERVER,
                     IID_IITIndex, (VOID**)&m_pIndex);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // Get IITDatabase pointer
    //
    hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER,
                     IID_IITDatabase, (VOID**)&m_pITDB);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // Open the storage system
    //
    hr = m_pITDB->Open(NULL, m_tcTitlePath, NULL);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // open the index.
    //
    hr = m_pIndex->Open(m_pITDB, txtwFtiMain, TRUE);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // Create query instance
    //
    hr = m_pIndex->CreateQueryInstance(&m_pQuery);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // set search options
    //
    hr = m_pQuery->SetOptions(m_dwQueryFlags);
    if (FAILED(hr))
    {
        return E_FAIL;
    }

    // Create Result Set object
    //
    if(!m_pITResultSet)
    {
        hr = CoCreateInstance(CLSID_IITResultSet, NULL, CLSCTX_INPROC_SERVER,
                              IID_IITResultSet, (VOID**) &m_pITResultSet);
        if (FAILED(hr))
        {
            return E_FAIL;
        }
    }

    m_pITResultSet->Clear();

    // we want topic numbers back
    //
    hr = m_pITResultSet->Add(STDPROP_UID, (DWORD) 0, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        return E_FAIL;
    }

    hr = m_pITResultSet->Add(STDPROP_TERM_UNICODE_ST, (DWORD)NULL, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        return E_FAIL;

    }

    hr = m_pITResultSet->Add(STDPROP_COUNT, (DWORD)NULL, PRIORITY_NORMAL);
    if (FAILED(hr))
    {
        // Error adding result property
        return E_FAIL;
    }

    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS query function
//
HRESULT CCombinedFTS::Query(WCHAR *pwcQuery, DWORD dwFlags, IITResultSet **ppITRS, CFullTextSearch *pFullTextSearch, CUWait *pWaitDlg, int iTitle)
{
    HRESULT hr;

    WCHAR *pNewQuery = NULL;

    if(FAILED(hr = Initialize()))
       return hr;

    *ppITRS = NULL;

    // return if search previous set, but no previous query
    //
    if((dwFlags & FTS_SEARCH_PREVIOUS) && !m_pPrevQuery)
    {
       ReleaseObjects();
        return S_OK;
    }

    WCHAR *pszQuery = pwcQuery;
	
    pszQuery = PreProcessQuery(pwcQuery, m_codepage);

    if(!pszQuery)
	    return E_FAIL;

    // If this combined index resulted in no hits last query, but the global result count was non-zero, then 
    // return no hits.  The only time we want to query this combined index using the last known good query is
    // when the global query count (combined results) was also zero.  In that case, we want to 
    // revert all titles and combined indexes back to the last known good query.  Otherwise, we skip
    // this combined index because the last query (which resulted in some results globally) is still valid.
    //
    if((dwFlags & FTS_SEARCH_PREVIOUS) && pFullTextSearch->m_iLastResultCount && !m_iLastResultCount)
    {
        lcFree(m_pPrevQuery);
		m_pPrevQuery = NULL;
        return S_OK;
    }

    WCHAR *pPrevQuerySaved = NULL;
	
    if(m_pPrevQuery)
	{
        pPrevQuerySaved = (WCHAR *) lcMalloc((wcslen(m_pPrevQuery)+ 2) * sizeof(WCHAR));
        wcscpy(pPrevQuerySaved,m_pPrevQuery);		
	}
	
    if(dwFlags & FTS_ENABLE_STEMMING)
        m_dwQueryFlags |= STEMMED_SEARCH;
    else
        m_dwQueryFlags &= (~STEMMED_SEARCH);

    // Set previous options
    //
    m_pQuery->ReInit();
    m_pQuery->SetOptions(m_dwQueryFlags);
    m_pQuery->SetResultCount(m_lMaxRowCount);
    m_pQuery->SetProximity(m_wQueryProximity);


   FCALLBACK_MSG fCallBackMsg;

   fCallBackMsg.MessageFunc = SearchMessageFunc;
   fCallBackMsg.pUserData = (PVOID)pWaitDlg;  // used to pass back userdata
   fCallBackMsg.dwFlags = 0;  // not recommended for use
   m_pQuery->SetResultCallback(&fCallBackMsg);

    // create group if doing "search previous results"
    //
    if(dwFlags & FTS_SEARCH_PREVIOUS)
    {
        // append new query onto old query for "search previous"
        //
        int cQuery = wcslen(pszQuery);
        int cPrevQuery = wcslen(m_pPrevQuery);
        pNewQuery = (WCHAR *) lcMalloc((cQuery+cPrevQuery+20) * sizeof(WCHAR));
        if(!pNewQuery)
       {
           ReleaseObjects();
            return E_FAIL;
       }
        *pNewQuery = 0;
        wcscat(pNewQuery,m_pPrevQuery);
        wcscat(pNewQuery,L" and ");
        wcscat(pNewQuery,pszQuery);
        wcscat(pNewQuery,L" ");
    }

    // free the previous prev query
    //
    if(m_pPrevQuery)
        lcFree(m_pPrevQuery);

    // Save the new query for next time
    //
    if(pNewQuery)
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pNewQuery)+ 2) * sizeof(WCHAR));
    else
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pszQuery)+ 2) * sizeof(WCHAR));

    if(!m_pPrevQuery)
   {
       ReleaseObjects();
        return E_FAIL;
   }

    if(pNewQuery)
        wcscpy(m_pPrevQuery,pNewQuery);
    else
        wcscpy(m_pPrevQuery,pszQuery);

    // clear the previous results
    //
    hr = m_pITResultSet->ClearRows();
    if (FAILED(hr))
   {
       if(pPrevQuerySaved)
	      lcFree(pPrevQuerySaved);
       ReleaseObjects();
        return E_FAIL;
   }

    // Set the query
    //
    if(pNewQuery)
	{
        pFullTextSearch->AddQueryToTermList(pNewQuery);
        hr = m_pQuery->SetCommand(pNewQuery);
	}
    else
	{
        pFullTextSearch->AddQueryToTermList(pszQuery);
        hr = m_pQuery->SetCommand(pszQuery);
	}

    if (FAILED(hr))
   {
       if(pPrevQuerySaved)
	      lcFree(pPrevQuerySaved);
       ReleaseObjects();
        return E_FAIL;
   }

    if(pNewQuery)
        lcFree(pNewQuery);

    // Execute the query
    //
    hr = m_pIndex->Search(m_pQuery, m_pITResultSet);


    if(hr == E_NOSTEMMER)
    {
        m_dwQueryFlags &= (~STEMMED_SEARCH);
        m_pQuery->SetOptions(m_dwQueryFlags);
        m_pQuery->SetCommand(pszQuery);
        hr = m_pIndex->Search(m_pQuery, m_pITResultSet);
    }

    long lRowCount;

    m_pITResultSet->GetRowCount(lRowCount);

    m_iLastResultCount = lRowCount;

    // If query failed, then restore the previous query (for next search previous)
	//
    if((FAILED(hr) || !lRowCount) && pPrevQuerySaved)
	{
        if(m_pPrevQuery)
	        lcFree(m_pPrevQuery);
			
        m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pPrevQuerySaved)+ 2) * sizeof(WCHAR));
        wcscpy(m_pPrevQuery,pPrevQuerySaved);		
	}

    if(pPrevQuerySaved)
        lcFree(pPrevQuerySaved);

    if (hr ==E_NULLQUERY || hr == E_MISSQUOTE || hr == E_EXPECTEDTERM || hr == E_MISSLPAREN
        || hr == E_MISSRPAREN || hr == E_ALL_WILD)
    {
        // Error invalid syntax
        //
        ReleaseObjects();
        return FTS_INVALID_SYNTAX;
    }

    if(hr == FTS_CANCELED)
    {
       ReleaseObjects();
       return FTS_CANCELED;
    }

    // Generic error
    //
    if (FAILED(hr))
    {
       ReleaseObjects();
       return E_FAIL;
    }

    // Add search terms to term list
    //

    // Make sure we have results
    //
    if(lRowCount)
    {
        WCHAR wcBuffer[2048];

        int i;
        CProperty Prop, Prop2, Prop3;
        DWORD dwLastTopic = 0xffffffff, dwNextWordCount;

        WCHAR *pwcTemp = wcBuffer;

        *pwcTemp = 0;

        // prime the topic number
        //
        m_pITResultSet->Get(0,0, Prop3);
        dwLastTopic = Prop3.dwValue;

        // walk through the results
        //
        for (i = 0; i < lRowCount; i++)
        {
            m_pITResultSet->Get(i,0, Prop3);
            if(i<lRowCount)
            {
                m_pITResultSet->Get(i+1,2, Prop2);
                dwNextWordCount = Prop2.dwValue;
            }
            else
                dwNextWordCount = 0;

            m_pITResultSet->Get(i,1, Prop);
            m_pITResultSet->Get(i,2, Prop2);


             if(!Prop.dwValue)
                continue;

             WORD wFlags[2];
			 wFlags[0]=0;

             // Convert term from Unicode to ANSI because GetStringTypeW is not supported on Win95.
	    	 //
             WORD dwStrLen = (WORD)(wcslen(Prop.lpszwData + 1) * sizeof(WCHAR));
             if(dwStrLen)
             {
                char *pAnsiString = (char *) lcMalloc(dwStrLen);
				DWORD dwTermLen = 1;

                WideCharToMultiByte(m_codepage, 0, Prop.lpszwData + 1, -1, pAnsiString, dwStrLen, NULL, NULL);
				
				if(IsDBCSLeadByteEx(m_codepage, *pAnsiString))
					dwTermLen=2;

                GetStringTypeA(m_lcid, CT_CTYPE3, pAnsiString, dwTermLen, wFlags);

                lcFree(pAnsiString);
             }		 

             // skip DB chars (DB words were already added)
             //
             if(wFlags[0] & C3_FULLWIDTH || wFlags[0] & C3_KATAKANA || wFlags[0] & C3_HIRAGANA)
             {
                if(*pwcTemp)
                {
                       pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer));
                      pwcTemp = wcBuffer;
                   *pwcTemp = 0;
                }
                dwLastTopic = Prop3.dwValue;
                continue;
             }

            CopyMemory(pwcTemp, Prop.lpszwData + 1, (*((WORD *)Prop.lpszwData) * sizeof(WCHAR)));
            pwcTemp+=*((WORD *)Prop.lpszwData);
            *pwcTemp = 0;

            if(dwNextWordCount != (Prop2.dwValue+1) || (wcslen(wcBuffer) > 500))
            {
                pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer));
                pwcTemp = wcBuffer;
            }
            else
            {
                *pwcTemp++ = L' ';
                *pwcTemp = 0;
            }
            dwLastTopic = Prop3.dwValue;
        }
    }

    // Send the results set back
    //
    *ppITRS = m_pITResultSet;

    ReleaseObjects();

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS abort current query
//
HRESULT CCombinedFTS::AbortQuery()
{
    // DOUGO - insert abort code here when Centaur support is available

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS update FTS options without calling into Centaur
//
HRESULT CCombinedFTS::UpdateOptions(WORD wNear, LONG cRows)
{
    m_wQueryProximity = wNear;
    m_lMaxRowCount = cRows;
    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set title FTS options
//
HRESULT CCombinedFTS::SetOptions(DWORD dwFlag)
{
    HRESULT hr = E_FAIL;

    m_dwQueryFlags = dwFlag;

    if(m_pQuery)
        hr = m_pQuery->SetOptions(dwFlag);

    if (FAILED(hr))
        return E_FAIL;
    else
        return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set proximity for title
//
HRESULT CCombinedFTS::SetProximity(WORD wNear)
{
    HRESULT hr = E_FAIL;

    m_wQueryProximity = wNear;

    if(m_pQuery)
        hr = m_pQuery->SetProximity(wNear);

   return hr;
}

/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set max result count for title query
//
HRESULT CCombinedFTS::SetResultCount(LONG cRows)
{
    HRESULT hr = E_FAIL;

    m_lMaxRowCount = cRows;

    if(m_pQuery)
        hr = m_pQuery->SetResultCount(cRows);

    return hr;
}

ERR SearchMessageFunc(DWORD dwFlag, LPVOID pUserData, LPVOID pMessage)
{
    MSG msg;
    CUWait *pUW = (CUWait *)pUserData;

    if(!pUW->m_bVisable)
    {
        ShowWindow(pUW->m_hwndUWait, SW_SHOW);
        pUW->m_bVisable = TRUE;
    }

    while (PeekMessage(&msg, pUW->m_hwndUWait, 0, 0, PM_REMOVE))
    {
        if(!IsDialogMessage(pUW->m_hwndUWait, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
	    }
    }

    if (pUW->m_bUserCancel == TRUE)
        return FTS_CANCELED;

   return S_OK; // return something else to abort
}
