/*
 * Automatic language and codepage detector
 * 
 * Bob Powell, 2/97
 * Copyright (C) 1996, 1997, Microsoft Corp.  All rights reserved.
 * 
 *  History:    1-Feb-97    BobP      Created
 *              5-Aug-97    BobP      Unicode support; Charmaps in data file.
 */
#include "private.h"
/****************************************************************/



Histogram::Histogram (const PFileHistogramSection pHS, const PHIdx pMap)
: m_nDimensionality((UCHAR)pHS->m_dwDimensionality),
  m_nEdgeSize((UCHAR)pHS->m_dwEdgeSize),
  m_nCodePage((USHORT)pHS->m_dwCodePage),
  m_pMap(pMap),
  m_panElts((HElt *)&pHS[1])	// table follows header struct  in the file
{
	// #elements = #unique character values ^ #dimensions

	m_nElts = 1;
	for (UCHAR i = 0; i < m_nDimensionality; i++)
		m_nElts *= m_nEdgeSize;
}

DWORD
Histogram::Validate (DWORD nBytes) const
{
	if ( nBytes < m_nElts * sizeof(HElt) ||
		 m_nDimensionality > 4 )
	{
		return ERROR_INTERNAL_DB_CORRUPTION;
	}

	return NO_ERROR;
}

Histogram::Histogram (const Histogram &H, const PHIdx pMap)
: m_nDimensionality(H.m_nDimensionality),
  m_nEdgeSize(H.m_nEdgeSize),
  m_nCodePage(H.m_nCodePage),
  m_nElts(H.m_nElts),
  m_pMap(pMap),
  m_panElts(H.m_panElts)
//
// Clone a histogram but use a different Charmap.
{
}

Histogram::~Histogram (void)
//
// The pointer members point to the mapped file and do not need to be freed.
{
}

/****************************************************************/

Language::Language (PLCDetect pL, int nLangID, int nCodePages, int nRangeID)
: m_pLC(pL),
  m_nLangID(nLangID),
  m_nCodePages(nCodePages),
  m_nRangeID(nRangeID)
{
}

Language7Bit::Language7Bit (PLCDetect pL, int nLangID, int nCodePages)
: Language(pL, nLangID, nCodePages),
  m_pLangHistogram(NULL)
{
	memset ((void *)m_ppCodePageHistogram, 0, sizeof(m_ppCodePageHistogram));
}

Language7Bit::~Language7Bit (void)
{
	if (m_pLangHistogram)
		delete m_pLangHistogram;

	for (int i = 0; i < MAXSUBLANG; i++)
		if (m_ppCodePageHistogram[i])
			delete m_ppCodePageHistogram[i];
}

DWORD
Language7Bit::AddHistogram (PFileHistogramSection pHS, DWORD nBytes, int nIdx)
//
// Add the raw histogram at *pHS in the mapped file to this language object.  
// The histograms must be for 7-bit detection.
{
	DWORD hr = NO_ERROR;

	PHIdx pMap = m_pLC->GetMap( pHS->m_dwMappingID );

	if (nIdx == 0)
	{
		// The first histogram for a language is its language-detection table.

		if ( (m_pLangHistogram = new Histogram (pHS, pMap)) == NULL)
			return ERROR_OUTOFMEMORY;

		if ((hr = m_pLangHistogram->Validate (nBytes)) != NO_ERROR)
			return hr;
	}
	else
	{
		// Each subsequent histogram is a code page detection table.

		if (nIdx - 1 >= m_nCodePages)
			return ERROR_INTERNAL_DB_CORRUPTION;

		Histogram *pH;

		if ((pH = new Histogram (pHS, pMap)) == NULL)
			return ERROR_OUTOFMEMORY;

		if ((hr = pH->Validate (nBytes)) != NO_ERROR)
			return hr;

		m_ppCodePageHistogram[nIdx - 1] = pH;

		// Cache for the scoring vector math

		m_paHElt[nIdx - 1] = pH->Array();
	}

	return hr;
}

/****************************************************************/

Language8Bit::Language8Bit (PLCDetect pL, int nLangID, int nCodePages)
: Language(pL, nLangID, nCodePages)
{
	memset ((void *)m_ppHistogram, 0, sizeof(m_ppHistogram));
}

Language8Bit::~Language8Bit (void)
{
	for (int i = 0; i < MAXSUBLANG; i++)
		if (m_ppHistogram[i])
			delete m_ppHistogram[i];
}

DWORD
Language8Bit::AddHistogram (PFileHistogramSection pHS, DWORD nBytes, int nIdx)
//
// Add the raw histogram at *pHS to this language object.  
// This language is known to use 8-bit detection.
{
	DWORD hr = NO_ERROR;

	PHIdx pMap = m_pLC->GetMap( pHS->m_dwMappingID );

	// The histograms are the direct language-code page tables

	if (nIdx >= m_nCodePages)
		return ERROR_INTERNAL_DB_CORRUPTION;

	Histogram *pH;

	if ((pH = new Histogram (pHS, pMap)) == NULL)
		return ERROR_OUTOFMEMORY;

	if ((hr = pH->Validate (nBytes)) != NO_ERROR)
		return hr;

	m_ppHistogram[nIdx] = pH;

	return hr;
}

/****************************************************************/

LanguageUnicode::LanguageUnicode (PLCDetect pL, int nLangID, 
	int nSubLangs, int nRangeID)
: Language(pL, nLangID, nSubLangs, nRangeID)
{
	memset ((void *)m_ppSubLangHistogram, 0, sizeof(m_ppSubLangHistogram));
}

LanguageUnicode::~LanguageUnicode (void)
{
	for (int i = 0; i < MAXSUBLANG; i++)
		if (m_ppSubLangHistogram[i])
			delete m_ppSubLangHistogram[i];
}

DWORD
LanguageUnicode::AddHistogram (PFileHistogramSection pHS, DWORD nBytes, int nIdx)
{
	DWORD hr = NO_ERROR;

	// All histograms for are sublanguage detection

	if (nIdx >= m_nSubLangs)
		return ERROR_INTERNAL_DB_CORRUPTION;

	// Get the custom charmap used for scoring this sublanguage group

	PHIdx pMap = m_pLC->GetMap( pHS->m_dwMappingID );

	Histogram *pH;

	if ((pH = new Histogram (pHS, pMap)) == NULL)
		return ERROR_OUTOFMEMORY;

	if ((hr = pH->Validate (nBytes)) != NO_ERROR)
		return hr;

	m_ppSubLangHistogram[nIdx] = pH;

	m_paHElt[nIdx] = pH->Array();

	return hr;
}

/****************************************************************/

LCDetect::LCDetect (HMODULE hM)
: m_hModule(hM),
  m_nCharmaps(0),
  m_n7BitLanguages(0),
  m_n8BitLanguages(0),
  m_nUnicodeLanguages(0),
  m_n7BitLangsRead(0),
  m_n8BitLangsRead(0),
  m_nUnicodeLangsRead(0),
  m_nMapsRead(0),
  m_nHistogramsRead(0),
  m_nScoreIdx(0),
  m_pp7BitLanguages(NULL),
  m_pp8BitLanguages(NULL),
  m_ppUnicodeLanguages(NULL),
  m_ppCharmaps(NULL),
  m_pv(NULL),
  m_hmap(0),
  m_hf(0),
  m_pHU27Bit(0)
{
}

LCDetect::~LCDetect ()
{
    delete m_pHU27Bit;

    for (unsigned int i = 0; i < m_n7BitLanguages; i++)
        delete m_pp7BitLanguages[i];
    delete m_pp7BitLanguages;

    for (i = 0; i < m_n8BitLanguages; i++)
        delete m_pp8BitLanguages[i];
    delete m_pp8BitLanguages;

    for (i = 0; i < m_nUnicodeLanguages; i++)
        delete m_ppUnicodeLanguages[i];
    delete m_ppUnicodeLanguages;

    for (i = 0; i < m_nCharmaps; i++)
        delete m_ppCharmaps[i];
    delete m_ppCharmaps;

    if (m_pv)
        UnmapViewOfFile (m_pv);

    CloseHandle (m_hmap);
    CloseHandle (m_hf);
}

DWORD
LCDetect::Initialize7BitLanguage (PFileLanguageSection pLS, PLanguage *ppL)
//
// Set *ppL to the Language object created from this section.
{
	// nRecordCount is lang histogram (1) + # of code page histograms

	if ( m_n7BitLangsRead >= m_n7BitLanguages || pLS->m_dwRecordCount < 1)
		return ERROR_INTERNAL_DB_CORRUPTION;

	PLanguage7Bit pL = new Language7Bit (this, pLS->m_dwLangID, pLS->m_dwRecordCount - 1);

	if (pL == NULL)
		return ERROR_OUTOFMEMORY;


	// Each 7-bit lang uses one score index slot per code page.
	// The range starts with the 7-bit langs, since both the 8-bit
	// and Unicode langs follow it.

	if (m_n7BitLangsRead == 0 && m_nScoreIdx != 0)
		return ERROR_INTERNAL_DB_CORRUPTION;;

	pL->SetScoreIdx(m_nScoreIdx);

	m_nScoreIdx += pLS->m_dwRecordCount - 1;	// skip 1st record (Language)

	m_pp7BitLanguages[ m_n7BitLangsRead++ ] = pL;

	*ppL = pL;

	return NO_ERROR;
}

DWORD
LCDetect::Initialize8BitLanguage (PFileLanguageSection pLS, Language **ppL)
//
// Set *ppL to the Language object created from this section.
{
	// nRecordCount is # of combined language / code page histograms

	if ( m_n8BitLangsRead >= m_n8BitLanguages || pLS->m_dwRecordCount < 1)
		return ERROR_INTERNAL_DB_CORRUPTION;

	PLanguage8Bit pL = new Language8Bit (this, pLS->m_dwLangID, pLS->m_dwRecordCount);

	if (pL == NULL)
		return ERROR_OUTOFMEMORY;


	// The 8-bit score indices follow the 7-bit languages

	// Each 8-bit lang uses a score index slot for each of its code pages,
	// since all the code pages are scored in the initial scoring pass.
	// The number of slots is the number of code page histograms, which is
	// one less than the number of records following this language.

	pL->SetScoreIdx(m_nScoreIdx);
	m_nScoreIdx += pLS->m_dwRecordCount;


	m_pp8BitLanguages[ m_n8BitLangsRead++ ] = pL;

	*ppL = pL;

	return NO_ERROR;
}

DWORD
LCDetect::InitializeUnicodeLanguage (PFileLanguageSection pLS, Language **ppL)
//
// Set *ppL to the Language object created from this section.
{
	// nRecordCount is # of sublanguage histograms

	if ( m_nUnicodeLangsRead >= m_nUnicodeLanguages ||
		 pLS->m_dwUnicodeRangeID >= m_nUnicodeLanguages )
	{
		return ERROR_INTERNAL_DB_CORRUPTION;
	}

	PLanguageUnicode pL = new LanguageUnicode (this, pLS->m_dwLangID, 
						pLS->m_dwRecordCount, pLS->m_dwUnicodeRangeID);

	if (pL == NULL)
		return ERROR_OUTOFMEMORY;


	// The Unicode score indices follow the 7-bit languages, and overlay the
	// 8-bit slots since they aren't used at the same time.

	if (m_nUnicodeLangsRead == 0 && GetN8BitLanguages() > 0)
		m_nScoreIdx = Get8BitLanguage(0)->GetScoreIdx();

	// Each Unicode entry uses exactly one score index.  SBCS subdetection
	// (Latin group) uses the slots for the corresponding 7-bit languages,
	// and Unicode subdetection (CJK) uses the slots already defined for the
	// Unicode sub-languages.

	pL->SetScoreIdx(m_nScoreIdx);

	m_nScoreIdx++;

	// For Unicode, the range ID is used as the Language array index.

	m_ppUnicodeLanguages[ pLS->m_dwUnicodeRangeID ] = pL;
	m_nUnicodeLangsRead++;

	*ppL = pL;

	return NO_ERROR;
}

DWORD
LCDetect::LoadLanguageSection (void *pv, int nSectionSize, PLanguage *ppL)
//
// A language section begins the definition of data for a language.
// Each language has exactly one of these records.  One or more
// histogram sections follow each language, and are always associated
// with the language of the preceding language section.
//
// Set *ppL to the Language object created from this section.
{
	DWORD hr = NO_ERROR;

	PFileLanguageSection pLS;

	pLS = (PFileLanguageSection)&((char *)pv)[sizeof(FileSection)];

	switch ( pLS->m_dwDetectionType ) {

	case DETECT_7BIT:
		hr = Initialize7BitLanguage (pLS, ppL);
		break;

	case DETECT_8BIT:
		hr = Initialize8BitLanguage (pLS, ppL);
		break;

	case DETECT_UNICODE:
		hr = InitializeUnicodeLanguage (pLS, ppL);
		break;
	}

	return hr;
}

DWORD
LCDetect::LoadHistogramSection (void *pv, int nSectionSize, Language *pL)
{
	PFileHistogramSection pHS;

	pHS = (PFileHistogramSection)&((char *)pv)[sizeof(FileSection)];

	int nBytes = nSectionSize - sizeof(FileSection) - sizeof(*pHS);

	return pL->AddHistogram ( pHS, nBytes, m_nHistogramsRead++);
}

DWORD
LCDetect::LoadMapSection (void *pv, int nSectionSize)
{
	PFileMapSection pMS;

	pMS = (PFileMapSection)&((char *)pv)[sizeof(FileSection)];

	int nBytes = nSectionSize - sizeof(FileSection) - sizeof(*pMS);

	if (m_nMapsRead >= m_nCharmaps)
		return ERROR_INTERNAL_DB_CORRUPTION;

	PCharmap pM = new Charmap (pMS);

	if (pM == NULL)
		return ERROR_OUTOFMEMORY;

	m_ppCharmaps[ m_nMapsRead++ ]  = pM;

	return NO_ERROR;
}

DWORD
LCDetect::BuildState (DWORD nFileSize)
//
// Build the detection structures from the mapped training file image at *m_pv
{
	PLanguage pL;
	PFileHeader pFH;
	PFileSection pFS;

	DWORD hr = NO_ERROR;

	// Validate header

	pFH = (PFileHeader) m_pv;

	if ( nFileSize < sizeof(*pFH) || 
		 pFH->m_dwAppSig != APP_SIGNATURE ||
		 pFH->m_dwVersion != APP_VERSION ||
		 pFH->m_dwHdrSizeBytes >= nFileSize ||
		 pFH->m_dwN7BitLanguages == 0 ||
		 pFH->m_dwN8BitLanguages == 0 ||
		 pFH->m_dwNUnicodeLanguages == 0 ||
		 pFH->m_dwNCharmaps == 0 )
	{
		return ERROR_INTERNAL_DB_CORRUPTION;
	}

	// Allocate language pointer table per header

	m_n7BitLanguages = pFH->m_dwN7BitLanguages;
	m_pp7BitLanguages = new PLanguage7Bit [m_n7BitLanguages];

	m_n8BitLanguages = pFH->m_dwN8BitLanguages;
	m_pp8BitLanguages = new PLanguage8Bit [m_n8BitLanguages];

	m_nUnicodeLanguages = pFH->m_dwNUnicodeLanguages;
	m_ppUnicodeLanguages = new PLanguageUnicode [m_nUnicodeLanguages];

	m_nCharmaps = pFH->m_dwNCharmaps;
	m_ppCharmaps = new PCharmap [m_nCharmaps];

	if ( m_pp7BitLanguages == NULL || 
		 m_pp8BitLanguages == NULL || 
		 m_ppUnicodeLanguages == NULL ||
		 m_ppCharmaps == NULL )
	{
		return ERROR_OUTOFMEMORY;
	}

	// Clear, because not all slots may be assigned
	memset (m_ppUnicodeLanguages, 0, sizeof(PLanguageUnicode) * m_nUnicodeLanguages);

	// Remember other header info

	m_LCDConfigureDefault.nMin7BitScore = pFH->m_dwMin7BitScore;
	m_LCDConfigureDefault.nMin8BitScore = pFH->m_dwMin8BitScore;
	m_LCDConfigureDefault.nMinUnicodeScore = pFH->m_dwMinUnicodeScore;
	m_LCDConfigureDefault.nRelativeThreshhold = pFH->m_dwRelativeThreshhold;
	m_LCDConfigureDefault.nDocPctThreshhold = pFH->m_dwDocPctThreshhold;
	m_LCDConfigureDefault.nChunkSize = pFH->m_dwChunkSize;

	// Position to first section

	pFS = (PFileSection) &((char *)m_pv)[pFH->m_dwHdrSizeBytes];

	// Read and process each file section

	while ( hr == NO_ERROR ) {

		// check alignment

		if (((DWORD_PTR)pFS & 3) != 0) {
			hr = ERROR_INTERNAL_DB_CORRUPTION;
			break;
		}

		// zero-length section marks end of data

		if (pFS->m_dwSizeBytes == 0)
			break;

		if ( &((char *)pFS)[pFS->m_dwSizeBytes] >= &((char *)m_pv)[nFileSize]) {
			hr = ERROR_INTERNAL_DB_CORRUPTION;
			break;
		}

		switch ( pFS->m_dwType ) {

		case SECTION_TYPE_LANGUAGE:								// sets pL
			hr = LoadLanguageSection ((void*)pFS, pFS->m_dwSizeBytes, &pL);
			m_nHistogramsRead = 0;
			break;

		case SECTION_TYPE_HISTOGRAM:							// uses pL
			hr = LoadHistogramSection ((void*)pFS, pFS->m_dwSizeBytes, pL);
			break;

		case SECTION_TYPE_MAP:
			hr = LoadMapSection ((void*)pFS, pFS->m_dwSizeBytes);
			break;

		default:					// ignore unrecognized sections
			break;
		}

		pFS = (PFileSection) &((char *)pFS)[pFS->m_dwSizeBytes];
	}

	if (hr != NO_ERROR)
		return hr;

	if ( m_nMapsRead != m_nCharmaps )
		return ERROR_INTERNAL_DB_CORRUPTION;


	// Set up quick-reference arrays used by the scoring inner loops

	for (unsigned int i = 0; i < GetN7BitLanguages(); i++)
		m_paHElt7Bit[i] = Get7BitLanguage(i)->GetLangHistogram()->Array();

	m_nHElt8Bit = 0;
	for (i = 0; i < GetN8BitLanguages(); i++) 
	{
		PLanguage8Bit pL = Get8BitLanguage(i);

		for (int j = 0; j < pL->NCodePages(); j++)
			m_paHElt8Bit[m_nHElt8Bit++] = pL->GetHistogram(j)->Array();
	}

	// Set up the Histogram used for ScoreVectorW() for scoring Unicode
	// text for 7-bit language detection.  Clone the first 7-bit language
	// histogram and replace its map with CHARMAP_U27BIT.

	m_pHU27Bit = new Histogram ( *Get7BitLanguage(0)->GetLangHistogram(),
								 GetMap(CHARMAP_U27BIT));

	return hr;
}


DWORD
LCDetect::LoadState (void)
//
// Overall initialization and state loading.  Open the compiled training
// file from its fixed location in the System32 directory, and assemble
// in-memory detection tables from its contents.
{
	DWORD hr = NO_ERROR;
	DWORD nFileSize;
#define MODULENAMELEN 100
	char szFilename[MODULENAMELEN+50], *p;

	// Find out if NT or Windows

	OSVERSIONINFOA OSVersionInfo;
	int nOSWinNT = 0;
	OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFOA );
	if ( GetVersionExA( &OSVersionInfo ) )
		nOSWinNT = OSVersionInfo.dwPlatformId;

	// Open the training data file,
	// look in the directory that contains the DLL.

	if (GetModuleFileNameA (m_hModule, szFilename, MODULENAMELEN) == 0)
		return GetLastError();

	if ( (p = strrchr (szFilename, '\\')) != NULL ||
		 (p = strrchr (szFilename, ':')) != NULL )
	{
		*++p = 0;
	}
	else
		*szFilename = 0;
	strcat (szFilename, DETECTION_DATA_FILENAME);

    if ((m_hf = CreateFileA (szFilename, GENERIC_READ, FILE_SHARE_READ, 
                    NULL, OPEN_EXISTING, 
                    FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) 
    {
        return E_FAIL;
    }

	if ((nFileSize = GetFileSize (m_hf, NULL)) == 0xffffffff) {
		hr = GetLastError();
		CloseHandle (m_hf);
		return hr;
	}

	// Virtual-map the file

	if ( nOSWinNT == VER_PLATFORM_WIN32_NT )
		m_hmap = CreateFileMapping (m_hf, NULL, PAGE_READONLY, 0, nFileSize, NULL);
	else
		m_hmap = CreateFileMappingA (m_hf, NULL, PAGE_READONLY, 0, nFileSize, NULL);

	if (m_hmap == NULL) {
		hr = GetLastError();
		CloseHandle (m_hf);
		return hr;
	}

	if ((m_pv = MapViewOfFile (m_hmap, FILE_MAP_READ, 0, 0, 0 )) == NULL) {
		hr = GetLastError();
		CloseHandle (m_hmap);
		CloseHandle (m_hf);
		return hr;
	}
		
	// Build the in-memory structures from the file

	hr = BuildState (nFileSize);

	return hr;
}

/****************************************************************/
