/*************************************************************************
*  @doc SHROOM EXTERNAL API                                              *
*																		 *
*  INDEXIMP.CPP                                                          *
*                                                                        *
*  Copyright (C) Microsoft Corporation 1997                              *
*  All Rights reserved.                                                  *
*                                                                        *
*  This file contains the implementation of the index object             *
*  												                         *
*																	     *
**************************************************************************
*                                                                        *
*  Written By   : Erin Foxford                                           *
*  Current Owner: erinfox                                                *
*                                                                        *
**************************************************************************/
#include <mvopsys.h>

#ifdef _DEBUG
static char s_aszModule[] = __FILE__;   /* For error report */
#endif


#include <atlinc.h>

// MediaView (InfoTech) includes
#include <groups.h>
#include <wwheel.h>

#include <itquery.h>
#include <itcat.h>
#include <itwbrk.h>
#include <ccfiles.h>
#include <itwbrkid.h>
#include "indeximp.h"
#include "queryimp.h"
#include "mvsearch.h"

#include <ITDB.h>
#include <itcc.h>   // for STDPROP_UID def.
#include <itrs.h>   // for IITResultSet def.
#include <itgroup.h>

#define QUERYRESULT_GROUPCREATE		0x0800


//----------------------------------------------------------------------
// REVIEW (billa): Need to add critical section locking to all methods
// that reference member variables.
//----------------------------------------------------------------------


/********************************************************************
 * @method    STDMETHODIMP | IITIndex | Open |
 *     Opens a full-text index, which can reside in the database's
 * storage or as a Win32 file.
 *
 * @parm IITDatabase* | pITDB | Pointer to database associated with 
 * full-text index
 * @parm LPCWSTR | lpszIndexMoniker | Name of full-text index to open.
 * If index resides outside database (as a file), this should include 
 * the full path to the index.
 * @parm BOOL | fInside | If TRUE, index resides inside of database; 
 * otherwise, index resides outside of database.
 *
 * @rvalue S_OK | The index was successfully opened 
 ********************************************************************/
STDMETHODIMP CITIndexLocal::Open(IITDatabase* pITDB, LPCWSTR lpszIndexMoniker,
								 BOOL fInside)
{
	HFPB hfpb = NULL;
	HRESULT hr;
	INDEXINFO indexinfo;
	char szFileName[_MAX_PATH + 1] = SZ_FI_STREAM_A;

	if (m_idx)
		return (SetErrReturn(E_ALREADYINIT));

	// We have to have a database for charmap (and stoplist and
	// operator table eventually)
    if (NULL == pITDB || NULL == lpszIndexMoniker)
        return (SetErrReturn(E_INVALIDARG));

	m_cs.Lock();
	
	// if index is inside storage, need to get hfpb
	if (fInside)
	{
		WCHAR	rgwch[1];
		IStorage *pStorageDBRoot = NULL;

		// Get root storage from database.
		rgwch[0] = (WCHAR) NULL;
		if (FAILED(hr = pITDB->GetObjectPersistence(rgwch, IITDB_OBJINST_NULL,
										(LPVOID *)&pStorageDBRoot, FALSE)) ||
			(hfpb = (HFPB)FpbFromHfs(pStorageDBRoot, &hr)) == NULL)
		{
			if (pStorageDBRoot != NULL)
				pStorageDBRoot->Release();
				
			m_cs.Unlock();
			return (hr);
		}
	}

	// TODO: make MVIndexOpen take Unicode file name. This might take a little
	// work because it depends on FileOpen, which has a call to one of the Fm 
	// functions...
    DWORD dwSize = (DWORD) STRLEN(szFileName);
	WideCharToMultiByte(CP_ACP, 0, lpszIndexMoniker, -1,
        szFileName + dwSize, _MAX_PATH + 1 - dwSize, NULL, NULL);

	if (NULL == (m_idx = MVIndexOpen(hfpb, (LSZ) szFileName, &hr)))
		goto cleanup;

	MVGetIndexInfoLpidx(m_idx, &indexinfo);
	if (SUCCEEDED(hr = pITDB->GetObject(indexinfo.dwBreakerInstID,
							IID_IWordBreaker, (LPVOID *) &m_piwbrk)))
	{
		BOOL	fLicense;
		
		hr = m_piwbrk->Init(TRUE, CB_MAX_WORD_LEN, &fLicense);
	}
	
	if (FAILED(hr))
		goto cleanup; 
		
	// Open catalog object - we only need one instance
	// TODO (evaluate): how bad of hit is this going to be?
	hr = CoCreateInstance(CLSID_IITCatalogLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITCatalog, 
		                  (VOID **) &m_pCatalog);

	if (FAILED(hr))
		goto cleanup;

	// if it fails, there is no catalog which we can run without.
	if (FAILED(m_pCatalog->Open(pITDB)))
	{
		m_pCatalog->Release();
		m_pCatalog = NULL;
	}
	
cleanup:
	if (FAILED(hr))
		Close();

	// If we have an HFPB for the DB's root storage, we need to release the
	// storage pointer and free the HFPB.  FileClose takes care of everything.
	if (hfpb)
		FileClose(hfpb);
		
	m_cs.Unlock();
	
	return hr;
}


/********************************************************************
 * @method    STDMETHODIMP | IITIndex | CreateQueryInstance |
 *     Creates a query object
 *
 * @parm IITQuery** | ppITQuery | Indirect pointer to query object
 *
 * @rvalue S_OK | The query object was successfully returned   
 *
 ********************************************************************/
STDMETHODIMP CITIndexLocal::CreateQueryInstance(IITQuery** ppITQuery)
{
	// TODO: possible optimization in case where user specifies multiple
	// query objects... get class factory pointer once; then call CreateInstance
	// Free CF when all done w/ index object.

	return CoCreateInstance(CLSID_IITQuery, NULL, CLSCTX_INPROC_SERVER, IID_IITQuery, 
		                  (VOID **) ppITQuery);
}

/********************************************************************
 * @method    STDMETHODIMP | IITIndex | Search |
 *     Performs a full-text search on the open index, returning the
 * results in a result set object.
 *
 * @parm IITQuery* | pITQuery | Pointer to query object
 * @parm IITResultSet* | pITResult | Pointer to result set object 
 *  containing search results. Caller is responsible for initializing
 *  the result set with the properties to be returned. 
 *
 * @rvalue S_FALSE | The search was successful, but returned no hits.
 * @rvalue S_OK | The search was successfully performed. 
 * @rvalue E_NOTOPEN | The index object is not open.
 * @rvalue E_INVALIDARG | One or both parameters is NULL. 
 * @rvalue E_OUTOFMEMORY | There was not enough memory to perform this function. 
 * @rvalue E_NULLQUERY | The query consisted of no terms, or is all stopwords.
 * @rvalue E_STOPWORD | A stopword was one of the terms in the query.
 * @rvalue E_* | An error occurred during the search. Check iterror.h for the possible error codes.
 *
 * @comm  The caller is responsible for setting the proper options
 * through the query object before calling this function.
 ********************************************************************/
STDMETHODIMP CITIndexLocal::Search(IITQuery* pITQuery, IITResultSet* pITResult)
{
	HRESULT hr;
	CITIndexObjBridge *pidxobr = NULL;
	LPQT pQueryTree = NULL;   // Pointer to query tree
	LPHL pHitList = NULL;     // Pointer to hit list
	IITGroup* piitGroup = NULL;
	_LPGROUP lpGroup;

	if (NULL == pITQuery || NULL == pITResult)
		return (SetErrReturn(E_INVALIDARG));

	if (m_idx == NULL)
		return (SetErrReturn(E_NOTOPEN));
		
	if ((pidxobr = new CITIndexObjBridge) != NULL)
	{
		pidxobr->AddRef();
		hr = pidxobr->SetWordBreaker(m_piwbrk);
	}
	else
		hr = E_OUTOFMEMORY;

	if (SUCCEEDED(hr) &&		
		SUCCEEDED(hr = QueryParse(pITQuery, &pQueryTree, pidxobr)))
	{
		SRCHINFO SrchInfo;        // Search parameters
		SrchInfo.dwMemAllowed = 0;
		pITQuery->GetResultCount((LONG &)SrchInfo.dwTopicCount);
		pITQuery->GetOptions(SrchInfo.Flag);
		SrchInfo.dwValue = 0;
		SrchInfo.dwTopicFullCalc = 0;
		SrchInfo.lpvIndexObjBridge = (LPVOID) pidxobr;

		pITQuery->GetGroup(&piitGroup);
		if (piitGroup)
			lpGroup = (_LPGROUP)piitGroup->GetLocalImageOfGroup();
		else
			lpGroup = NULL;
	
		// Perform search
		pHitList = MVIndexSearch(m_idx, pQueryTree, &SrchInfo, lpGroup, &hr);
	
		// Massage hitlist into a result set. 
		if (pHitList)
		{
			hr = HitListToResultSet(pHitList, pITResult, pidxobr);
			MVHitListDispose(pHitList);
		}
	}

	if (pQueryTree)		
		MVQueryFree(pQueryTree);

	// We don't want to delete pidxobr if HitListToResultSet AddRef'ed it
	// so that the result set can hold onto a term string heap via pidxobr.
	if (pidxobr && pidxobr->Release() == 0)
		delete pidxobr;

	return hr;
}

/********************************************************************
 * @method    STDMETHODIMP | IITIndex | Search |
 *     Performs a full-text search on the open index, returning the
 * results in a group object.
 *
 * @parm IITQuery* | pITQuery | Pointer to query object
 * @parm IITGroup* | pITGroup | Pointer to group object. The caller
 * is responsible for initializing this object before passing it.
 *
 * @rvalue S_OK | The search was successfully performed   
 *
 * @comm  The caller is responsible for setting the proper options
 * through the query object before calling this function.
 ********************************************************************/
STDMETHODIMP CITIndexLocal::Search(IITQuery* pITQuery, IITGroup* pITGroup)
{
	HRESULT hr = S_OK;
	CITIndexObjBridge *pidxobr = NULL;
	LPQT pQueryTree = NULL;   // Pointer to query tree
	LPHL pHitList = NULL;     // Pointer to hit list
	
	if (NULL == pITQuery || NULL == pITGroup)
		return (SetErrReturn(E_INVALIDARG));

	if (m_idx == NULL)
		return (SetErrReturn(E_NOTOPEN));
		
	// TODO: MVIndexSearch would take IITGroup*, not _LPGROUP
	_LPGROUP lpGroup = (_LPGROUP) pITGroup->GetLocalImageOfGroup();
	

	if ((pidxobr = new CITIndexObjBridge) != NULL)
		hr = pidxobr->SetWordBreaker(m_piwbrk);
	else
		hr = E_OUTOFMEMORY;

	if (SUCCEEDED(hr) &&		
		SUCCEEDED(hr = QueryParse(pITQuery, &pQueryTree, pidxobr)))
	{
		SRCHINFO SrchInfo;        // Search parameters
		SrchInfo.dwMemAllowed = 0;
		pITQuery->GetResultCount((LONG &)SrchInfo.dwTopicCount);
		pITQuery->GetOptions(SrchInfo.Flag);
		SrchInfo.Flag |= QUERYRESULT_GROUPCREATE;
		SrchInfo.dwValue = 0;
		SrchInfo.dwTopicFullCalc = 0;
		SrchInfo.lpvIndexObjBridge = (LPVOID) pidxobr;
		
		// Perform search - if pHitList comes back NULL, we will return hr
		if (pHitList = MVIndexSearch(m_idx, pQueryTree, &SrchInfo, lpGroup, &hr))
			MVHitListDispose(pHitList);
		}

	if (pQueryTree)		
		MVQueryFree(pQueryTree);
		
	if (pidxobr)
		delete pidxobr;

	return hr;
}


// This is private - it encapsulates the query parsing needed
// in all searches
STDMETHODIMP CITIndexLocal::QueryParse(IITQuery* pITQuery, LPQT* pQueryTree,
												CITIndexObjBridge *pidxobr)
{
	HRESULT hr  = S_OK;
	EXBRKPM	exbrkpm;
	PARSE_PARMS ParseParm;

	ITASSERT(pITQuery != NULL && pQueryTree != NULL && pidxobr != NULL);
	
	// Fill PARSE_PARMS structure
	DWORD dwFlags;
	pITQuery->GetOptions(dwFlags);
	if (dwFlags & QUERYRESULT_SKIPOCCINFO)
		m_fSkipOcc = TRUE;

	ParseParm.cDefOp = (WORD)(dwFlags & IMPLICIT_OR);
	ParseParm.wCompoundWord = (WORD)(dwFlags & COMPOUNDWORD_PHRASE);
	pITQuery->GetProximity(ParseParm.cProxDist);

	IITGroup* ITGroup;
	pITQuery->GetGroup(&ITGroup);
	if (ITGroup)
	{
		_LPGROUP lpGroup = (_LPGROUP) ITGroup->GetLocalImageOfGroup();
		ParseParm.lpGroup = lpGroup;
	}
	else
		ParseParm.lpGroup = NULL;

	// Breaker bridge setup
	exbrkpm.lpvIndexObjBridge = (LPVOID)pidxobr;
	ParseParm.pexbrkpm = &exbrkpm; 
	
	// TODO: provide the right stuff
	ParseParm.lpOpTab = NULL;

    LPSTR lpszQuery = NULL;  // Pointer to query buffer
    DWORD cbQuery;           // Query buffer's length
	DWORD dwCodePageID;
	LCID  lcid;
	
	if (FAILED(GetLocaleInfo(&dwCodePageID, &lcid)))
	{
		ITASSERT(FALSE);
		dwCodePageID = CP_ACP;
	} 

	LPCWSTR lpszwQuery;
	pITQuery->GetCommand(lpszwQuery);
	if (NULL == lpszwQuery)
		return E_NULLQUERY;

	// Query comes in as Unicode, but the FTI still uses MBCS.
	cbQuery = WideCharToMultiByte
        (dwCodePageID, 0, lpszwQuery, -1, NULL, 0, NULL, NULL);

	if ((lpszQuery = new char[cbQuery]) != NULL)
	{
		WideCharToMultiByte(dwCodePageID, 0, lpszwQuery, -1, lpszQuery, cbQuery,
																NULL, NULL);
		ParseParm.cbQuery = cbQuery - 1;
		ParseParm.lpbQuery = (const char*) lpszQuery;
	}
	else	
		hr = E_OUTOFMEMORY;

	// Parse query
	if (SUCCEEDED(hr))
	{
		FCALLBACK_MSG fcbkmsg;
		
		*pQueryTree = MVQueryParse (&ParseParm, &hr);

		if (SUCCEEDED(hr) && SUCCEEDED(pITQuery->GetResultCallback(&fcbkmsg)))
			MVSearchSetCallback(*pQueryTree, &fcbkmsg);
	}
	
	if (lpszQuery)
		delete lpszQuery;
		
	return hr;
}

/********************************************************************
 * @method    STDMETHODIMP | IITIndex | Close |
 *     Closes the full-text index.
 *
 * @rvalue S_OK | The index was successfully closed   
 *
 ********************************************************************/
STDMETHODIMP CITIndexLocal::Close()
{
	m_cs.Lock();

	if (m_idx)
	{
		MVIndexClose(m_idx);
		m_idx = NULL;
	}

	if (m_pCatalog)
	{
		m_pCatalog->Close();
		m_pCatalog->Release();
		m_pCatalog = NULL;
	}
	
	if (m_piwbrk != NULL)
	{
		m_piwbrk->Release();
		m_piwbrk = NULL;
	}
	
	m_cs.Unlock();
	return S_OK;
}


/********************************************************************
 * @method    STDMETHODIMP | IITIndex | GetLocaleInfo |
 *     Gets locale info that the full text index was built with.
 * @parm DWORD* | pdwCodePageID | On exit, pointer to code page ID.
 * @parm LCID* | plcid | On exit, pointer to locale ID.
 *
 * @rvalue S_OK | The locale info was successfully retrieved.   
 *
 ********************************************************************/
STDMETHODIMP CITIndexLocal::GetLocaleInfo(DWORD *pdwCodePageID, LCID *plcid)
{
	INDEXINFO	indexinfo;
	
	if (pdwCodePageID == NULL || plcid == NULL)
		return (SetErrReturn(E_POINTER));

	if (m_idx == NULL)
		return (SetErrReturn(E_NOTOPEN));
		
	MVGetIndexInfoLpidx(m_idx, &indexinfo);
	*pdwCodePageID = indexinfo.dwCodePageID;
	*plcid = indexinfo.lcid;
	
	return (S_OK);
}


/********************************************************************
 * @method    STDMETHODIMP | IITIndex | GetWordBreakerInstance |
 *     Gets the ID of the word breaker instance that the full text
 *		index was built with.
 * @parm DWORD* | pdwObjInstance | On exit, pointer to word breaker instance.
 *
 * @rvalue S_OK | The word breaker instance ID was successfully retrieved.   
 *
 ********************************************************************/
STDMETHODIMP CITIndexLocal::GetWordBreakerInstance(DWORD *pdwObjInstance)
{
	INDEXINFO	indexinfo;
	
	if (pdwObjInstance == NULL)
		return (SetErrReturn(E_POINTER));
	
	if (m_idx == NULL)
		return (SetErrReturn(E_NOTOPEN));
		
	MVGetIndexInfoLpidx(m_idx, &indexinfo);
	*pdwObjInstance = indexinfo.dwBreakerInstID;
	
	return (S_OK);
}


// Private function - passed as a parameter by
// CITIndexLocal::HitListToResultSet.
SCODE __stdcall FreeRSColumnHeap(LPVOID lpvIndexObjBridge)
{
	CITIndexObjBridge *pidxobr;
	
	if (lpvIndexObjBridge == NULL)
		return (SetErrReturn(E_POINTER));
		
	pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
	pidxobr->Release();
	delete pidxobr;

	return (S_OK);
}


// Private function - one grand hack to provide a result set from a hit list
STDMETHODIMP CITIndexLocal::HitListToResultSet(LPHL pHitList, IITResultSet* pRS,
												CITIndexObjBridge *pidxobr)
{
	DWORD cEntry;  // Number of entries
	HIT HitInfo;
	TOPICINFO TopicInfo;
	LONG lColumnUID = -1;
	LONG lColumnOccInfo[5];
	DWORD iTopic, iHit, iColumn;   // Loop indices
	LONG lRow = 0;
	HRESULT hr;

	ITASSERT(pRS != NULL && pidxobr != NULL);

    // Number of entries in hit list - if 0, just return FALSE
    if (0 == (cEntry = MVHitListEntries(pHitList)))
		return S_FALSE;

	hr = pRS->GetColumnFromPropID(STDPROP_UID, lColumnUID);

	if (!m_fSkipOcc)
	{
		for (iColumn = 0; iColumn < 5; iColumn++)
			lColumnOccInfo[iColumn] = -1;

		pRS->GetColumnFromPropID(STDPROP_FIELD, lColumnOccInfo[0]);
		pRS->GetColumnFromPropID(STDPROP_LENGTH, lColumnOccInfo[1]);
		pRS->GetColumnFromPropID(STDPROP_COUNT, lColumnOccInfo[2]);
		pRS->GetColumnFromPropID(STDPROP_OFFSET, lColumnOccInfo[3]);
		pRS->GetColumnFromPropID(STDPROP_TERM_UNICODE_ST, lColumnOccInfo[4]);
	}

    // Loop over all the topics in the hit list
    for (iTopic = 0; iTopic < cEntry; iTopic++)
    {
		hr = MVHitListGetTopic(pHitList, iTopic, &TopicInfo);
		if (FAILED(hr))
			return hr;   // or do we continue?

		if (m_fSkipOcc)
		{
			// No occurrence info
			if (-1 != lColumnUID)
				pRS->Set(lRow, lColumnUID, TopicInfo.dwTopicId);

			lRow++;
		}
		else
		{
			// Requested occurence info, so loop
			// over all the hits in this topic
			for (iHit = 0; iHit < TopicInfo.lcHits; iHit++)
			{
				if (-1 != lColumnUID)
					pRS->Set(lRow, lColumnUID, TopicInfo.dwTopicId);
				
				hr = MVHitListGetHit(pHitList, &TopicInfo, iHit, &HitInfo);
				if (FAILED(hr))
					continue;

				if (-1 != lColumnOccInfo[0])
					pRS->Set(lRow, lColumnOccInfo[0],  HitInfo.dwFieldId);
				if (-1 != lColumnOccInfo[1])
					pRS->Set(lRow, lColumnOccInfo[1],  HitInfo.dwLength);
				if (-1 != lColumnOccInfo[2])
					pRS->Set(lRow, lColumnOccInfo[2],  HitInfo.dwCount);
				if (-1 != lColumnOccInfo[3])
					pRS->Set(lRow, lColumnOccInfo[3],  HitInfo.dwOffset);
				if (-1 != lColumnOccInfo[4])
					pRS->Set(lRow, lColumnOccInfo[4],  (DWORD_PTR) HitInfo.lpvTerm);

				lRow++;

			}  
		}

	}

	// Fill in rest of properties from catalog (like IITWordWheel::GetData)
	if (m_pCatalog)
	{
		hr = m_pCatalog->Lookup(pRS);
		if (S_FALSE == hr)
			hr = S_OK;         // don't report S_FALSE
	}

	// If the caller requested Unicode term STs, then we need to give the result
	// set the string heap and adjust the string lengths in the heap.  Otherwise,
	// we will just let the heap get freed whenever pidxobr gets deleted.
	if (-1 != lColumnOccInfo[4])
	{
		pidxobr->AdjustQueryResultTerms();
		pRS->SetColumnHeap(lColumnOccInfo[4], (LPVOID) pidxobr, FreeRSColumnHeap);

		// Tell our caller not to delete pidxobr because the result set is
		// holding onto it.
		pidxobr->AddRef();
	}

	return S_OK;
}


// Need to export these without decoration to the linker so they can be called
// from the old .c files.
extern "C" {


PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtBreakText(PEXBRKPM pexbrkpm)
{
	CITIndexObjBridge *pidxobr;
	
	if (pexbrkpm == NULL || pexbrkpm->lpvIndexObjBridge == NULL)
		return (SetErrReturn(E_POINTER));
		
	pidxobr = (CITIndexObjBridge *) pexbrkpm->lpvIndexObjBridge;
	
	return (pidxobr->BreakText(pexbrkpm));
}


PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtStemWord(LPVOID lpvIndexObjBridge, LPBYTE lpbStemWord, LPBYTE lpbRawWord)
{
	CITIndexObjBridge *pidxobr;
	
	if (lpvIndexObjBridge == NULL ||
		lpbStemWord == NULL || lpbRawWord == NULL)
		return (SetErrReturn(E_POINTER));
		
	pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
	return (pidxobr->StemWord(lpbStemWord, lpbRawWord));
}


PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtLookupStopWord(LPVOID lpvIndexObjBridge, LPBYTE lpbStopWord)
{
	CITIndexObjBridge *pidxobr;
	
	if (lpvIndexObjBridge == NULL || lpbStopWord == NULL)
		return (SetErrReturn(E_POINTER));
		
	pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
	return (pidxobr->LookupStopWord(lpbStopWord));
}


PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtAddQueryResultTerm(LPVOID lpvIndexObjBridge, LPBYTE lpbTermHit,
												LPVOID *ppvTermHit)
{
	CITIndexObjBridge *pidxobr;
	
	if (lpvIndexObjBridge == NULL || lpbTermHit == NULL || ppvTermHit == NULL)
		return (SetErrReturn(E_POINTER));
		
	pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
	return (pidxobr->AddQueryResultTerm(lpbTermHit, ppvTermHit));
}


}	// End extern "C"