#include <string>
#include <map>
#include "StringVar.h"
#include "GameForms.h"
#include <algorithm>

StringVar::StringVar(const char* in_data, UInt32 in_refID)
{
	data = std::string(in_data);
	owningModIndex = in_refID >> 24;
}

const char* StringVar::GetCString()
{
	return data.c_str();
}

void StringVar::Set(const char* newString)
{
	data = std::string(newString);
}

SInt32 StringVar::Compare(char* rhs, bool caseSensitive)
{
	SInt32 cmp = 0;
	if (!caseSensitive)
	{
		cmp = _stricmp(data.c_str(), rhs);
		if (cmp > 0)
			return -1;
		else if (cmp < 0)
			return 1;
		else
			return 0;
	}
	else
	{
		std::string str2(rhs);
		if (data == str2)
			return 0;
		else if (data > str2)
			return -1;
		else
			return 1;
	}
}

void StringVar::Insert(char* subString, UInt32 insertionPos)
{
	if (insertionPos < GetLength())
		data.insert(insertionPos, subString);
	else if (insertionPos == GetLength())
		data.append(subString);
}

#pragma warning(disable : 4996)	// disable checked iterator warning for std::transform with char*
UInt32 StringVar::Find(char* subString, UInt32 startPos, UInt32 numChars, bool bCaseSensitive)
{
	UInt32 pos = -1;

	if (numChars + startPos >= GetLength())
		numChars = GetLength() - startPos;

	if (startPos < GetLength())
	{
		std::string source = data.substr(startPos, numChars);
		if (!bCaseSensitive)
		{
			std::transform(source.begin(), source.end(), source.begin(), tolower);
			std::transform(subString, subString + strlen(subString), subString, tolower);
		}

		 //pos = data.substr(startPos, numChars).find(subString);	//returns -1 if not found
		pos = source.find(subString);
		if (pos != -1)
			pos += startPos;
	}

	return pos;
}

UInt32 StringVar::Count(char* subString, UInt32 startPos, UInt32 numChars, bool bCaseSensitive)
{
	if (numChars + startPos >= GetLength())
		numChars = GetLength() - startPos;

	if (startPos >= GetLength())
		return 0;

	std::string source = data.substr(startPos, numChars);	//only count occurences beginning before endPos
	UInt32 subStringLen = strlen(subString);
	if (!subStringLen)
		return 0;

	if (!bCaseSensitive)
	{
		std::transform(source.begin(), source.end(), source.begin(), tolower);
		std::transform(subString, subString + strlen(subString), subString, tolower);
	}

	UInt32 strIdx = 0;
	UInt32 count = 0;
	while (strIdx < GetLength() && ((strIdx = source.find(subString, strIdx)) != -1))
	{
		count++;
		strIdx += subStringLen;
	}

	return count;
}
#pragma warning(default : 4996)

UInt32 StringVar::GetLength()
{
	return data.length();
}

UInt32 StringVar::Replace(char* toReplace, char* replaceWith, UInt32 startPos, UInt32 numChars, UInt32 numToReplace)
{
	// calc length of substring
	if (startPos >= GetLength())
		return 0;
	else if (numChars + startPos > GetLength())
		numChars = GetLength() - startPos;

	UInt32 numReplaced = 0;
	UInt32 replacementLen = strlen(replaceWith);
	UInt32 toReplaceLen = strlen(toReplace);

	// create substring
	std::string srcStr = data.substr(startPos, numChars);

	// remove substring from original string
	data.erase(startPos, numChars);

	UInt32 strIdx = 0;
	while (numReplaced < numToReplace && (strIdx = srcStr.find(toReplace, strIdx)) != -1)
	{
		numReplaced++;
		srcStr.erase(strIdx, toReplaceLen);
		if (strIdx == srcStr.length())
		{
			srcStr.append(replaceWith);
			break;						// reached end of string so all done
		}
		else
		{
			srcStr.insert(strIdx, replaceWith);
			strIdx += replacementLen;
		}
	}

	// paste altered string back into original string
	if (startPos == GetLength())
		data.append(srcStr);
	else
		data.insert(startPos, srcStr);

	return numReplaced;
}

void StringVar::Erase(UInt32 startPos, UInt32 numChars)
{
	if (numChars + startPos >= GetLength())
		numChars = GetLength() - startPos;

	if (startPos < GetLength())
		data.erase(startPos, numChars);
}

std::string StringVar::SubString(UInt32 startPos, UInt32 numChars)
{
	if (numChars + startPos >= GetLength())
		numChars = GetLength() - startPos;

	if (startPos < GetLength())
		return data.substr(startPos, numChars);
	else
		return "";
}

UInt8 StringVar::GetOwningModIndex()
{
	return owningModIndex;
}

UInt32 StringVar::GetCharType(char ch)
{
	UInt32 charType = 0;
	if (isalpha(ch))
		charType |= kCharType_Alphabetic;
	if (isdigit(ch))
		charType |= kCharType_Digit;
	if (ispunct(ch))
		charType |= kCharType_Punctuation;
	if (isprint(ch))
		charType |= kCharType_Printable;
	if (isupper(ch))
		charType |= kCharType_Uppercase;

	return charType;
}

char StringVar::At(UInt32 charPos)
{
	if (charPos < GetLength())
		return data[charPos];
	else
		return -1;
}

StringVarMap::StringVarMap()
{
	//
}

StringVarMap::~StringVarMap()
{
	//
}

UInt32 StringVarMap::GetUnusedID()
{	//could probably be made faster
	UInt32 id = 1;
	while (strings.find(id) != strings.end())
	{
		id++;
	}
	return id;
}

bool StringVarMap::StringExists(UInt32 strID)
{
	if (strID == 0)
		return false;
	else if (Get(strID))
		return true;
	else
	{
		_MESSAGE("Error: initialized string %d not found", strID);
		return false;
	}
}

StringVar* StringVarMap::Get(UInt32 strID)
{
	if (strID == 0 || strings.find(strID) == strings.end())
		return NULL;
	else
		return strings[strID];
}

UInt32 StringVarMap::Add(UInt8 varModIndex, const char* data)
{
	UInt32 strID = GetUnusedID();
	strings[strID] = new StringVar(data, varModIndex);

	return strID;
}

void StringVarMap::Save(OBSESerializationInterface* intfc)
{
	intfc->OpenRecord('STVS', 0);

	for (std::map<UInt32, StringVar*>::iterator iter = strings.begin();
			iter != strings.end();
			iter++)
	{
		intfc->OpenRecord('STVR', 0);
		UInt8 modIndex = iter->second->GetOwningModIndex();
		intfc->WriteRecordData(&modIndex, sizeof(UInt8));
		intfc->WriteRecordData(&iter->first, sizeof(UInt32));
		UInt16 len = iter->second->GetLength();
		intfc->WriteRecordData(&len, sizeof(len));
		intfc->WriteRecordData(iter->second->GetCString(), len);
	}

	intfc->OpenRecord('STVE', 0);
}

void StringVarMap::Load(OBSESerializationInterface* intfc)
{
	_MESSAGE("Loading strings");
	UInt32 type, length, version, stringID, tempRefID;
	UInt16 strLength;
	UInt8 modIndex;
	char buffer[kMaxMessageLength] = { 0 };

	Reset(intfc);
	bool bContinue = true;
	while (bContinue && intfc->GetNextRecordInfo(&type, &version, &length))
	{
		switch (type)
		{
		case 'STVE':			//end of block
			bContinue = false;
			break;
		case 'STVR':
			intfc->ReadRecordData(&modIndex, sizeof(modIndex));
			if (!intfc->ResolveRefID(modIndex << 24, &tempRefID))
			{
				_MESSAGE("RefID %08x resolved to %08x returns false", modIndex << 24, tempRefID);
				continue;
			}

			intfc->ReadRecordData(&stringID, sizeof(stringID));
			intfc->ReadRecordData(&strLength, sizeof(strLength));
			
			intfc->ReadRecordData(buffer, strLength);
			buffer[strLength] = 0;

			strings[stringID] = new StringVar(buffer, tempRefID);
			break;
		default:
			_MESSAGE("Error loading string map: unhandled chunk type %d", type);
			break;
		}
	}
}

void StringVarMap::Reset(OBSESerializationInterface* intfc)
{
	for (std::map<UInt32, StringVar*>::iterator iter = strings.begin();
			iter != strings.end();
			++iter)
	{
		delete iter->second;
	}

	strings.clear();
}

void StringVarMap::Delete(UInt32 strID)
{
	StringVar* var = Get(strID);
	if (var)
	{
		delete var;
		strings.erase(strID);
	}
}

StringVarMap g_StringMap;


bool AssignToStringVar(ParamInfo * paramInfo, void * arg1, TESObjectREFR * thisObj, UInt32 arg3, Script * scriptObj, ScriptEventList * eventList, double * result, UInt32 * opcodeOffsetPtr, const char* newValue)
{
	double strID = 0;
	UInt8 modIndex = 0;
	
	if (!ExtractSetStatementVar(scriptObj, eventList, arg1, &strID, &modIndex))
		return false;

	StringVar* strVar = g_StringMap.Get(strID);
	if (!newValue)		//if null pointer, assign an empty string
		newValue = "";

	if (strVar)
		strVar->Set(newValue);
	else
		strID = g_StringMap.Add(modIndex, newValue);

	*result = strID;
	if (IsConsoleMode())
	{
		if (strlen(newValue) < 480)
			Console_Print("Assigned string >> \"%s\"", newValue);
		else
			Console_Print("Assigned string (too long to print)");
	}

	return true;
}

const char* GetString(UInt32 stringID)
{
	StringVar* var = g_StringMap.Get(stringID);
	if (var)
		return var->GetCString();
	else
		return NULL;
}

void SetString(UInt32 stringID, const char* newVal)
{
	StringVar* var = g_StringMap.Get(stringID);
	if (var)
		var->Set(newVal);
}

UInt32 CreateString(const char* strVal, void* owningScript)
{
	Script* script = (Script*)owningScript;
	if (script)
		return g_StringMap.Add(script->GetModIndex(), strVal);
	else
		return 0;
}