// idbg - IJVM debugger.
// Copyright 2000 Tom Rothamel <tom-idbg@onegeek.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 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., 675 Mass Ave, Cambridge, MA 02139, USA.  

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "ijvm.h"
#include "idbg.h"

// Implementation of opcodes ////////////////////////////////////////////////

int op_bipush(VM *vm) {
	vm->push(vm->p->byte);
	return 1;
}

int op_dup(VM *vm) {
	int n;

	n = vm->pop();
	vm->push(n);
	vm->push(n);

	return 1;
}

int op_err(VM *vm) {
	vm->err = strdup("Program Error Halt.");
	return 0;
}

int op_goto(VM *vm) {
	vm->regs->pc += vm->p->label;
	vm->p->len = 0;

	return 1;
}

int op_halt(VM *vm) {
	vm->err = strdup("Halted.");
	return 0;
}

int op_iadd(VM *vm) {
	vm->push(vm->pop() + vm->pop());
	return 1;
}

int op_iand(VM *vm) {
	vm->push(vm->pop() & vm->pop());
	return 1;
}
	
int op_if_icmpeq(VM *vm) {
	int a;
	int b;

	a = vm->pop();
	b = vm->pop();

	if (a == b) {
		vm->regs->pc += vm->p->label;
		vm->p->len = 0;
	}

	return 1;
}

int op_ifeq(VM *vm) {
	if (vm->pop()) return 1;

	vm->regs->pc += vm->p->label;
	vm->p->len = 0;

	return 1;
}

int op_iflt(VM *vm) {
	if (vm->pop() >= 0) return 1;
	vm->regs->pc += vm->p->label;
	vm->p->len = 0;

	return 1;
}	

int op_iinc(VM *vm) {
	int a = vm->getvar(vm->p->variable);
	a += vm->p->byte;
	vm->setvar(vm->p->variable, a);
	return 1;
}

int op_iload(VM *vm) {
	vm->push(vm->getvar(vm->p->variable));
	return 1;
}

extern int errno;

int op_in(VM *vm) {
	int ch;

	ch = getch();
	/* The hacky way of handling ctrl-c. */
	if (ch == 3) {
		ch = 0;
		got_sigint = 1;
	}
	
	vm->push(ch);
	return 1;
}

int op_invokevirtual(VM *vm) {
	int newpc = vm->m->getword(vm->p->method + vm->regs->cpp);
	int params;
	int locals;
	int newlv;
	
	params = vm->m->getbyte(newpc++);
	params <<= 8;
	params += vm->m->getbyte(newpc++);

	locals = vm->m->getbyte(newpc++);
	locals <<= 8;
	locals += vm->m->getbyte(newpc++);

	newlv = (vm->regs->sp - params) + 1;
	
	vm->regs->sp += locals;
	// I think that vm->p->len should be added here, rather than
	// in ireturn.
	vm->push(vm->regs->pc + vm->p->len);

	vm->m->setword(newlv, vm->regs->sp);
	vm->push(vm->regs->lv);

	vm->regs->lv = newlv;
	vm->regs->pc = newpc;

	vm->p->len = 0;
	vm->depth++;
	
	return 1;
}

int op_ior(VM *vm) {
	vm->push(vm->pop() | vm->pop());
	return 1;
}

int op_ireturn(VM *vm) {
	int rv = vm->pop();
	int newsp = vm->m->getword(vm->regs->lv);
	int oldsp = vm->regs->lv;

	vm->regs->sp = newsp + 1;
	vm->regs->lv = vm->pop();
	vm->regs->pc = vm->pop();

	vm->regs->sp = oldsp;
	vm->pop();
	vm->push(rv);

	vm->p->len = 0;
	vm->depth--;
	
	return 1;
}

int op_istore(VM *vm) {
	vm->setvar(vm->p->variable, vm->pop());
	return 1;
}

int op_isub(VM *vm) {
	int a = vm->pop();
	int b = vm->pop();

	vm->push(b-a);
	return 1;
}

int op_ldc_w(VM *vm) {
	vm->push(vm->getconst(vm->p->constant));
	return 1;
}

int op_nop(VM *vm) {
	return 1;
}

int op_out(VM *vm) {
	printw("%c", vm->pop());

	return 1;
}

int op_pop(VM *vm) {
	vm->pop();
	return 1;
}

int op_swap(VM *vm) {
	int a = vm->pop();
	int b = vm->pop();

	vm->push(a);
	vm->push(b);

	return 1;
}

// IJVMA Instructions. //////////////////////////////////////////////////////

int op_alloca(VM *vm) {	
	int base = vm->regs->sp - 1;
	int len = vm->pop();
	vm->regs->sp += len;
	vm->push(base);

	return 1;
}

int op_base(VM *vm) {
	vm->regs->base = vm->pop();

	return 1;
}

int op_iget(VM *vm) {
	vm->push(vm->m->getword(vm->regs->base + vm->pop()));

	return 1;
}

int op_iput(VM *vm) {
	unsigned int addr = vm->regs->base + vm->pop();
	vm->m->setword(addr, vm->pop());

	return 1;
}
	
// Implemetation of VM, Opcode, etc. ////////////////////////////////////////

Opcode::Opcode(char *n, int l, int v, int c, int m, int b, int (*f)(VM *)) {
	name = n;
	label = l;
	variable = v;
	constant = c;
	method = m;
	byte = b;
	func = f;
}

int VM::getconst(unsigned int cn) {
	return m->getword(cn + regs->cpp);
}

int VM::getvar(unsigned int var) {
	return m->getword(var + regs->lv);
}

void VM::setvar(unsigned int var, unsigned int val) {
	m->setword(var + regs->lv, val);
}

void VM::push(int d) {
	regs->sp++;
	m->setword(regs->sp, d);
}

int VM::pop() {
	return m->getword(regs->sp--);
}

int VM::execute() {
	int rv;
	
	rv = ops[p->opcode]->func(this);
	if (!rv) return rv;
	regs->pc += p->len;
	return rv;
}
	
int VM::decode(unsigned int pc) {
	unsigned int pco = 0;
	Opcode *op;
	char emsg[128];
	
	p->wide = 0;
	p->opcode = 0;

	while (1) {
		p->opcode = m->getbyte(pc + pco++) & 0xff;
		if (p->opcode == 0xc4) { // wide
			p->opcode = 0;
			p->wide = 1;
		} else {
			break;
		}
	}

	op = ops[p->opcode];

	if (!op) {
		p->len = 1;

		snprintf(emsg, 128, "I have no idea what opcode %02X is.",
			 p->opcode);
		err = strdup(emsg);
		return 0;
	}

	if (op->label) {
		p->label = m->getbyte(pc + pco++);
		p->label <<= 8;
		p->label += m->getbyte(pc + pco++);
	}

	if (op->variable) {
		p->variable = m->getbyte(pc + pco++);
		if (p->wide) {
			p->variable <<= 8;
			p->variable += m->getbyte(pc + pco++);
		}
	}

	if (op->constant) {
		p->constant = m->getbyte(pc + pco++);
		p->constant <<= 8;
		p->constant += m->getbyte(pc + pco++);
	}

	if (op->method) {
		p->method = m->getbyte(pc + pco++);
		p->method <<= 8;
		p->method += m->getbyte(pc + pco++);
	}

	if (op->byte) {
		p->byte = m->getbyte(pc + pco++);
	}

	p->len = pco;
	return 1;
}	

void VM::load(FILE *f) {
	err = m->load(f);
}

VM::VM() {
	int i;

	err = NULL;
	depth = 0;
	
	m = new Memory();
	regs = new Regs();
	regs->pc = 0x0000;
	regs->sp = 0x7fff;
	regs->lv = 0xc000;
	regs->cpp = 0x4000;

	p = new Parse();

	for (i = 0; i < 0x100; i++) {
		ops[i] = NULL;
	}

	ops[0x00] = new Opcode("nop",
			       0, 0, 0, 0, 0, op_nop);
	ops[0x10] = new Opcode("bipush",
			       0, 0, 0, 0, 1, op_bipush);
	ops[0x13] = new Opcode("ldc_w",
			       0, 0, 1, 0, 0, op_ldc_w);
	ops[0x15] = new Opcode("iload",
			       0, 1, 0, 0, 0, op_iload);
	ops[0x36] = new Opcode("istore",
			       0, 1, 0, 0, 0, op_istore);
	ops[0x57] = new Opcode("pop",
			       0, 0, 0, 0, 0, op_pop);
	ops[0x59] = new Opcode("dup",
			       0, 0, 0, 0, 0, op_dup);
	ops[0x5f] = new Opcode("swap",
			       0, 0, 0, 0, 0, op_swap);
	ops[0x60] = new Opcode("iadd",
			       0, 0, 0, 0, 0, op_iadd);
	ops[0x64] = new Opcode("isub",
			       0, 0, 0, 0, 0, op_isub);
	ops[0x7e] = new Opcode("iand",
			       0, 0, 0, 0, 0, op_iand);
	ops[0x84] = new Opcode("iinc",
			       0, 1, 0, 0, 1, op_iinc);
	ops[0x99] = new Opcode("ifeq",
			       1, 0, 0, 0, 0, op_ifeq);
	ops[0x9b] = new Opcode("iflt",
			       1, 0, 0, 0, 0, op_iflt);
	ops[0x9f] = new Opcode("if_icmpeq",
			       1, 0, 0, 0, 0, op_if_icmpeq);
	ops[0xa7] = new Opcode("goto",
			       1, 0, 0, 0, 0, op_goto);
	ops[0xac] = new Opcode("ireturn",
			       0, 0, 0, 0, 0, op_ireturn);
	ops[0xb0] = new Opcode("ior",
			       0, 0, 0, 0, 0, op_ior);
	ops[0xb6] = new Opcode("invokevirtual",
			       0, 0, 0, 1, 0, op_invokevirtual);
// Wide is handled magically.
//	ops[0xc4] = new Opcode("wide", 0, 0, 0, 0, op_nop);
	ops[0xe0] = new Opcode("alloca",
			       0, 0, 0, 0, 0, op_alloca);
	ops[0xe1] = new Opcode("base",
			       0, 0, 0, 0, 0, op_base);
	ops[0xe2] = new Opcode("iget",
			       0, 0, 0, 0, 0, op_iget);
	ops[0xe3] = new Opcode("iput",
			       0, 0, 0, 0, 0, op_iput);	
	ops[0xff] = new Opcode("halt",
			       0, 0, 0, 0, 0, op_halt);
	ops[0xfe] = new Opcode("err",
			       0, 0, 0, 0, 0, op_err);
	ops[0xfd] = new Opcode("out",
			       0, 0, 0, 0, 0, op_out);
	ops[0xfc] = new Opcode("in",
			       0, 0, 0, 0, 0, op_in);
}

VM::~VM() {
	if (err) free(err);
	delete m;
	delete p;
	delete regs;
}
