/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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/>.
 *
 */

#include "glk/alan3/types.h"
#include "glk/alan3/lists.h"
#include "glk/alan3/checkentry.h"
#include "glk/alan3/rules.h"
#include "glk/alan3/msg.h"
#include "glk/alan3/utils.h"
#include "glk/alan3/compatibility.h"
#include "glk/alan3/syserr.h"
#include "glk/alan3/memory.h"

namespace Glk {
namespace Alan3 {

extern Aword *memory;

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static Aaddr memorySize = 0;
static Aword *addressesDone = nullptr;
static int numberDone = 0;
static int doneSize = 0;

static bool alreadyDone(Aaddr address) {
	int i;

	if (address == 0) return TRUE;

	/* Have we already done it? */
	for (i = 0; i < numberDone; i++)
		if (addressesDone[i] == address)
			return TRUE;

	if (doneSize == numberDone) {
		doneSize += 100;
		addressesDone = (Aword *)realloc(addressesDone, doneSize * sizeof(Aword));
	}
	addressesDone[numberDone] = address;
	numberDone++;

	return FALSE;
}



#define NATIVE(w)   \
	( (((Aword)((w)[3])      ) & 0x000000ff)    \
	  | (((Aword)((w)[2]) <<  8) & 0x0000ff00)    \
	  | (((Aword)((w)[1]) << 16) & 0x00ff0000)    \
	  | (((Aword)((w)[0]) << 24) & 0xff000000))

/*----------------------------------------------------------------------*/
Aword reversed(Aword w) { /* IN - The ACODE word to swap bytes of */
#ifdef TRYNATIVE
	return NATIVE(&w);
#else
	Aword s;                      /* The swapped ACODE word */
	char *wp, *sp;
	uint i;

	wp = (char *) &w;
	sp = (char *) &s;

	for (i = 0; i < sizeof(Aword); i++)
		sp[sizeof(Aword) - 1 - i] = wp[i];

	return s;
#endif
}


void reverseWord(Aword *w) {        /* IN - The ACODE word to reverse bytes in */
	*w = reversed(*w);
}

void reverse(Aword *w) {        /* IN - The ACODE word to reverse bytes in */
	if (w < &memory[0] || w > &memory[memorySize])
		syserr("Reversing address outside of memory");
	reverseWord(w);
}


static void reverseTable(Aword adr, int elementSize) {
	Aword *e = &memory[adr];
	uint i;

	if (elementSize < (int)sizeof(Aword) || elementSize % (int)sizeof(Aword) != 0)
		syserr("***Wrong size in 'reverseTable()' ***");

	if (adr == 0) return;

	while (!isEndOfArray(e)) {
		for (i = 0; i < elementSize / sizeof(Aword); i++) {
			reverse(e);
			e++;
		}
	}
}


static void reverseStms(Aword adr) {
	Aword *e = &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	while (TRUE) {
		reverse(e);
		if (*e == ((Aword)C_STMOP << 28 | (Aword)I_RETURN)) break;
		e++;
	}
}


static void reverseMsgs(Aword adr) {
	MessageEntry *e = (MessageEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(MessageEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseDictionary(Aword adr) {
	DictionaryEntry *e = (DictionaryEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(DictionaryEntry));
		while (!isEndOfArray(e)) {
			if ((e->classBits & SYNONYM_BIT) == 0) { /* Do not do this for synonyms */
				reverseTable(e->adjectiveRefs, sizeof(Aword));
				reverseTable(e->nounRefs, sizeof(Aword));
				reverseTable(e->pronounRefs, sizeof(Aword));
			}
			e++;
		}
	}
}


static void reverseChks(Aword adr) {
	CheckEntry *e = (CheckEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(CheckEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->exp);
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseAlts(Aword adr) {
	AltEntry *e = (AltEntry *)&memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(AltEntry));
		while (!isEndOfArray(e)) {
			reverseChks(e->checks);
			reverseStms(e->action);
			e++;
		}
	}
}


static void reverseVerbs(Aword adr) {
	VerbEntry *e = (VerbEntry *)&memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(VerbEntry));
		while (!isEndOfArray(e)) {
			reverseAlts(e->alts);
			e++;
		}
	}
}


static void reverseSteps(Aword adr) {
	StepEntry *e = (StepEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(StepEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->after);
			reverseStms(e->exp);
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseScrs(Aword adr) {
	ScriptEntry *e = (ScriptEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ScriptEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->description);
			reverseSteps(e->steps);
			e++;
		}
	}
}


static void reverseExits(Aword adr) {
	ExitEntry *e = (ExitEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ExitEntry));
		while (!isEndOfArray(e)) {
			reverseChks(e->checks);
			reverseStms(e->action);
			e++;
		}
	}
}


static void reverseClasses(Aword adr) {
	ClassEntry *e = (ClassEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ClassEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->name);
			reverseStms(e->initialize);
			reverseChks(e->descriptionChecks);
			reverseStms(e->description);
			reverseStms(e->entered);
			reverseStms(e->definite.address);
			reverseStms(e->indefinite.address);
			reverseStms(e->negative.address);
			reverseStms(e->mentioned);
			reverseVerbs(e->verbs);
			e++;
		}
	}
}


static void reverseInstances(Aword adr) {
	InstanceEntry *e = (InstanceEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(InstanceEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->name);
			reverseTable(e->initialAttributes, sizeof(AttributeHeaderEntry));
			reverseStms(e->initialize);
			reverseStms(e->definite.address);
			reverseStms(e->indefinite.address);
			reverseStms(e->negative.address);
			reverseStms(e->mentioned);
			reverseChks(e->checks);
			reverseStms(e->description);
			reverseVerbs(e->verbs);
			reverseStms(e->entered);
			reverseExits(e->exits);
			e++;
		}
	}
}


static void reverseRestrictions(Aword adr) {
	RestrictionEntry *e = (RestrictionEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;
	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(RestrictionEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseElms(Aword adr) {
	ElementEntry *e = (ElementEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ElementEntry));
		while (!isEndOfArray(e)) {
			if ((uint)e->code == EOS) reverseRestrictions(e->next);
			else reverseElms(e->next);
			e++;
		}
	}
}


static void reverseSyntaxTableCurrent(Aword adr) {
	SyntaxEntry *e = (SyntaxEntry *) &memory[adr];

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(SyntaxEntry));
		while (!isEndOfArray(e)) {
			reverseElms(e->elms);
			reverseTable(e->parameterNameTable, sizeof(Aaddr));
			e++;
		}
	}
}


static void reverseSyntaxTablePreBeta2(Aword adr) {
	SyntaxEntryPreBeta2 *e = (SyntaxEntryPreBeta2 *) &memory[adr];

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(SyntaxEntryPreBeta2));
		while (!isEndOfArray(e)) {
			reverseElms(e->elms);
			e++;
		}
	}
}


static void reverseSyntaxTable(Aword adr, byte version[]) {
	if (!adr || alreadyDone(adr)) return;

	if (isPreBeta2(version))
		reverseSyntaxTablePreBeta2(adr);
	else
		reverseSyntaxTableCurrent(adr);
}


static void reverseParameterNames(Aaddr parameterMapAddress) {
	Aaddr *e;
	Aaddr adr;

	adr = addressAfterTable(parameterMapAddress, sizeof(ParameterMapEntry));
	reverse(&memory[adr]);
	adr = memory[adr];

	reverseTable(adr, sizeof(Aaddr));

	e = (Aaddr *) &memory[adr];
	while (!isEndOfArray(e)) {
		reverseTable(*e, sizeof(Aaddr));
		e++;
	}
}


static void reverseParameterTable(Aword adr) {
	ParameterMapEntry *e = (ParameterMapEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ParameterMapEntry));
		while (!isEndOfArray(e)) {
			reverseTable(e->parameterMapping, sizeof(Aword));
			e++;
		}
	}
}


static void reverseEvts(Aword adr) {
	EventEntry *e = (EventEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(EventEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->code);
			e++;
		}
	}
}


static void reverseLims(Aword adr) {
	LimitEntry *e = (LimitEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(LimitEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseContainers(Aword adr) {
	ContainerEntry *e = (ContainerEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(ContainerEntry));
		while (!isEndOfArray(e)) {
			reverseLims(e->limits);
			reverseStms(e->header);
			reverseStms(e->empty);
			reverseChks(e->extractChecks);
			reverseStms(e->extractStatements);
			e++;
		}
	}
}


static void reverseRuls(Aword adr) {
	RuleEntry *e = (RuleEntry *) &memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(RuleEntry));
		while (!isEndOfArray(e)) {
			reverseStms(e->exp);
			reverseStms(e->stms);
			e++;
		}
	}
}


static void reverseSetInitTable(Aaddr adr) {
	SetInitEntry *e = (SetInitEntry *)&memory[adr];

	if (!adr || alreadyDone(adr)) return;

	if (!isEndOfArray(e)) {
		reverseTable(adr, sizeof(SetInitEntry));
		while (!isEndOfArray(e)) {
			reverseTable(e->setAddress, sizeof(Aword));
			e++;
		}
	}
}



/*----------------------------------------------------------------------*/
static void reversePreAlpha5Header(Pre3_0alpha5Header *hdr) {
	uint i;

	/* Reverse all words in the header except the tag */
	for (i = 1; i < sizeof(*hdr) / sizeof(Aword); i++)
		reverseWord(&((Aword *)hdr)[i]);
}


/*----------------------------------------------------------------------*/
static void reversePreAlpha5() {
	/* NOTE that the reversePreXXX() have different header definitions */
	Pre3_0alpha5Header *hdr = (Pre3_0alpha5Header *)memory;

	reversePreAlpha5Header(hdr);
	memorySize = hdr->size;

	reverseDictionary(hdr->dictionary);
	reverseSyntaxTable(hdr->syntaxTableAddress, hdr->version);
	reverseParameterTable(hdr->parameterMapAddress);
	reverseVerbs(hdr->verbTableAddress);
	reverseClasses(hdr->classTableAddress);
	reverseInstances(hdr->instanceTableAddress);
	reverseScrs(hdr->scriptTableAddress);
	reverseContainers(hdr->containerTableAddress);
	reverseEvts(hdr->eventTableAddress);
	reverseRuls(hdr->ruleTableAddress);
	reverseTable(hdr->stringInitTable, sizeof(StringInitEntry));
	reverseSetInitTable(hdr->setInitTable);
	reverseTable(hdr->sourceFileTable, sizeof(SourceFileEntry));
	reverseTable(hdr->sourceLineTable, sizeof(SourceLineEntry));
	reverseStms(hdr->start);
	reverseMsgs(hdr->messageTableAddress);

	reverseTable(hdr->scores, sizeof(Aword));
	reverseTable(hdr->freq, sizeof(Aword));
}


/*----------------------------------------------------------------------*/
static void reversePreBeta2Header(Pre3_0beta2Header *hdr) {
	uint i;

	/* Reverse all words in the header except the tag */
	for (i = 1; i < sizeof(*hdr) / sizeof(Aword); i++)
		reverseWord(&((Aword *)hdr)[i]);
}


/*----------------------------------------------------------------------*/
static void reversePreBeta2() {
	/* NOTE that the reversePreXXX() have different header definitions */
	Pre3_0beta2Header *hdr = (Pre3_0beta2Header *)memory;

	reversePreBeta2Header(hdr);
	memorySize = hdr->size;

	reverseDictionary(hdr->dictionary);
	reverseSyntaxTable(hdr->syntaxTableAddress, hdr->version);
	reverseParameterTable(hdr->parameterMapAddress);
	reverseVerbs(hdr->verbTableAddress);
	reverseClasses(hdr->classTableAddress);
	reverseInstances(hdr->instanceTableAddress);
	reverseScrs(hdr->scriptTableAddress);
	reverseContainers(hdr->containerTableAddress);
	reverseEvts(hdr->eventTableAddress);
	reverseRuls(hdr->ruleTableAddress);
	reverseTable(hdr->stringInitTable, sizeof(StringInitEntry));
	reverseSetInitTable(hdr->setInitTable);
	reverseTable(hdr->sourceFileTable, sizeof(SourceFileEntry));
	reverseTable(hdr->sourceLineTable, sizeof(SourceLineEntry));
	reverseStms(hdr->start);
	reverseMsgs(hdr->messageTableAddress);

	reverseTable(hdr->scores, sizeof(Aword));
	reverseTable(hdr->freq, sizeof(Aword));
}


/*======================================================================*/
void reverseHdr(ACodeHeader *hdr) {
	uint i;

	/* Reverse all words in the header except the tag and the version marking */
	for (i = 1; i < sizeof(*hdr) / sizeof(Aword); i++)
		reverseWord(&((Aword *)hdr)[i]);
}


/*----------------------------------------------------------------------*/
static void reverseInstanceIdTable(ACodeHeader *hdr) {
	reverseTable(hdr->instanceTableAddress + hdr->instanceMax * sizeof(InstanceEntry) / sizeof(Aword) + 1, sizeof(Aword));
}


/*----------------------------------------------------------------------*/
static void reverseNative() {
	/* NOTE that the reversePreXXX() have different hdr definitions */
	ACodeHeader *hdr = (ACodeHeader *)memory;

	reverseHdr(hdr);
	memorySize = hdr->size;

	reverseDictionary(hdr->dictionary);
	reverseSyntaxTable(hdr->syntaxTableAddress, hdr->version);
	if (hdr->debug && !isPreBeta3(hdr->version))
		reverseParameterNames(hdr->parameterMapAddress);
	reverseParameterTable(hdr->parameterMapAddress);
	reverseVerbs(hdr->verbTableAddress);
	reverseClasses(hdr->classTableAddress);
	reverseInstances(hdr->instanceTableAddress);
	if (hdr->debug && !isPreBeta3(hdr->version))
		reverseInstanceIdTable(hdr);
	reverseScrs(hdr->scriptTableAddress);
	reverseContainers(hdr->containerTableAddress);
	reverseEvts(hdr->eventTableAddress);
	reverseRuls(hdr->ruleTableAddress);
	reverseTable(hdr->stringInitTable, sizeof(StringInitEntry));
	reverseSetInitTable(hdr->setInitTable);
	reverseTable(hdr->sourceFileTable, sizeof(SourceFileEntry));
	reverseTable(hdr->sourceLineTable, sizeof(SourceLineEntry));
	reverseStms(hdr->prompt);
	reverseStms(hdr->start);
	reverseMsgs(hdr->messageTableAddress);

	reverseTable(hdr->scores, sizeof(Aword));
	reverseTable(hdr->freq, sizeof(Aword));
}


/*======================================================================

  reverseACD()

  Traverse all the data structures and reverse all integers.
  Only performed in architectures with reversed byte ordering, which
  makes the .ACD files fully compatible across architectures

  */
void reverseACD(void) {
	ACodeHeader *hdr = (ACodeHeader *)memory;
	byte version[4];
	int i;

	/* Make a copy of the version marking to reverse */
	for (i = 0; i <= 3; i++)
		version[i] = hdr->version[i];
	reverseWord((Aword *)&version);

	if (isPreAlpha5(version))
		reversePreAlpha5();
	else if (isPreBeta2(version))
		reversePreBeta2();
	else
		reverseNative();

	free(addressesDone);
}

} // End of namespace Alan3
} // End of namespace Glk
