#include "CachingHRCParser.h"

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#ifdef keyword
#undef keyword
#endif

#define QUALIFY_SCHEME(name, scheme)	\
	{\
		if ((name) && !(scheme))\
		{\
			String *schemeName = qualifyForeignName((name), QNT_SCHEME, true);\
			if (schemeName != null)\
			{\
				(scheme) = schemeHash.get(schemeName);\
				delete schemeName;\
			}\
		}\
	}


void CachingHRCParser::loadFileType(FileType *filetype)
{
	if (buf)
	{
		FriendlyFileTypeImpl *f = (FriendlyFileTypeImpl*)filetype;
		if (!f->typeLoaded && f->baseScheme)
		{
			curFileType = f;
			int pos = (int)f->baseScheme;
			deserialize(pos, f->baseScheme);
			f->typeLoaded = TRUE;
			f->loadDone = TRUE;
		}
	}
	else
		HRCParserImpl::loadFileType(filetype);
}

void CachingHRCParser::serializeToFile(const char *filename)
{
	schemesSerialized.clear();
	regionsSerialized.clear();
	curPos = 0;
	dryRun = TRUE;
	serializePrimitive('HRCC');
	serializeVector(fileTypeVector);
	processQueue();

	file = fopen(filename, "wb");
	if (!file)
		croak("Couldn't open file %s for writing", filename);

	schemesSerialized.clear();
	regionsSerialized.clear();
	curPos = 0;
	dryRun = FALSE;
	serializePrimitive('HRCC');
	serializeVector(fileTypeVector);
	processQueue();

	fclose(file);
}

void CachingHRCParser::deserializeFromFile(const char *filename)
{
	file = fopen(filename, "rb");
	if (!file)
		croak("Couldn't open file %s for reading", filename);

	struct stat s;
	if (fstat(fileno(file), &s) < 0)
	{
		fclose(file);
		croak("Couldn't stat file %s", filename);
	}

	bufSize = s.st_size;
	buf = new char[bufSize];
	fread(buf, sizeof(char), bufSize, file);
	fclose(file);

	int pos = 0;

	int magic;
	deserializePrimitive(pos, magic);
	if (magic != 'HRCC')
		croak("File %s has wrong format", filename);

	deserializeVector(pos, fileTypeVector);
}

void CachingHRCParser::serialize(const String *value)
{
	if (!value)
	{
		serializePrimitive((char)0xFF);
		return;
	}

	int len = value->length();
	if (len > 0xFFFF)
	{
		if (!dryRun)
			fclose(file);
		croak("Can't serialize a string that is longer than %i characters", 0xFFFF);
	}

	if (len <= 0xFD)
		serializePrimitive((char)len);
	else
	{
		serializePrimitive((char)0xFE);
		serializePrimitive((short)len);
	}

	if (!dryRun)
		fwrite(value->getChars(), sizeof(char), len, file);

	curPos += len;
}

void CachingHRCParser::deserialize(int &pos, String* &value)
{
	int len = *(unsigned char*)(buf + pos);
	pos += sizeof(unsigned char);
	if (len == 0xFF)
	{
		value = NULL;
		return;
	}
	else if (len == 0xFE)
	{
		len = *(unsigned short*)(buf + pos);
		pos += sizeof(unsigned short);
	}

	value = new DString(buf + pos, 0, len);
	pos += len;
}

void CachingHRCParser::deserialize(int &pos, const SString* &value)
{
	String* v;
	deserialize(pos, v);
	value = v ? new SString(v) : NULL;
}

void CachingHRCParser::serialize(const Vector<String*> &names, const Hashtable<String*> &values)
{
	int len = names.size();
	serializePrimitive(len);
	for (int i = 0; i < len; i++)
	{
		String *name = names.elementAt(i);
		serialize(name);
		serialize(values.get(name));
	}
}

void CachingHRCParser::deserialize(int &pos, Vector<String*> &names, Hashtable<String*> &values)
{
	int len;
	deserializePrimitive(pos, len);
	for (int i = 0; i < len; i++)
	{
		String *name, *value;
		deserialize(pos, name);
		deserialize(pos, value);
		names.addElement(name);
		values.put(name, value);
	}
}

void CachingHRCParser::serialize(const Region *value)
{
	if (regionsSerialized.get(value->getName()))
		return;

	regions.put(value->getName(), curPos);
	regionsSerialized.put(value->getName(), true);
	serializePrimitive((Region*)NULL);

	serialize(value->getName());
	serialize(value->getDescription());
	serializeQueued((Region*)value->getParent());
}

void CachingHRCParser::deserialize(int &pos, Region* &value)
{
	int origPos = pos;

	deserializePrimitive(pos, value);
	if (value)
		return;

	String* regionName;
	deserialize(pos, regionName);

	String* regionDescr;
	deserialize(pos, regionDescr);

	FriendlyRegion* v = new FriendlyRegion(regionName, regionDescr, null, regionNamesVector.size());

	value = v;
	*(Region**)(buf + origPos) = value;
	regionNamesVector.addElement(value);
	regionNamesHash.put(regionName, value);
	delete regionName;
	delete regionDescr;

	Region* parent;
	deserializePointer(pos, parent);
	v->parent = parent;
}

void CachingHRCParser::serialize(VirtualEntry *value)
{
	QUALIFY_SCHEME(value->virtSchemeName, value->virtScheme);
	QUALIFY_SCHEME(value->substSchemeName, value->substScheme);

	serializeQueued(value->virtScheme);
	serializeQueued(value->substScheme);
}

void CachingHRCParser::deserialize(int &pos, VirtualEntry* &value)
{
	DString s("");
	value = new VirtualEntry(&s, &s);
	delete value->virtSchemeName;
	delete value->substSchemeName;
	value->virtSchemeName = null;
	value->substSchemeName = null;

	deserializePointer(pos, value->virtScheme);
	deserializePointer(pos, value->substScheme);
}

void CachingHRCParser::serialize(const KeywordInfo *value)
{
	serialize((String*)value->keyword);
	serializePrimitive((char)(value->isSymbol ? 1 : 0));
	serializeQueued((Region*)value->region);
}

void CachingHRCParser::deserialize(int &pos, KeywordInfo* value)
{
	deserialize(pos, value->keyword);

	char isSymbol;
	deserializePrimitive(pos, isSymbol);
	value->isSymbol = isSymbol;

	Region* region;
	deserializePointer(pos, region);
	value->region = region;
}

void CachingHRCParser::serialize(const KeywordList *value)
{
	if (!value || !value->num)
	{
		serializePrimitive((int)0);
		return;
	}

	serializePrimitive(value->num);
	serializePrimitive((char)(value->matchCase ? 1 : 0));
	for (int i = 0; i < value->num; i++)
		serialize(value->kwList + i);
}

void CachingHRCParser::deserialize(int &pos, KeywordList* &value)
{
	int num;
	deserializePrimitive(pos, num);
	if (!num)
	{
		value = NULL;
		return;
	}

	value = new KeywordList();
	value->firstChar = new CharacterClass();
	value->num = num;
	value->kwList = new KeywordInfo[num];
	value->minKeywordLength = 0x10000;

	char matchCase;
	deserializePrimitive(pos, matchCase);
	value->matchCase = matchCase;

	for (int i = 0; i < num; i++)
	{
		deserialize(pos, value->kwList + i);
		if (value->kwList[i].keyword)
		{
			value->firstChar->addChar((*value->kwList[i].keyword)[0]);
			if (!value->matchCase)
			{
				value->firstChar->addChar(Character::toLowerCase((*value->kwList[i].keyword)[0]));
				value->firstChar->addChar(Character::toUpperCase((*value->kwList[i].keyword)[0]));
				value->firstChar->addChar(Character::toTitleCase((*value->kwList[i].keyword)[0]));
			}
			if (value->minKeywordLength > value->kwList[i].keyword->length())
				value->minKeywordLength = value->kwList[i].keyword->length();
		}
	}
}

void CachingHRCParser::serialize(SchemeNode *value)
{
	serializePrimitive((char)value->type);

	if (value->type == SNT_SCHEME || value->type == SNT_INHERIT)
	{
		QUALIFY_SCHEME(value->schemeName, value->scheme);
		serializeQueued(value->scheme);
	}

	serializeVector(value->virtualEntryVector);

	if (value->type == SNT_KEYWORDS)
	{
		serialize(value->kwList);
		serialize(value->worddivString);
	}

	if (value->type == SNT_SCHEME || value->type == SNT_RE)
	{
		serializeQueued((Region*)value->region);

		for (int i = 0; i < REGIONS_NUM; i++)
			serializeQueued((Region*)value->regions[i]);

		for (int i = 0; i < NAMED_REGIONS_NUM; i++)
			serializeQueued((Region*)value->regionsn[i]);

		if (value->type == SNT_SCHEME)
		{
			for (int i = 0; i < REGIONS_NUM; i++)
				serializeQueued((Region*)value->regione[i]);

			for (int i = 0; i < NAMED_REGIONS_NUM; i++)
				serializeQueued((Region*)value->regionen[i]);
		}

		serialize(value->startString);
		if (value->type == SNT_SCHEME)
			serialize(value->endString);
	}

	serializePrimitive((char)(value->lowPriority ? 1 : 0));
	serializePrimitive((char)(value->lowContentPriority ? 1 : 0));
}

void CachingHRCParser::deserialize(int &pos, SchemeNode* &value)
{
	value = new SchemeNode();

	char type;
	deserializePrimitive(pos, type);
	value->type = (SchemeNodeType)type;

	if (value->type == SNT_SCHEME || value->type == SNT_INHERIT)
	{
		deserializePointer(pos, value->scheme);
		value->schemeName = NULL;
	}

	deserializeVector(pos, value->virtualEntryVector);

	if (value->type == SNT_KEYWORDS)
	{
		deserialize(pos, value->kwList);

		deserialize(pos, value->worddivString);
		value->worddiv = value->worddivString ? CharacterClass::createCharClass(*value->worddivString, 0, null) : null;
	}

	if (value->type == SNT_SCHEME || value->type == SNT_RE)
	{
		Region* region;

		deserializePointer(pos, region);
		value->region = region;

		for (int i = 0; i < REGIONS_NUM; i++)
		{
			deserializePointer(pos, region);
			value->regions[i] = region;
		}

		for (int i = 0; i < NAMED_REGIONS_NUM; i++)
		{
			deserializePointer(pos, region);
			value->regionsn[i] = region;
		}

		if (value->type == SNT_SCHEME)
		{
			for (int i = 0; i < REGIONS_NUM; i++)
			{
				deserializePointer(pos, region);
				value->regione[i] = region;
			}

			for (int i = 0; i < NAMED_REGIONS_NUM; i++)
			{
				deserializePointer(pos, region);
				value->regionen[i] = region;
			}
		}

		deserialize(pos, value->startString);
		value->start = value->startString ? new CRegExp(value->startString) : null;
		value->start->setPositionMoves(FALSE);

		if (value->type == SNT_SCHEME)
		{
			deserialize(pos, value->endString);
			if (value->endString)
			{
				value->end = new CRegExp();
				value->end->setPositionMoves(TRUE);
				value->end->setBackRE(value->start);
				value->end->setRE(value->endString);
			}
			else
				value->end = null;
		}
	}

	char priority;
	deserializePrimitive(pos, priority);
	value->lowPriority = priority;
	deserializePrimitive(pos, priority);
	value->lowContentPriority = priority;
}

void CachingHRCParser::serialize(const SchemeImpl *value)
{
	if (schemesSerialized.get(value->getName()))
		return;

	schemes.put(value->getName(), curPos);
	schemesSerialized.put(value->getName(), true);
	serializePrimitive((SchemeImpl*)NULL);

	FriendlySchemeImpl *v = (FriendlySchemeImpl*)value;
	serialize(v->schemeName);
	serializeVector(v->nodes);

	processQueue();
}

void CachingHRCParser::deserialize(int &pos, SchemeImpl* &value)
{
	int origPos = pos;

	deserializePrimitive(pos, value);
	if (value)
		return;

	String* schemeName;
	deserialize(pos, schemeName);

	FriendlySchemeImpl *v = new FriendlySchemeImpl(schemeName);
	v->fileType = curFileType;

	value = v;
	*(SchemeImpl**)(buf + origPos) = value;
	schemeHash.put(schemeName, value);
	delete schemeName;

	deserializeVector(pos, v->nodes);
}

void CachingHRCParser::serialize(const FileTypeImpl *value)
{
	FriendlyFileTypeImpl *v = (FriendlyFileTypeImpl*) value;

	serialize(v->name);

	v->getBaseScheme();
	serializeQueued(v->baseScheme);

	serialize(v->paramVector, v->paramDefaultHash);
}

void CachingHRCParser::deserialize(int &pos, FileTypeImpl* &value)
{
	FriendlyFileTypeImpl *v = new FriendlyFileTypeImpl(this);

	deserialize(pos, v->name);

	int baseScheme;
	deserializePrimitive(pos, baseScheme);
	v->baseScheme = (SchemeImpl*)baseScheme;

	deserialize(pos, v->paramVector, v->paramDefaultHash);

	v->protoLoaded = TRUE;
	v->typeLoaded = FALSE;
	v->loadDone = FALSE;
	v->loadBroken = FALSE;

	value = v;
	fileTypeHash.put(v->name, value);
}

void CachingHRCParser::serializeQueued(SchemeImpl *value)
{
	if (value)
	{
		if (!schemesSerialized.get(value->getName()))
			schemeQueue.addElement(value);

		serializePrimitive(schemes.get(value->getName()));
	}
	else
		serializePrimitive((int)0);
}

void CachingHRCParser::serializeQueued(Region *value)
{
	if (value)
	{
		if (!regionsSerialized.get(value->getName()))
			regionQueue.addElement(value);

		serializePrimitive(regions.get(value->getName()));
	}
	else
		serializePrimitive((int)0);

}

void CachingHRCParser::processQueue()
{
	while (regionQueue.size())
	{
		int i = regionQueue.size() - 1;
		Region* region = regionQueue.elementAt(i);
		regionQueue.removeElementAt(i);
		serialize(region);
	}

	while (schemeQueue.size())
	{
		int i = schemeQueue.size() - 1;
		SchemeImpl* scheme = schemeQueue.elementAt(i);
		schemeQueue.removeElementAt(i);
		serialize(scheme);
	}
}