/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// FILE: Dict.cpp 
//-----------------------------------------------------------------------------
//                                                                          
//                       Westwood Studios Pacific.                          
//                                                                          
//                       Confidential Information					         
//                Copyright (C) 2001 - All Rights Reserved                  
//                                                                          
//-----------------------------------------------------------------------------
//
// Project:    RTS3
//
// File name:  Dict.cpp
//
// Created:    Steven Johnson, November 2001
//
// Desc:       General-purpose dictionary class
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////

#include "PreRTS.h"	// This must go first in EVERY cpp file in the GameEngine

#include "Common/Dict.h"
#include "Common/GameMemory.h"

// -----------------------------------------------------
void Dict::DictPair::copyFrom(DictPair* that)
{
	Dict::DataType curType = this->getType();
	Dict::DataType newType = that->getType();
	if (curType != newType)
	{
		clear();
	}

	switch(newType)
	{
		case DICT_BOOL:
		case DICT_INT:
		case DICT_REAL:
			*this = *that;
			break;
		case DICT_ASCIISTRING:
			this->m_key = that->m_key;
			*this->asAsciiString() = *that->asAsciiString();
			break;
		case DICT_UNICODESTRING:
			this->m_key = that->m_key;
			*this->asUnicodeString() = *that->asUnicodeString();
			break;
	}
}

// -----------------------------------------------------
void Dict::DictPair::clear()
{
	switch (getType())
	{
		case DICT_BOOL:
		case DICT_INT:
		case DICT_REAL:
			m_value = 0;
			break;
		case DICT_ASCIISTRING:
			asAsciiString()->clear();
			break;
		case DICT_UNICODESTRING:
			asUnicodeString()->clear();
			break;
	}
}

// -----------------------------------------------------
void Dict::DictPair::setNameAndType(NameKeyType key, Dict::DataType type)
{
	Dict::DataType curType = getType();
	if (curType != type)
	{
		clear();
	}
	m_key = createKey(key, type);
}

// -----------------------------------------------------
#ifdef _DEBUG
void Dict::validate() const
{
	if (!m_data) return;
	DEBUG_ASSERTCRASH(m_data->m_refCount > 0, ("m_refCount is zero"));
	DEBUG_ASSERTCRASH(m_data->m_refCount < 32000, ("m_refCount is suspiciously large"));
	DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated > 0, ("m_numPairsAllocated is zero"));
	DEBUG_ASSERTCRASH(m_data->m_numPairsUsed >= 0, ("m_numPairsUsed is neg"));
	DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated >= m_data->m_numPairsUsed, ("m_numPairsAllocated too small"));
	DEBUG_ASSERTCRASH(m_data->m_numPairsAllocated < 1024, ("m_numPairsAllocated suspiciously large"));
}
#endif

// -----------------------------------------------------
Dict::DictPair* Dict::findPairByKey(NameKeyType key) const
{
	DEBUG_ASSERTCRASH(key != NAMEKEY_INVALID, ("invalid namekey!"));
	DEBUG_ASSERTCRASH((UnsignedInt)key < (1L<<23), ("namekey too large!"));
	if (!m_data)
		return NULL;
	DictPair* base = m_data->peek();
	Int minIdx = 0;
	Int maxIdx = m_data->m_numPairsUsed;
	while (minIdx < maxIdx) 
	{
		Int midIdx = (((minIdx + maxIdx) - 1) >> 1);
		DictPair* mid = base + midIdx;
		NameKeyType midKey = mid->getName();
		if (key > midKey)
			minIdx = midIdx + 1;
		else if (key < midKey)
			maxIdx = midIdx;
		else
			return mid;
	}

	return NULL;
}

// -----------------------------------------------------
Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPair *pairToTranslate)
{
	if (m_data &&
			m_data->m_refCount == 1 &&
			m_data->m_numPairsAllocated >= numPairsNeeded)
	{
		// no buffer manhandling is needed (it's already large enough, and unique to us)
		return pairToTranslate;
	}

	if (numPairsNeeded > MAX_LEN)
		throw ERROR_OUT_OF_MEMORY;

	Dict::DictPairData* newData = NULL;
	if (numPairsNeeded > 0)
	{
		int minBytes = sizeof(Dict::DictPairData) + numPairsNeeded*sizeof(Dict::DictPair);
		int actualBytes = TheDynamicMemoryAllocator->getActualAllocationSize(minBytes);
		// note: be certain to alloc with zero; we'll take advantage of the fact that all-zero
		// is a bit-pattern that happens to init all our pairs to legal values: 
		// type BOOL, key INVALID, value FALSE.
		newData = (Dict::DictPairData*)TheDynamicMemoryAllocator->allocateBytes(actualBytes, "Dict::ensureUnique");
		newData->m_refCount = 1;
		newData->m_numPairsAllocated = (actualBytes - sizeof(Dict::DictPairData))/sizeof(Dict::DictPair);
		newData->m_numPairsUsed = 0;

		if (preserveData && m_data)
		{
			Dict::DictPair* src = m_data->peek();
			Dict::DictPair* dst = newData->peek();
			for (Int i = 0; i < m_data->m_numPairsUsed; ++i, ++src, ++dst)
				dst->copyFrom(src);
			newData->m_numPairsUsed = m_data->m_numPairsUsed;
		}
	}

	Int delta;
	if (pairToTranslate && m_data)
		delta = pairToTranslate - m_data->peek();

	releaseData();
	m_data = newData;

	if (pairToTranslate && m_data)
		pairToTranslate = m_data->peek() + delta;

	return pairToTranslate;
}


// -----------------------------------------------------
void Dict::clear()
{
	releaseData();
	m_data = NULL;
}

// -----------------------------------------------------
void Dict::releaseData()
{
	if (m_data)
	{
		if (--m_data->m_refCount == 0)
		{
			Dict::DictPair* src = m_data->peek();
			for (Int i = 0; i < m_data->m_numPairsUsed; ++i, ++src)
				src->clear();
			TheDynamicMemoryAllocator->freeBytes(m_data);
		}
		m_data = 0;
	}
}

// -----------------------------------------------------
Dict::Dict(Int numPairsToPreAllocate) : m_data(0)
{

	/*
		This class plays some skanky games, in the name of memory and code
		efficiency; it assumes all the data types will fit into a pointer.
		This is currently true, but if that assumption ever changes, all hell
		will break loose. So we do a quick check to assure this...
	*/
	DEBUG_ASSERTCRASH(sizeof(Bool) <= sizeof(void*) &&
										sizeof(Int) <= sizeof(void*) &&
										sizeof(Real) <= sizeof(void*) &&
										sizeof(AsciiString) <= sizeof(void*) &&
										sizeof(UnicodeString) <= sizeof(void*), ("oops, this code needs attention"));

	if (numPairsToPreAllocate)
		ensureUnique(numPairsToPreAllocate, false, NULL);	// will throw on error
}

// -----------------------------------------------------
Dict& Dict::operator=(const Dict& src)
{
	validate();
	if (&src != this)
	{
		releaseData();
		m_data = src.m_data;
		if (m_data)
			++m_data->m_refCount;
	}
	validate();
	return *this;
}

// -----------------------------------------------------
Dict::DataType Dict::getType(NameKeyType key) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair)
		return pair->getType();
	return DICT_NONE;
}

// -----------------------------------------------------
Bool Dict::getBool(NameKeyType key, Bool *exists/*=NULL*/) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair && pair->getType() == DICT_BOOL)
	{
		if (exists) *exists = true;
		return *pair->asBool();
	}
	DEBUG_ASSERTCRASH(exists != NULL, ("dict key missing, or of wrong type\n"));	// only assert if they didn't check result
	if (exists) *exists = false;
	return false;
}

// -----------------------------------------------------
Int Dict::getInt(NameKeyType key, Bool *exists/*=NULL*/) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair && pair->getType() == DICT_INT)
	{
		if (exists) *exists = true;
		return *pair->asInt();
	}
	DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n"));	// only assert if they didn't check result
	if (exists) *exists = false;
	return 0;
}

// -----------------------------------------------------
Real Dict::getReal(NameKeyType key, Bool *exists/*=NULL*/) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair && pair->getType() == DICT_REAL)
	{
		if (exists) *exists = true;
		return *pair->asReal();
	}
	DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n"));	// only assert if they didn't check result
	if (exists) *exists = false;
	return 0.0f;
}

// -----------------------------------------------------
AsciiString Dict::getAsciiString(NameKeyType key, Bool *exists/*=NULL*/) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair && pair->getType() == DICT_ASCIISTRING)
	{
		if (exists) *exists = true;
		return *pair->asAsciiString();
	}
	DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n"));	// only assert if they didn't check result
	if (exists) *exists = false;
	return AsciiString::TheEmptyString;
}

// -----------------------------------------------------
UnicodeString Dict::getUnicodeString(NameKeyType key, Bool *exists/*=NULL*/) const
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair && pair->getType() == DICT_UNICODESTRING)
	{
		if (exists) *exists = true;
		return *pair->asUnicodeString();
	}
	DEBUG_ASSERTCRASH(exists != NULL,("dict key missing, or of wrong type\n"));	// only assert if they didn't check result
	if (exists) *exists = false;
	return UnicodeString::TheEmptyString;
}

// -----------------------------------------------------
Bool Dict::getNthBool(Int n) const
{
	validate();
	DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
	if (m_data)
	{
		DictPair* pair = &m_data->peek()[n];
		if (pair && pair->getType() == DICT_BOOL)
			return *pair->asBool();
	}
	DEBUG_CRASH(("dict key missing, or of wrong type\n"));
	return false;
}

// -----------------------------------------------------
Int Dict::getNthInt(Int n) const
{
	validate();
	DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
	if (m_data)
	{
		DictPair* pair = &m_data->peek()[n];
		if (pair && pair->getType() == DICT_INT)
			return *pair->asInt();
	}
	DEBUG_CRASH(("dict key missing, or of wrong type\n"));
	return 0;
}

// -----------------------------------------------------
Real Dict::getNthReal(Int n) const
{
	validate();
	DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
	if (m_data)
	{
		DictPair* pair = &m_data->peek()[n];
		if (pair && pair->getType() == DICT_REAL)
			return *pair->asReal();
	}
	DEBUG_CRASH(("dict key missing, or of wrong type\n"));
	return 0.0f;
}

// -----------------------------------------------------
AsciiString Dict::getNthAsciiString(Int n) const
{
	validate();
	DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
	if (m_data)
	{
		DictPair* pair = &m_data->peek()[n];
		if (pair && pair->getType() == DICT_ASCIISTRING)
			return *pair->asAsciiString();
	}
	DEBUG_CRASH(("dict key missing, or of wrong type\n"));
	return AsciiString::TheEmptyString;
}

// -----------------------------------------------------
UnicodeString Dict::getNthUnicodeString(Int n) const
{
	validate();
	DEBUG_ASSERTCRASH(n >= 0 && n < getPairCount(), ("n out of range\n"));
	if (m_data)
	{
		DictPair* pair = &m_data->peek()[n];
		if (pair && pair->getType() == DICT_UNICODESTRING)
			return *pair->asUnicodeString();
	}
	DEBUG_CRASH(("dict key missing, or of wrong type\n"));
	return UnicodeString::TheEmptyString;
}

// -----------------------------------------------------
Dict::DictPair *Dict::setPrep(NameKeyType key, Dict::DataType type)
{
	DictPair* pair = findPairByKey(key);
	Int pairsNeeded = getPairCount();
	if (!pair)
		++pairsNeeded;
	pair = ensureUnique(pairsNeeded, true, pair);	
	if (!pair)
	{
		pair = &m_data->peek()[m_data->m_numPairsUsed++];
	}
	pair->setNameAndType(key, type);
	DEBUG_ASSERTCRASH(pair, ("pair must not be null here"));
	return pair;
}

// -----------------------------------------------------
void Dict::sortPairs()
{
	if (!m_data)
		return;

	// yer basic shellsort.
	for (Int gap = m_data->m_numPairsUsed >> 1; gap > 0; gap >>= 1) 
	{
		for (Int i = gap; i < m_data->m_numPairsUsed; i++) 
		{
			for (Int j = i - gap; j >= 0; j -= gap) 
			{
				DictPair* a = m_data->peek() + j;
				DictPair* b = m_data->peek() + j + gap;
				if (a->getName() > b->getName()) 
				{
					DictPair tmp = *a;
					*a = *b;
					*b = tmp;
				} 
				else 
				{
					break;
				}
			}
		}
	}
}

// -----------------------------------------------------
void Dict::setBool(NameKeyType key, Bool value)
{
	validate();
	DictPair* pair = setPrep(key, DICT_BOOL);
	*pair->asBool() = value;
	sortPairs();
	validate();
}

// -----------------------------------------------------
void Dict::setInt(NameKeyType key, Int value)
{
	validate();
	DictPair* pair = setPrep(key, DICT_INT);
	*pair->asInt() = value;
	sortPairs();
	validate();
}

// -----------------------------------------------------
void Dict::setReal(NameKeyType key, Real value)
{
	validate();
	DictPair* pair = setPrep(key, DICT_REAL);
	*pair->asReal() = value;
	sortPairs();
	validate();
}

// -----------------------------------------------------
void Dict::setAsciiString(NameKeyType key, const AsciiString& value)
{
	validate();
	DictPair* pair = setPrep(key, DICT_ASCIISTRING);
	*pair->asAsciiString() = value;
	sortPairs();
	validate();
}

// -----------------------------------------------------
void Dict::setUnicodeString(NameKeyType key, const UnicodeString& value)
{
	validate();
	DictPair* pair = setPrep(key, DICT_UNICODESTRING);
	*pair->asUnicodeString() = value;
	sortPairs();
	validate();
}

// -----------------------------------------------------
Bool Dict::remove(NameKeyType key)
{
	validate();
	DictPair* pair = findPairByKey(key);
	if (pair)
	{
		pair = ensureUnique(m_data->m_numPairsUsed, true, pair);	
		pair->setNameAndType((NameKeyType)0x7fffffff, DICT_BOOL);
		sortPairs();
		--m_data->m_numPairsUsed;
		validate();
		return true;
	}
	DEBUG_CRASH(("dict key missing in remove\n"));
	return false;
}

// -----------------------------------------------------
void Dict::copyPairFrom(const Dict& that, NameKeyType key)
{
	this->validate();
	DictPair* thatPair = that.findPairByKey(key);
	if (thatPair)
	{
		DictPair* thisPair = this->setPrep(key, thatPair->getType());
		thisPair->copyFrom(thatPair);
		this->sortPairs();
	}
	else
	{
		if (this->findPairByKey(key))
			this->remove(key);
	}
	this->validate();
}
