/*
 *	Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *	SOFTWARE.
 */


#include "stddclmr.h"

#include "interpreter.h"
#include "portme.h"
#include "messages.h"
#include "story.h"
#include "state.h"
#include "variable.h"
#include "memory.h"
#include "lib.h"


void interpreterDoBranch(int32_t truth) {
	uint8_t branch  = ZPEEK(__state.pc++);
	int32_t farJump = (branch & (1 << 6)) == 0;
	int32_t onTruth = (branch & (1 << 7)) ? 1 : 0;
	uint8_t byte2   = farJump ? ZPEEK(__state.pc++) : 0;
	int16_t offset;

	if (onTruth == truth) {
		offset = (int16_t)(branch & 0x3f);
		if (farJump) {
			if (offset & (1 << 5)) offset |= 0xc0;
			offset = (offset << 8) | ((int16_t)byte2);
		}
		if (offset == 0) {
			interpreterDoReturn(0);
		} else if (offset == 1) {
			interpreterDoReturn(1);
		} else {
			__state.pc = (__state.pc + offset) - 2;
		}
	}
}


void interpreterDoReturn(uint16_t r) {
	uint8_t  storeId;

	//***TODO*** Newer versions start in a real routine, but still aren't allowed to return from it.

	if (__state.bp == 0) portDie(MSG_INT_STACK_UNDERFLOW);

#ifdef DEBUGGINGx
	printf("Returning: initial pc=%X, bp=%u, sp=%u\n", (unsigned int)__state.pc, __state.bp, __state.sp);
#endif

	// Dump all locals and data pushed on stack during the routine.
	__state.sp = __state.bp;
	// Dump our copy of numLocals.
	__state.sp--;
	// Restore previous frame's base pointer.
	__state.bp = __state.stack[--__state.sp];

	// Point to start of saved program counter;
	__state.sp -= 2;
	__state.pc = ((uint32_t)__state.stack[__state.sp]) | (((uint32_t)__state.stack[__state.sp + 1]) << 16);

	// Pop the result storage location.
	storeId = (uint8_t)__state.stack[--__state.sp];

#ifdef DEBUGGINGx
	printf("Returning: new pc=%X, bp=%u, sp=%u\n", (unsigned int)__state.pc, __state.bp, __state.sp);
#endif

	// Store the routine result.
	variableStore(storeId, r);
}


bool interpreterParseOperand(uint8_t opType, uint8_t operandId) {
	switch(opType) {
		// Large constant.
		case 0:
			__state.operands[operandId] = ZPEEKW(__state.pc);
			__state.pc += 2;
			return true;

		// Small constant.
		case 1:
			__state.operands[operandId] = ZPEEK(__state.pc++);
			return true;

		// Variable.
		case 2:
			__state.operands[operandId] = variableLoad(ZPEEK(__state.pc++));
			return true;
	}

	return false;
}


uint8_t interpreterParseVariableOperands(uint8_t index) {
	uint8_t operandTypes = ZPEEK(__state.pc++);
	uint8_t shifter      = 6;
	uint8_t i;
	uint8_t opType;

	for (i=0; i<4; i++) {
		opType = (operandTypes >> shifter) & 0x3;
		shifter -= 2;
		if (!interpreterParseOperand(opType, index + i)) break;
	}

	return i;
}


void interpreterRun(void) {
	uint8_t opcode;
	bool    extended;
	opcodeT op;
	bool    eight;

	__state.savedX = 1;
	__state.savedY = storyScreenHeightLines();
	portCharSetPos(__state.savedX, __state.savedY);

	while (__state.quit == 0) {
		opcode   = ZPEEK(__state.pc++);
		extended = ((opcode == 190) && (storyVersion() >= 5)) ? true : false;

		if (extended) {

			opcode = ZPEEK(__state.pc++);
			if (opcode >= (sizeof(__state.extOpcodes) / sizeof(__state.extOpcodes[0]))) portDie(MSG_INT_INVALID_EXT_OPCODE);
			__state.operandCount = interpreterParseVariableOperands(0);
			op = __state.extOpcodes[opcode];

		} else {

			if (opcode <= 127) {
				// 2OP
				__state.operandCount = 2;
				interpreterParseOperand(((opcode >> 6) & 0x1) ? 2 : 1, 0);
				interpreterParseOperand(((opcode >> 5) & 0x1) ? 2 : 1, 1);
			} else if (opcode <= 175) {
				// 1OP
				__state.operandCount = 1;
				interpreterParseOperand((opcode >> 4) & 0x3, 0);
			} else if (opcode <= 191) {
				// 0OP
				__state.operandCount = 0;
			} else if (opcode > 191) {
				// Variable Opcodes
				eight = (opcode == 236) || (opcode == 250);
				if (!eight) {
					// call_vs2 and call_vn2 take up to EIGHT arguments!
					__state.operandCount = interpreterParseVariableOperands(0);
				} else {
					__state.operandCount = interpreterParseVariableOperands(0);
					if (__state.operandCount == 4) {
						__state.operandCount += interpreterParseVariableOperands(4);
					} else {
						__state.pc++;
					}
				}
			}

			op = __state.opcodes[opcode];

		}  // extended

		if (op == 0) portDie(MSG_INT_UNIMPLEMENTED_OPCODE, extended ? "ext" : "", opcode);

#ifdef DEBUGGINGx
		printf("ins=%u pc=%X sp=%u bp=%u %sopcode=%u ('%s') [",
			   __state.instructionsRun,
			   (unsigned int)__state.pc,
			   __state.sp,
			   __state.bp,
			   extended ? "ext " : "",
			   opcode,
			   extended ? __state.extOpcodesName[opcode] : __state.opcodesName[opcode]);
		if (__state.operandCount) {
			uint8_t i;
			for (i=0; i<__state.operandCount - 1; i++)
				printf("%X,", (unsigned int)__state.operands[i]);
			printf("%X", (unsigned int)__state.operands[i]);
		}
		printf("]\n");
		fflush(stdout);

		if (__state.instructionsRun == 261) {
			printf("\n*** BREAKING ***\n");
			fflush(stdout);
			printf(" ");  // Set breakpoint here.
		}
#endif

		op();

#ifdef DEBUGGING
		__state.instructionsRun++;
#endif
	}
}


void interpreterTokenizeInput(void) {
	static char  tableA2v1[]     = "0123456789.,!?_#\'\"/\\<-:()";
	static char  tableA2v2plus[] = "\n0123456789.,!?_#\'\"/\\-:()";
	char        *tableA2         = (storyVersion() <= 1) ? tableA2v1 : tableA2v2plus;
	uint32_t     input           = __state.operands[0];
	uint32_t     parse           = __state.operands[1];
	byte         parseLen        = ZPEEK(parse++);
	uint32_t     seps            = storyDictionaryAddress();
	byte         numSeps         = ZPEEK(seps++);
	uint32_t     dict            = seps + numSeps;
	byte         entryLen        = ZPEEK(dict++);
	uint16_t     numEntries      = ZPEEKW(dict);  dict += 2;
	byte         numToks         = 0;
	uint32_t     strStart;
	uint32_t     ptr;
	byte        *ptr2;
	bool         isSep;
	byte         ch;
	uint32_t     i;
	uint16_t     encoded[3];
	byte         tokLen;
	byte         zChars[12];
	byte         zChIdx;
	byte         pos;
	uint32_t     dictPtr;
	uint16_t     zscii1;
	uint16_t     zscii2;
	uint16_t     zscii3;

#ifdef DEBUGGING
	printf("Max parse: %u\n", parseLen);
	printf("Parse Input: [");
#endif

	if (parseLen == 0) portDie(MSG_INT_PARSE_BUFFER_TOO_SMALL);

	input++;  // Skip inputLen byte.
	parse++;  // Skip where we put final token count.

	// Tokenize input.
	strStart = input;
	ptr      = input;
	while (1) {
		isSep = false;
		ch    = ZPEEK(ptr);
#ifdef DEBUGGING
		printf("%c", ch);
#endif
		if ((ch == ' ') || (ch == 0)) {
			isSep = true;
		} else {
			for (i=0; i<numSeps; i++) {
				if (ch == ZPEEK(seps + i)) {
					isSep = true;
					break;
				}
			}
		} // isSep

		if (isSep) {
			encoded[0] = 0;
			encoded[1] = 0;
			encoded[2] = 0;

			tokLen = ptr - strStart;
			if (tokLen == 0) break;  // Ran out of string.

			zChIdx = 0;
			for (i=0; i<tokLen; i++) {
				ch = ZPEEK(strStart + i);
				if ((ch >= 'a') && (ch <= 'z')) {
					zChars[zChIdx++] = ((ch - 'a') + 6);
				} else {
					ptr2 = (byte *)libStrChr(tableA2, ch);
					if (ptr2) {
						// Command char to shift to table A2 for just the next char.
						zChars[zChIdx++] = 3;
						// +1 because the first table entry is a different piece of magic.
						zChars[zChIdx++] = ((ptr2 - (byte *)tableA2) + 1) + 6;
					}
				}
				if (zChIdx >= (sizeof(zChars) / sizeof(zChars[0]))) break;
			} // for

			pos = 0;
			encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
			encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
			encoded[0] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;
			encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
			encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
			encoded[1] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;

			dictPtr = dict;
			if (storyVersion() <= 3) {

				encoded[1] |= 0x8000;
				for (i=0; i<numEntries; i++) {
					zscii1 = ZPEEKW(dictPtr);  dictPtr += 2;
					zscii2 = ZPEEKW(dictPtr);  dictPtr += 2;
					if ((encoded[0] == zscii1) && (encoded[1] == zscii2)) {
						dictPtr -= 4;
						break;
					}
					dictPtr += (entryLen - 4);
				}

			} else {  // V3

				encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 10;
				encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 5;
				encoded[2] |= ((pos < zChIdx) ? zChars[pos++] : 5) << 0;
				encoded[2] |= 0x8000;

				for (i=0; i<numEntries; i++) {
					zscii1 = ZPEEKW(dictPtr);  dictPtr += 2;
					zscii2 = ZPEEKW(dictPtr);  dictPtr += 2;
					zscii3 = ZPEEKW(dictPtr);  dictPtr += 2;
					if ((encoded[0] == zscii1) && (encoded[1] == zscii2) && (encoded[2] == zscii3)) {
						dictPtr -= 6;
						break;
					}
					dictPtr += (entryLen - 6);
				}

			}  // V3

			if (i == numEntries) dictPtr = 0;  // Not Found.

			ZPOKEW(parse, dictPtr);  parse += 2;
			ZPOKE(parse++, tokLen);
			ZPOKE(parse++, (strStart - input) + 1);
			numToks++;

			if (numToks >= parseLen) break;  // Ran out of space.

			strStart = ptr + 1;
		}  // if isSep

		if (ch == 0) break;  // End of string.

		ptr++;

	}  // while

#ifdef DEBUGGING
	printf("]\n");
	printf("Tokenized %u tokens\n", (unsigned int)numToks);
	fflush(stdout);
#endif

	ZPOKE(__state.operands[1] + 1, numToks);
}
