#include "Process.h"
#include "Dice.h"
#include "Boolean.h"

//********************** Program ************************************

Process::Process(int serialNumber, istream& in, ostream& out)
:	_input(in),
	_output(out),
	_pc(0),
	_state(1),//active
	_instructionCount(0),
	_program(Array< Association<int, int> > ()),
	_programLength(0),
	_stack(Stack<int>()),
	_serialNumber(serialNumber),
	_memory(NULL)
{
}

void Process::setSerialNumber(int serialNumber)
{	_serialNumber = serialNumber;
}

void Process::insert(Association<int, int> a)
{	if(_programLength == _program.length()) _program.extendBy(5);
	_program[_programLength++] = a;
}

void Process::reset()
{	_pc = 0;
	_stack = Stack<int>(); // clear the stack;
}

void Process::instruction(Instruction opcode, int operand)
{	insert(Association<int, int>(opcode, operand));
}
 
void Process::pushi(int i)
{	insert(Association<int, int>(Pushi,i));
}

void Process::pushv(unsigned int v)
{	insert(Association<int, int>(Pushv,v));
}

void Process::pop(unsigned int v)
{	insert(Association<int, int>(Pop,v));
}

void Process::halt()
{	insert(Association<int, int>(Halt,0));
}

void Process::sum()
{	insert(Association<int, int>(Sum,0));
}

void Process::diff()
{	insert(Association<int, int>(Diff,0));
}

void Process::prod()
{	insert(Association<int, int>(Prod,0));
}

void Process::quot()
{	insert(Association<int, int>(Quot,0));
}

void Process::pos(int L)
{	insert(Association<int, int>(Pos,L));
}

void Process::zero(int L)
{	insert(Association<int, int>(Zero,L));
}

void Process::jmp(int L)
{	insert(Association<int, int>(Jmp,L));
}

void Process::input()
{	insert(Association<int, int>(Input,0));
}

void Process::output()
{	insert(Association<int, int>(Output,0));
}

void Process::toss()
{	insert(Association<int, int>(Toss,0));
}

void Process::dup()
{	insert(Association<int, int>(Dup,0));
}

void Process::declare(unsigned int v)
{	insert(Association<int, int>(Define,v));
}

ostream & operator<<(ostream & os, const Process & P)
{	os << "PROGRAM "<<P._serialNumber<<endl;
	os << "PC: "<<P._pc<<(P._state?" Active ":" Inactive ")<<endl;
	for (int i = 0; i < P._programLength; i++)
		os<< '<'<<P._program[i].key()<<','<<P._program[i].value()<<"> ";
	os<<endl;
	os<<"Stack: ";
	if(!P._stack.empty())
	{	Array<int> curStack = P._stack.asArray();
		for(int i = 0; i< curStack.length(); i++)
			os<< curStack[i]<<' ';
		os <<endl;
	}
	return os;
}

//********************** Simulator ************************************

Simulator::Simulator()
:	_processes(Queue<Process *> (NULL)),
	_processCount(0),
	_instructionCount(0),
	_haltedProcesses(List<Process *>()),
	_current(NULL),
	_quantum(0),
	_pc(0),
	_memory(Dictionary<unsigned int, int>())
{
}

void Simulator::step(unsigned int n)
{	unsigned int quittime = _instructionCount + n;
	while(! _processes.empty() && _instructionCount < quittime)
	{	Die roller(8);
		_current = _processes.dequeue();
//	cout <<endl<<"R "<< *_current<<' ';
		_processCount--;
		_pc = _current->_pc;
		_quantum = roller.roll();
//	cout << _quantum<<endl;
		unsigned int quitthis = _instructionCount + _quantum;
		Boolean terminated = false;
		while(_instructionCount < quitthis && & terminated)
		{	_instructionCount++;
			// fetch
				Association<int, int> * IB = &(_current->_program[_pc]); 
			// increment
//	cout << _pc<<' ';
				_pc++;
			// decode
				int right;
				switch(IB->key())
			// do
				{	case Halt:
						_current->_state = 0; //halted
						_haltedProcesses = _current + _haltedProcesses;
						terminated = true;
						break;
					case Sum:
						right = _current->_stack.pop();
						_current->_stack.push(_current->_stack.pop() + right);
						break;
					case Diff:
						right = _current->_stack.pop();
						_current->_stack.push(_current->_stack.pop() - right);
						break;
					case Prod:
						right = _current->_stack.pop();
						_current->_stack.push(_current->_stack.pop() * right);
						break;
					case Quot:
						right = _current->_stack.pop();
						if(right == 0)userERROR("Zero Divide.");
						_current->_stack.push(_current->_stack.pop() / right);
						break;
					case Pos:
						if(_current->_stack.top() > 0) _pc = IB->value();
						break;
					case Zero:
						if(_current->_stack.top() == 0) _pc = IB->value();
						break;
					case Jmp:
						_pc = IB->value();
						break;
					case Dup:
						_current->_stack.push(_current->_stack.top());
						break;
					case Input:
						int val;
						if(_current->_input>>val) 
						{	_current->_stack.push(val);
//	cout<< "[ "<<val<<"]";
						}
						else
						{	_current->_state = 0; //halted
							_haltedProcesses = _current + _haltedProcesses;
							terminated = true;
						} 
						break;
					case Output: 
						_current->_output <<_current->_stack.top() << endl;
//						_current->_output << "< "<<_current->_stack.top() <<" >"<<endl;
						break;
					case Pushi:
						_current->_stack.push(IB->value());
						break;
					case Pushv: 
						int temp;
						if(!_current->_memory->element(IB->value()))
							userERROR("ILLEGAL Memory Location");
						_current->_memory->at(IB->value(),temp);
						_current->_stack.push(temp);
						break;
					case Pop:
						right = _current->_stack.pop();
//	cout << "popping "<< right<<' ';
						if(!_current->_memory->element(IB->value()))
							userERROR("ILLEGAL Memory Location");
						_current->_memory->atPut(IB->value(),right);
						_current->_memory->at(IB->value(),temp);
//	cout << "stored "<<temp<<" at "<<IB->value()<<' ';
						break;
					case Toss:
						_current->_stack.pop(); // discard
						break;
					case Define:
						if(!_current->_memory->element(IB->value()))
						{	_current->_memory->atPut(IB->value(),0);
						}
						break;
				}
			
		}
		if(_current->_state == 1)
		{	_processes.enqueue(_current);
			_processCount++;
		}
		_current->_pc = _pc;
	}
}

void Simulator::insertProcess(Process * P)
{	P->_memory = &_memory;
	_processes.enqueue(P);
	_processCount++;
}
 
void Simulator::reset()
{	Process * p;
	for(int i = 0; i<_processCount; i++)
	{	p = _processes.dequeue();
		p->reset();
		_processes.enqueue(p);
	}
	while(!_haltedProcesses.empty())
	{	p = _haltedProcesses.head();
		p->reset();
		_processes.enqueue(p);
		_processCount++;
		_haltedProcesses = _haltedProcesses.tail();
	}
}

Simulator::Simulator(Simulator & x)
:	_processes(x._processes)
{	userERROR("Illegal Simulator copy.");
}

Simulator& Simulator::operator=(Simulator &)
{	userERROR("Illegal Simulator assignment.");
	return *this;
}

ostream & operator<<(ostream & os, const Simulator & M)
{	os << "SIMULATOR "<<endl;
	os << "Active Processes: "<<M._processCount<<endl;
	unsigned int memsize = M._memory.cardinality();
	os << "Memory Size: "<<memsize<<endl;
	if(memsize>0)
	{	Array< Association<unsigned int, int> > curMem = M._memory.asArray();
		for (int i = 0; i< M._memory.cardinality(); i++)
			os << '['<<curMem[i].key()<< ','<<curMem[i].value()<<"] ";
		os<<endl;
	}
	return os;
}


