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

#include "glk/frotz/processor.h"
#include "glk/frotz/frotz.h"
#include "glk/conf.h"

namespace Glk {
namespace Frotz {

// TODO: Stubs to replace with actual code
zword save_undo() { return 0; }
zword restore_undo() { return 0; }


Opcode Processor::var_opcodes[64] = {
	&Processor::__illegal__,
	&Processor::z_je,
	&Processor::z_jl,
	&Processor::z_jg,
	&Processor::z_dec_chk,
	&Processor::z_inc_chk,
	&Processor::z_jin,
	&Processor::z_test,
	&Processor::z_or,
	&Processor::z_and,
	&Processor::z_test_attr,
	&Processor::z_set_attr,
	&Processor::z_clear_attr,
	&Processor::z_store,
	&Processor::z_insert_obj,
	&Processor::z_loadw,
	&Processor::z_loadb,
	&Processor::z_get_prop,
	&Processor::z_get_prop_addr,
	&Processor::z_get_next_prop,
	&Processor::z_add,
	&Processor::z_sub,
	&Processor::z_mul,
	&Processor::z_div,
	&Processor::z_mod,
	&Processor::z_call_s,
	&Processor::z_call_n,
	&Processor::z_set_colour,
	&Processor::z_throw,
	&Processor::__illegal__,
	&Processor::__illegal__,
	&Processor::__illegal__,
	&Processor::z_call_s,
	&Processor::z_storew,
	&Processor::z_storeb,
	&Processor::z_put_prop,
	&Processor::z_read,
	&Processor::z_print_char,
	&Processor::z_print_num,
	&Processor::z_random,
	&Processor::z_push,
	&Processor::z_pull,
	&Processor::z_split_window,
	&Processor::z_set_window,
	&Processor::z_call_s,
	&Processor::z_erase_window,
	&Processor::z_erase_line,
	&Processor::z_set_cursor,
	&Processor::z_get_cursor,
	&Processor::z_set_text_style,
	&Processor::z_buffer_mode,
	&Processor::z_output_stream,
	&Processor::z_input_stream,
	&Processor::z_sound_effect,
	&Processor::z_read_char,
	&Processor::z_scan_table,
	&Processor::z_not,
	&Processor::z_call_n,
	&Processor::z_call_n,
	&Processor::z_tokenise,
	&Processor::z_encode_text,
	&Processor::z_copy_table,
	&Processor::z_print_table,
	&Processor::z_check_arg_count
};

Opcode Processor::ext_opcodes[64] = {
	&Processor::z_save,
	&Processor::z_restore,
	&Processor::z_log_shift,
	&Processor::z_art_shift,
	&Processor::z_set_font,
	&Processor::z_draw_picture,
	&Processor::z_picture_data,
	&Processor::z_erase_picture,
	&Processor::z_set_margins,
	&Processor::z_save_undo,
	&Processor::z_restore_undo,
	&Processor::z_print_unicode,
	&Processor::z_check_unicode,
	&Processor::z_set_true_colour,	// spec 1.1
	&Processor::__illegal__,
	&Processor::__illegal__,
	&Processor::z_move_window,
	&Processor::z_window_size,
	&Processor::z_window_style,
	&Processor::z_get_wind_prop,
	&Processor::z_scroll_window,
	&Processor::z_pop_stack,
	&Processor::z_read_mouse,
	&Processor::z_mouse_window,
	&Processor::z_push_stack,
	&Processor::z_put_wind_prop,
	&Processor::z_print_form,
	&Processor::z_make_menu,
	&Processor::z_picture_table,
	&Processor::z_buffer_screen		// spec 1.1
};

Processor::Processor(OSystem *syst, const GlkGameDescription &gameDesc) :
		GlkInterface(syst, gameDesc),
		_finished(0), _sp(nullptr), _fp(nullptr), _frameCount(0),
		zargc(0), _decoded(nullptr), _encoded(nullptr), _resolution(0),
		_randomInterval(0), _randomCtr(0), first_restart(true), script_valid(false),
		_bufPos(0), _locked(false), _prevC('\0'), script_width(0),
		sfp(nullptr), rfp(nullptr), pfp(nullptr), ostream_screen(true), ostream_script(false),
		ostream_memory(false), ostream_record(false), istream_replay(false), message(false) {
	static const Opcode OP0_OPCODES[16] = {
		&Processor::z_rtrue,
		&Processor::z_rfalse,
		&Processor::z_print,
		&Processor::z_print_ret,
		&Processor::z_nop,
		&Processor::z_save,
		&Processor::z_restore,
		&Processor::z_restart,
		&Processor::z_ret_popped,
		&Processor::z_catch,
		&Processor::z_quit,
		&Processor::z_new_line,
		&Processor::z_show_status,
		&Processor::z_verify,
		&Processor::__extended__,
		&Processor::z_piracy
	};
	static const Opcode OP1_OPCODES[16] = {
		&Processor::z_jz,
		&Processor::z_get_sibling,
		&Processor::z_get_child,
		&Processor::z_get_parent,
		&Processor::z_get_prop_len,
		&Processor::z_inc,
		&Processor::z_dec,
		&Processor::z_print_addr,
		&Processor::z_call_s,
		&Processor::z_remove_obj,
		&Processor::z_print_obj,
		&Processor::z_ret,
		&Processor::z_jump,
		&Processor::z_print_paddr,
		&Processor::z_load,
		&Processor::z_call_n
	};

	op0_opcodes.resize(16);
	op1_opcodes.resize(16);
	Common::copy(&OP0_OPCODES[0], &OP0_OPCODES[16], &op0_opcodes[0]);
	Common::copy(&OP1_OPCODES[0], &OP1_OPCODES[16], &op1_opcodes[0]);
	Common::fill(&_stack[0], &_stack[STACK_SIZE], 0);
	Common::fill(&zargs[0], &zargs[8], 0);
	Common::fill(&_buffer[0], &_buffer[TEXT_BUFFER_SIZE], '\0');
	Common::fill(&_errorCount[0], &_errorCount[ERR_NUM_ERRORS], 0);
}

void Processor::initialize() {
	Mem::initialize();
	GlkInterface::initialize();

	if (h_version <= V4) {
		op0_opcodes[9] = &Processor::z_pop;
		op1_opcodes[15] = &Processor::z_not;
	} else {
		op0_opcodes[9] = &Processor::z_catch;
		op1_opcodes[15] = &Processor::z_call_n;
	}
}

void Processor::load_operand(zbyte type) {
	zword value;

	if (type & 2) {
		// variable
		zbyte variable;

		CODE_BYTE(variable);

		if (variable == 0)
			value = *_sp++;
		else if (variable < 16)
			value = *(_fp - variable);
		else {
			zword addr = h_globals + 2 * (variable - 16);
			LOW_WORD(addr, value);
		}
	} else if (type & 1) {
		// small constant
		zbyte bvalue;

		CODE_BYTE(bvalue);
		value = bvalue;

	} else {
		// large constant
		CODE_WORD(value);
	}

	zargs[zargc++] = value;
}

void Processor::load_all_operands(zbyte specifier) {
	for (int i = 6; i >= 0; i -= 2) {
		zbyte type = (specifier >> i) & 0x03;

		if (type == 3)
			break;

		load_operand(type);
	}
}

void Processor::interpret() {
	do {
		zbyte opcode;
		CODE_BYTE(opcode);
		zargc = 0;

		if (opcode < 0x80) {
			// 2OP opcodes
			load_operand((zbyte)(opcode & 0x40) ? 2 : 1);
			load_operand((zbyte)(opcode & 0x20) ? 2 : 1);

			(*this.*var_opcodes[opcode & 0x1f])();

		} else if (opcode < 0xb0) {
			// 1OP opcodes
			load_operand((zbyte)(opcode >> 4));

			(*this.*op1_opcodes[opcode & 0x0f])();

		} else if (opcode < 0xc0) {
			// 0OP opcodes
			(*this.*op0_opcodes[opcode - 0xb0])();


		} else {
			// VAR opcodes
			zbyte specifier1;
			zbyte specifier2;

			if (opcode == 0xec || opcode == 0xfa) {	// opcodes 0xec
				CODE_BYTE(specifier1);			// and 0xfa are
				CODE_BYTE(specifier2);          // call opcodes
				load_all_operands(specifier1);	// with up to 8
				load_all_operands(specifier2);	// arguments
			} else {
				CODE_BYTE(specifier1);
				load_all_operands(specifier1);
			}

			(*this.*var_opcodes[opcode - 0xc0])();
		}

#if defined(DJGPP) && defined(SOUND_SUPPORT)
		if (end_of_sound_flag)
			end_of_sound();
#endif
	} while (!shouldQuit() && !_finished);

	_finished--;
}

void Processor::call(zword routine, int argc, zword *args, int ct) {
	uint32 pc;
	zword value;
	zbyte count;
	int i;

	if (_sp - _stack < 4)
		runtimeError(ERR_STK_OVF);

	GET_PC(pc);

	*--_sp = (zword)(pc >> 9);
	*--_sp = (zword)(pc & 0x1ff);
	*--_sp = (zword)(_fp - _stack - 1);
	*--_sp = (zword)(argc | (ct << (_quetzal ? 12 : 8)));

	_fp = _sp;
	_frameCount++;

	// Calculate byte address of routine
	if (h_version <= V3)
		pc = (long)routine << 1;
	else if (h_version <= V5)
		pc = (long)routine << 2;
	else if (h_version <= V7)
		pc = ((long)routine << 2) + ((long)h_functions_offset << 3);
	else if (h_version <= V8)
		pc = (long)routine << 3;
	else {
		// h_version == V9
		long indirect = (long)routine << 2;
		HIGH_LONG(indirect, pc);
	}

	if ((uint)pc >= story_size)
		runtimeError(ERR_ILL_CALL_ADDR);

	SET_PC(pc);

	// Initialise local variables
	CODE_BYTE(count);

	if (count > 15)
		runtimeError(ERR_CALL_NON_RTN);
	if (_sp - _stack < count)
		runtimeError(ERR_STK_OVF);

	if (_quetzal)
		_fp[0] |= (zword)count << 8;	// Save local var count for Quetzal.

	value = 0;

	for (i = 0; i < count; i++) {
		if (h_version <= V4)		// V1 to V4 games provide default
			CODE_WORD(value);		// values for all local variables

			*--_sp = (zword)((argc-- > 0) ? args[i] : value);
	}

	// Start main loop for direct calls
	if (ct == 2)
		interpret();
}

void Processor::ret(zword value) {
	offset_t pc;
	int ct;

	if (_sp > _fp)
		runtimeError(ERR_STK_UNDF);

	_sp = _fp;

	ct = *_sp++ >> (_quetzal ? 12 : 8);
	_frameCount--;
	_fp = _stack + 1 + *_sp++;
	pc = *_sp++;
	pc = ((offset_t)*_sp++ << 9) | pc;

	SET_PC(pc);

	// Handle resulting value
	if (ct == 0)
		store(value);
	if (ct == 2)
		*--_sp = value;

	// Stop main loop for direct calls
	if (ct == 2)
		_finished++;
}

void Processor::branch(bool flag) {
	offset_t pc;
	zword offset;
	zbyte specifier;
	zbyte off1;
	zbyte off2;

	CODE_BYTE(specifier);
	off1 = specifier & 0x3f;

	if (!flag)
		specifier ^= 0x80;

	if (!(specifier & 0x40)) {
		// it's a long branch
		if (off1 & 0x20)		// propagate sign bit
			off1 |= 0xc0;

		CODE_BYTE(off2);
		offset = (off1 << 8) | off2;
	} else {
		// It's a short branch
		offset = off1;
	}

	if (specifier & 0x80) {
		if (offset > 1) {
			// normal branch
			GET_PC(pc);
			pc += (short)offset - 2;
			SET_PC(pc);
		} else {
			// special case, return 0 or 1
			ret(offset);
		}
	}
}

void Processor::store(zword value) {
	zbyte variable;

	CODE_BYTE(variable);

	if (variable == 0)
		*--_sp = value;
	else if (variable < 16)
		*(_fp - variable) = value;
	else {
		zword addr = h_globals + 2 * (variable - 16);
		SET_WORD(addr, value);
	}
}

int Processor::direct_call(zword addr) {
	zword saved_zargs[8];
	int saved_zargc;
	int i;

	// Calls to address 0 return false
	if (addr == 0)
		return 0;

	// Save operands and operand count
	for (i = 0; i < 8; i++)
		saved_zargs[i] = zargs[i];

	saved_zargc = zargc;

	// Call routine directly
	call(addr, 0, 0, 2);

	// Restore operands and operand count
	for (i = 0; i < 8; i++)
		zargs[i] = saved_zargs[i];

	zargc = saved_zargc;

	// Resulting value lies on top of the stack
	return (short)*_sp++;
}

void Processor::seed_random(int value) {
	if (value == 0) {
		// Now using random values
		_randomInterval = 0;
	} else if (value < 1000) {
		// special seed value
		_randomCtr = 0;
		_randomInterval = value;
	} else {
		// standard seed value
		_random.setSeed(value);
		_randomInterval = 0;
	}
}

void Processor::__extended__() {
	zbyte opcode;
	zbyte specifier;

	CODE_BYTE(opcode);
	CODE_BYTE(specifier);

	load_all_operands(specifier);

	if (opcode < 0x1e)					// extended opcodes from 0x1e on
		(*this.*ext_opcodes[opcode])();	// are reserved for future spec'
}

void Processor::__illegal__() {
	runtimeError(ERR_ILL_OPCODE);
}

void Processor::z_catch() {
	store(_quetzal ? _frameCount : (zword)(_fp - _stack));
}

void Processor::z_throw() {
	if (_quetzal) {
		if (zargs[1] > _frameCount)
			runtimeError(ERR_BAD_FRAME);

		// Unwind the stack a frame at a time.
		for (; _frameCount > zargs[1]; --_frameCount)
			_fp = _stack + 1 + _fp[1];
	} else {
		if (zargs[1] > STACK_SIZE)
			runtimeError(ERR_BAD_FRAME);

		_fp = _stack + zargs[1];
	}

	ret(zargs[0]);
}

void Processor::z_call_n() {
	if (zargs[0] != 0)
		call(zargs[0], zargc - 1, zargs + 1, 1);
}

void Processor::z_call_s() {
	if (zargs[0] != 0)
		call(zargs[0], zargc - 1, zargs + 1, 0);
	else
		store(0);
}

void Processor::z_check_arg_count() {
	if (_fp == _stack + STACK_SIZE)
		branch(zargs[0] == 0);
	else
		branch(zargs[0] <= (*_fp & 0xff));
}

void Processor::z_jump() {
	offset_t pc;
	GET_PC(pc);

	pc += (short)zargs[0] - 2;

	if (pc >= story_size)
		runtimeError(ERR_ILL_JUMP_ADDR);

	SET_PC(pc);
}

void Processor::z_nop() {
	// Do nothing
}

void Processor::z_quit() {
	_finished = 9999;
}

void Processor::z_ret() {
	ret(zargs[0]);
}

void Processor::z_ret_popped() {
	ret(*_sp++);
}

void Processor::z_rfalse() {
	ret(0);
}

void Processor::z_rtrue() {
	ret(1);
}

void Processor::z_random() {
	if ((short) zargs[0] <= 0) {
		// set random seed
		seed_random(- (short) zargs[0]);
		store(0);

	} else {
		// generate random number
		zword result;
		if (_randomInterval != 0) {
			// ...in special mode
			result = _randomCtr++;
			if (_randomCtr == _randomInterval)
				_randomCtr = 0;
		} else {
			// ...in standard mode
			result = _random.getRandomNumber(0xffff);
		}

		store((zword)(result % zargs[0] + 1));
	}
}

void Processor::z_sound_effect() {
	zword number = zargs[0];
	zword effect = zargs[1];
	zword volume = zargs[2];

	if (zargc < 1)
		number = 0;
	if (zargc < 2)
		effect = EFFECT_PLAY;
	if (zargc < 3)
		volume = 8;

	if (number >= 3 || number == 0) {
		_soundLocked = true;

		if (_storyId == LURKING_HORROR && (number == 9 || number == 16)) {
			if (effect == EFFECT_PLAY) {
				next_sample = number;
				next_volume = volume;

				_soundLocked = false;

				if (!_soundPlaying)
					start_next_sample();
			} else {
				_soundLocked = false;
			}
			return;
		}

		_soundPlaying = false;

		switch (effect) {
		case EFFECT_PREPARE:
			os_prepare_sample(number);
			break;
		case EFFECT_PLAY:
			start_sample(number, lo(volume), hi(volume), (zargc == 4) ? zargs[3] : 0);
			break;
		case EFFECT_STOP:
			os_stop_sample (number);
			break;
		case EFFECT_FINISH_WITH:
			os_finish_with_sample (number);
			break;
		}

		_soundLocked = false;
	} else {
		os_beep(number);
	}
}

void Processor::z_piracy() {
	branch(!_piracy);
}

void Processor::z_save_undo(void) {
	store((zword)save_undo());
}

void Processor::z_restore_undo(void) {
	store((zword)restore_undo());
}

} // End of namespace Frotz
} // End of namespace Glk