/***************************************************************************

  gb_pcode_temp.h

  (c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

  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 2, 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "gb_common.h"
#include "gb_error.h"

#ifdef PROJECT_COMP
#include "gb_limit.h"
#include "gbc_compile.h"
#endif

#include "gb_pcode.h"

static int _code_count[256] = { 0 };

/*#define DEBUG*/

static void print_quoted_string(FILE *out, const char *s, bool trans)
{
	const char *p = s;
	unsigned char c;
	
	if (trans)
		fputc('(', out);
	fputc('"', out);
	while ((c = (unsigned char)*p++))
	{
		if (c < 32)
		{
			fputc('\\', out);
			if (c == '\n')
				fputc('n', out);
			else if (c == '\t')
				fputc('n', out);
			else
				fprintf(out, "\\x%02X", c);
		}
		else
			fputc(c, out);
	}
	fputc('"', out);
	if (trans)
		fputc(')', out);
}

short PCODE_dump(FILE *out, ushort addr, PCODE *code)
{
	static const char *op_comp[] = { "=", "<>", ">", "<=", "<", ">=", "==", "CASE" };
	static const char *op_arith[] = { "+", "-" , "*", "/", "NEG", "\\", "MOD", "POW", "AND", "OR", "XOR", "NOT", "&", "LIKE", "&/" };

	int j;
	unsigned short op;
	unsigned short digit;
	int value;
	#ifdef PROJECT_COMP
	int index;
	TABLE *table;
	bool trans;
	#endif
	int ncode;

	op = *code;

	switch (op & 0xFF00)
	{
		case C_PUSH_UNKNOWN: case C_POP_UNKNOWN:
		case C_PUSH_INTEGER:
		case C_JUMP: case C_JUMP_IF_TRUE: case C_JUMP_IF_FALSE: case C_GOSUB:
		case C_NEXT: case C_JUMP_NEXT:
		case C_TRY:
		case C_JUMP_IF_TRUE_FAST: case C_JUMP_IF_FALSE_FAST:

			ncode = 2;
			break;

		case C_PUSH_LONG:

			ncode = 3;
			break;
			
		case C_BYREF:
			ncode = 2 + (op & 0xFF);
			break;
			
		case C_ON:
			ncode = 1 + (op & 0xFF);
			break;
			
		case C_PUSH_EVENT:
			ncode = 1 + ((op & 0xFF) == 0xFF);
			break;

		default:

			if ((op & 0xFF00) == (C_PUSH_CONST | 0xF00))
				ncode = 2;
			else
				ncode = 1;
	}

	fprintf(out, "%04d : ", addr);

	for (j = 0; j < 3; j++)
	{
		if (j >= ncode)
			fprintf(out, "     ");
		else
			fprintf(out, " %04hX", code[j]);
	}

	fprintf(out, "  ");

	_code_count[op >> 8]++;

	digit = (op >> 12);
	value = op & 0xFFF;
	if (value >= 0x800) value |= 0xFFFFF000;

	switch (digit)
	{
		case 0xF:
			switch (op & 0xFF00)
			{
				case C_PUSH_LOCAL_NOREF:
					fprintf(out, "PUSH LOCAL NOREF %d", (op & 0xFF));
					break;

				case C_PUSH_PARAM_NOREF:
					fprintf(out, "PUSH PARAM NOREF %d", (char)(op & 0xFF));
					break;

				case C_PUSH_VARIABLE:
					fprintf(out, "PUSH VARIABLE %d", (op & 0xFF));
					break;

				case C_POP_VARIABLE:
					fprintf(out, "POP VARIABLE %d", (op & 0xFF));
					break;

				case C_PUSH_FLOAT:
					fprintf(out, "PUSH FLOAT %d", (char)(op & 0xFF));
					break;

				case C_POKE:
					fprintf(out, "POKE %d", (op & 0xFF));
					break;

				case C_POP_LOCAL_NOREF:
					fprintf(out, "POP LOCAL NOREF %d", (op & 0xFF));
					break;

				case C_POP_PARAM_NOREF:
					fprintf(out, "POP PARAM NOREF %d", (char)(op & 0xFF));
					break;

				case C_POP_LOCAL_FAST:
					fprintf(out, "POP LOCAL FAST %d", (op & 0xFF));
					break;

				case C_POP_PARAM_FAST:
					fprintf(out, "POP PARAM FAST %d", (char)(op & 0xFF));
					break;

				case C_JUMP_NEXT_INTEGER:
					fprintf(out, "JUMP NEXT INTEGER %d", (op & 0xFF));
					break;

				case C_JUMP_IF_TRUE_FAST:
				case C_JUMP_IF_FALSE_FAST:
					value = code[1];
					fprintf(out, "JUMP IF %s FAST %04d",
						(digit == C_JUMP_IF_TRUE_FAST ? "TRUE" : "FALSE"),
						(short)(addr + value + 2));
					break;

				default:
					fprintf(out, "PUSH QUICK %d", (short)value);
			}
			break;

		case 0xA:

			switch(op & 0xFF00)
			{
				case C_PUSH_ARRAY_NATIVE_INTEGER:
					fprintf(out, "PUSH ARRAY NATIVE INTEGER %d", (op & 0xFF));
					break;
				case C_POP_ARRAY_NATIVE_INTEGER:
					fprintf(out, "POP ARRAY NATIVE INTEGER %d", (op & 0xFF));
					break;
				case C_PUSH_ARRAY_NATIVE_FLOAT:
					fprintf(out, "PUSH ARRAY NATIVE FLOAT %d", (op & 0xFF));
					break;
				case C_POP_ARRAY_NATIVE_FLOAT:
					fprintf(out, "POP ARRAY NATIVE FLOAT %d", (op & 0xFF));
					break;
				case C_ADD_INTEGER:
					fprintf(out, "ADD INTEGER %d", (op & 0xFF));
					break;
				case C_ADD_FLOAT:
					fprintf(out, "ADD FLOAT %d", (op & 0xFF));
					break;
				case C_SUB_INTEGER:
					fprintf(out, "SUB INTEGER %d", (op & 0xFF));
					break;
				case C_SUB_FLOAT:
					fprintf(out, "SUB FLOAT %d", (op & 0xFF));
					break;
				case C_MUL_INTEGER:
					fprintf(out, "MUL INTEGER %d", (op & 0xFF));
					break;
				case C_MUL_FLOAT:
					fprintf(out, "MUL FLOAT %d", (op & 0xFF));
					break;
				case C_DIV_INTEGER:
					fprintf(out, "DIV INTEGER %d", (op & 0xFF));
					break;
				case C_DIV_FLOAT:
					fprintf(out, "DIV FLOAT %d", (op & 0xFF));
					break;
				case C_ADD_QUICK_INTEGER:
					fprintf(out, "ADD QUICK INTEGER %d", (signed char)(op & 0xFF));
					break;
				case C_ADD_QUICK_FLOAT:
					fprintf(out, "ADD QUICK FLOAT %d", (signed char)(op & 0xFF));
					break;
				default:
					fprintf(out, "ADD QUICK %d", (short)value);
			}
			break;

		#ifdef PROJECT_COMP

		case 0xE:
			if ((op & 0xF00) == 0xF00)
			{
				value = code[1];
			}

			fprintf(out, "PUSH CONST %d", value);

			switch(JOB->class->constant[value].type.t.id)
			{
				case T_STRING:
					table = JOB->class->string;
					trans = FALSE;
					break;

				case T_CSTRING:
					table = JOB->class->string;
					trans = TRUE;
					break;

				default:
					table = JOB->class->table;
					trans = FALSE;
					break;
			}

			fputc(' ', out);
			
			print_quoted_string(out, TABLE_get_symbol_name(table, JOB->class->constant[value].value), trans);
			
			break;

		case 0xD: case 0xC:
			fprintf(out, "%s %s ", (digit == 0xD ? "POP" : "PUSH"), (value & 0x800) ? "STATIC" : "DYNAMIC");
			index = ((value & 0x800) ?  JOB->class->stat[value & 0x7FF].index : JOB->class->dyn[value & 0x7FF].index);
			fprintf(out, "%s", TABLE_get_symbol_name(JOB->class->table, index));
			break;

		case 0xB:
			if (value & 0x800)
			{
				fprintf(out, "PUSH FUNCTION ");
				index = JOB->class->function[value & 0x7FF].name;
			}
			else
			{
				fprintf(out, "PUSH CLASS ");
				index = JOB->class->class[value].index;
			}
			fprintf(out, "%s", TABLE_get_symbol_name(JOB->class->table, index));
			break;

		#else

		case 0xE:
			fprintf(out, "PUSH CONST %d", (short)value);
			break;

		case 0xD:
			fprintf(out, "POP %s %d", (value & 0x800) ? "STATIC" : "DYNAMIC", value & 0x7FF);
			break;

		case 0xC:
			fprintf(out, "PUSH %s %d", (value & 0x800) ? "STATIC" : "DYNAMIC", value & 0x7FF);
			break;

		case 0xB:
			fprintf(out, "PUSH %s %d", (value & 0x800) ? "FUNCTION" : "CLASS", value & 0x7FF);
			break;

		#endif

		default:

			digit = op & 0xFF00;
			value = op & 0xFF;
			if (value >= 0x80) value |= 0xFFFFFF00;

			if (digit >= C_PUSH_LOCAL && digit < C_QUIT && digit != C_BYREF)
				fprintf(out, "PUSH ");
			else if (digit >= C_POP_LOCAL && digit < C_BREAK)
				fprintf(out, "POP ");

			switch(digit)
			{
				case C_PUSH_LOCAL: case C_POP_LOCAL: case C_PUSH_PARAM: case C_POP_PARAM:
					if (value >= 0)
						fprintf(out, "LOCAL %d", (short)value);
					else
						fprintf(out, "PARAM %d", (short)value);
					break;

				case C_POP_CTRL:
					fprintf(out, "CTRL %d", (short)value);
					break;

				case C_POP_OPTIONAL:
					fprintf(out, "OPTIONAL %d", (short)value);
					break;

				case C_PUSH_UNKNOWN: case C_POP_UNKNOWN:
					value = code[1];
					#ifdef PROJECT_COMP
					fprintf(out, "UNKNOWN %s", TABLE_get_symbol_name(JOB->class->table, JOB->class->unknown[value]));
					#else
					fprintf(out, "UNKNOWN %d", (short)value);
					#endif

					break;

				/*case C_PUSH_SPECIAL:
					fprintf(out, "SPECIAL %d", (short)value);
					break;*/

				case C_PUSH_EXTERN:
					fprintf(out, "EXTERN %d", (short)value);
					break;

				case C_PUSH_EVENT:
					if ((unsigned char)value == 0xFF)
					{
						value = code[1];
						#ifdef PROJECT_COMP
						fprintf(out, "UNKNOWN EVENT %s", TABLE_get_symbol_name(JOB->class->table, JOB->class->unknown[value]));
						#else
						fprintf(out, "UNKNOWN EVENT %d", (short)value);
						#endif
					}
					else
						fprintf(out, "EVENT %d", (short)value);
					break;

				case C_PUSH_ARRAY: case C_POP_ARRAY:
					fprintf(out, "ARRAY (%d)", (short)value);
					break;

				case C_CALL: case C_CALL_QUICK: case C_CALL_SLOW:
					if (digit == C_CALL)
						fprintf(out, "CALL ");
					else if (digit == C_CALL_QUICK)
						fprintf(out, "CALL QUICK ");
					else
						fprintf(out, "CALL SLOW ");
					if (value & CODE_CALL_VARIANT)
						fprintf(out, "VARIANT ");
					//if (value & CODE_CALL_VOID)
					//  fprintf(out, "VOID ");

					fprintf(out, "(%d)", (short)value & 0x3F);
					break;
					
				case C_BYREF:
					fprintf(out, "BYREF (%d) ", (short)value & 0x3F);
					for (j = 1; j < ncode; j++)
						fprintf(out, "%04X", code[j]);
					break;

				case C_PUSH_INTEGER:
					value = code[1];
					fprintf(out, "PUSH SHORT %d", (short)value);
					break;

				case C_PUSH_LONG:
					// FIXME: endianness
					value = code[1] | (code[2] << 16); //*((int *)&code[1]);
					fprintf(out, "PUSH INTEGER %d", value);
					break;

				case C_PUSH_ME:
					fprintf(out, "PUSH %s", (value & 2) ? "SUPER": "ME");
					break;

				case C_PUSH_MISC:
					switch (value)
					{
						case CPM_NULL: fprintf(out, "PUSH NULL"); break;
						case CPM_VOID: fprintf(out, "PUSH VOID"); break;
						case CPM_FALSE: fprintf(out, "PUSH FALSE"); break;
						case CPM_TRUE: fprintf(out, "PUSH TRUE"); break;
						case CPM_LAST: fprintf(out, "PUSH LAST"); break;
						case CPM_STRING: fprintf(out, "PUSH NULL STRING"); break;
						case CPM_PINF: fprintf(out, "PUSH +INF"); break;
						case CPM_MINF: fprintf(out, "PUSH -INF"); break;
						case CPM_COMPLEX: fprintf(out, "PUSH COMPLEX"); break;
						case CPM_VARGS: fprintf(out, "PUSH VARGS"); break;
						case CPM_DROP_VARGS: fprintf(out, "DROP VARGS"); break;
						case CPM_END_VARGS: fprintf(out, "END VARGS"); break;
					}
					break;

				case C_JUMP: case C_JUMP_IF_TRUE: case C_JUMP_IF_FALSE:
					value = code[1];
					fprintf(out, "JUMP%s %04d",
						(digit == C_JUMP ? "" :
						digit == C_JUMP_IF_TRUE ? " IF TRUE" :
						" IF FALSE"),
						(short)(addr + value + 2));
					break;

				case C_JUMP_FIRST:

					fprintf(out, "JUMP FIRST LOCAL %d", (short)value);
					break;

				case C_JUMP_NEXT:

					fprintf(out, "JUMP NEXT ");

					value = code[1];
					fprintf(out, "%04d ", (short)(addr + value + 2));

					break;

				case C_GOSUB:
					value = code[1];
					fprintf(out, "GOSUB %04d", (short)(addr + value + 2));
					break;

				case C_ON:
					fprintf(out, "ON (%d)", (short)value);
					break;

				case C_FIRST:

					fprintf(out, "ENUM FIRST LOCAL %d", (short)value);
					break;

				case C_NEXT:

					fprintf(out, "ENUM NEXT ");
					if (value & 0xFF)
						fprintf(out, "DROP ");
					value = code[1];
					fprintf(out, "%04d ", (short)(addr + value + 2));
					break;

				case C_DROP:
					fprintf(out, "DROP (%d)", (short)value);
					break;

				case C_DUP:
					fprintf(out, "DUP");
					break;

				case C_NEW:
					fprintf(out, "NEW ");
					if (value & CODE_NEW_EVENT)
						fprintf(out, "EVENT ");
					if (value & CODE_NEW_ARRAY)
						fprintf(out, "ARRAY ");
					fprintf(out, "(%d)", (short)value & 0x3F);
					break;

				case C_BREAK:
					fprintf(out, "BREAK");
					break;

				case C_RETURN:
					switch(value)
					{
						case 0: fprintf(out, "RETURN VOID"); break;
						case 1: fprintf(out, "RETURN"); break;
						case 2: fprintf(out, "RETURN VOID"); break;
						default: fprintf(out, "RETURN (%d) ?", (short)value);
					}
					break;

				case C_QUIT:
					switch(value)
					{
						case 0: fprintf(out, "QUIT"); break;
						case 1: fprintf(out, "STOP"); break;
						case 2: default: fprintf(out, "STOP EVENT"); break;
						case 3: fprintf(out, "QUIT (1)"); break;
					}
					break;

				case C_PUSH_CHAR:
					fprintf(out, "PUSH CHAR (%d)", (short)value);
					break;

				case C_TRY:
					value = code[1];
					fprintf(out, "TRY %04d", (short)(addr + value + 2));
					break;

				case C_END_TRY:
					fprintf(out, "END TRY");
					break;

				case C_CATCH:
					fprintf(out, "CATCH");
					break;

				case C_NOP:
					fprintf(out, "NOP");
					break;

				default:
					digit = (digit >> 8);
					if (digit >= CODE_FIRST_SUBR)
					{
						#ifdef PROJECT_COMP
						fprintf(out, "SUBR %s ", SUBR_get_from_opcode(digit - CODE_FIRST_SUBR, (short)value & 0x3F)->name);
						#else
						fprintf(out, "SUBR #%d ", digit - CODE_FIRST_SUBR);
						#endif

						if (value & CODE_CALL_VARIANT)
							fprintf(out, "VARIANT ");
						//if (value & CODE_CALL_VOID)
						//  fprintf(out, "VOID ");

						fprintf(out, "(%d)", (short)value & 0x3F);
					}
					else if (digit >= 0x28 && digit <= 0x2F)
						fprintf(out, "%s (%d)", op_comp[digit - 0x28], (short)value);
					else if (digit >= 0x30 && digit <= 0x3E)
						fprintf(out, "%s (%d)", op_arith[digit - 0x30], (short)value);
					else if (digit == 0x3F)
						fprintf(out, "IS (%d)", (short)value);
					else
						fprintf(out, "ILLEGAL");
			}
	}

	fprintf(out, "\n");

	if (ncode > 3)
	{
		for (j = 3; j < ncode; j++)
		{
			if (((j - 3) % 16) == 0)
				fprintf(out, "       ");
			fprintf(out, " %04hX", code[j]);
			if (((j - 3) % 16) == 15)
				fprintf(out, "\n");
		}
		fprintf(out, "\n");
	}

	return ncode;
}

void PCODE_dump_count(FILE *out)
{
	int i;

	for (i = 0; i < 256; i++)
		fprintf(out, "%02X : %d\n", i, _code_count[i]);
}
