package ga050426;

import  java.io.*;
import java.util.*;

public class Emulator {
	enum CPUstatus { running, finished, nodata, baddata, badop };
	public static byte mem[] = new byte[(int)0xffff];
	public static int toWrite = 0;
	public static int initPC = 0;
	public static CPUstatus ps;
	public static Citaj reader;
	
	public static void main(String[] args) {
		if (args.length != 1) {
			System.out.println("Error in program argument(s)!");
			System.exit(1);
		}
		interpret(args[0]);
	}
	public static void emulate(boolean tracing){
		int PCnow;
		int signum, signum1;
		short a;
		byte lower;
		byte higher;
		CPU.Z = false;
		CPU.P = false;
		CPU.C = false; // initialize flags
		CPU.A = 0;
		CPU.X = 0;
		CPU.SP =(int)0xffff;
		CPU.EAR = 0; // initialize registers
		CPU.PC = initPC;                             // initialize program counter
		  ps = CPUstatus.running;    
		do{
			CPU.IRh=mem[CPU.PC];
			PCnow=CPU.PC++;
			if(tracing){
				trace(PCnow);
				System.out.println("Execute instruction (y/Q)?");
				char t=Citaj.Char();
				if(Character.toLowerCase(t)!='y'){
					System.out.println("Emulation aborted !!");
					System.exit(1);
				}
			}
			switch(BitsManipulator.getOpcode(CPU.IRh)){
			//oneByteInstructions
			case Opcodes.NOP:
				break;
			case Opcodes.CLA:
				CPU.A=0; break;
			case Opcodes.CLC:
				CPU.C=false; break;
			case Opcodes.CLX:
				CPU.X=0; break;
			case Opcodes.CMC:
				CPU.C=!CPU.C; break;
			case Opcodes.INC:
				CPU.A++;setFlags(CPU.A); break;
			case Opcodes.DEC:
				CPU.A--;setFlags(CPU.A); break;
			case Opcodes.INX:
				CPU.X++;setFlags(CPU.X); break;
			case Opcodes.DEX:
				CPU.X--;setFlags(CPU.X); break;
			case Opcodes.TAX:
				CPU.X=CPU.A; break;
			case Opcodes.INI:
				try{
				CPU.A=reader.ShortF();setFlags(CPU.A);
				}catch(NumberFormatException nfe){
					System.out.println("Wrong input data!!");
					System.exit(1);
				}
				break;
			case Opcodes.INA:
				try{
					CPU.A=(short)reader.CharF();setFlags(CPU.A);
					}catch(NumberFormatException nfe){
						System.out.println("Wrong input data!!");
						System.exit(1);
					}
					break;
			case Opcodes.OTI:
				System.out.println(String.format("%d",CPU.A));
				break;
			case Opcodes.OTA:
				try{
				System.out.println(String.format("%c",CPU.A));
				}catch(IllegalFormatCodePointException ifo){
					System.out.println("Illegal format code point!!");
					System.exit(1);
				}
			case Opcodes.PSH:
				CPU.SP--;
				mem[CPU.SP]=BitsManipulator.getHigherByte(CPU.A);
				CPU.SP--;
				mem[CPU.SP]=BitsManipulator.getLowerByte(CPU.A);
				break;
			case Opcodes.POP:
				lower = mem[CPU.SP];
				CPU.SP++;
				higher = mem[CPU.SP];
				CPU.SP++;
				CPU.A=BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				break;
			case Opcodes.SHL:
				CPU.C = (-1==Integer.signum(CPU.A));
				CPU.A <<= 1; 
				setFlags(CPU.A);
				break;
			case Opcodes.SHR:
				CPU.C = (1 == (CPU.A & 1));
				CPU.A >>= 1;
				setFlags(CPU.A);
				break;
			case Opcodes.MSA:
				CPU.SP = CPU.A;
				break;
			case Opcodes.MAS:
				CPU.A =(short)CPU.SP;
				break;
			case Opcodes.LDA:
				lower = mem[CPU.A];
				CPU.A++;
				higher = mem[CPU.A];
				CPU.A=BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				break;
			case Opcodes.LDX:
				lower = mem[CPU.A + CPU.X];
				higher = mem[CPU.A + CPU.X + 1];
				CPU.A=BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				break;
			case Opcodes.RET:
				lower = mem[CPU.SP];
				CPU.SP++;
				higher = mem[CPU.SP];
				CPU.SP++;
				CPU.PC = BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;	
			case Opcodes.HLT:
				ps = CPUstatus.finished;
				break;
				
			//twoByteInstructios
			case Opcodes.LDI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.A=BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				break;
			case Opcodes.LDD:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.X;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				CPU.A = BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				break;
			case Opcodes.LDR:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				CPU.X = BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.X);
				break;
			case Opcodes.STA:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower);
				mem[CPU.EAR] = BitsManipulator.getLowerByte(CPU.A);
				mem[CPU.EAR + 1] = BitsManipulator.getHigherByte(CPU.A);				
				break;
			case Opcodes.STX:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.X;
				mem[CPU.EAR] = BitsManipulator.getLowerByte(CPU.A);
				mem[CPU.EAR + 1] = BitsManipulator.getHigherByte(CPU.A);	
				break;
			case Opcodes.ADD:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A += BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum==signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.ADX:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.X;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A += BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum==signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.ADI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A += BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum==signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.ADC:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				if(CPU.C)CPU.EAR++;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A += BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum==signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.SUB:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum!=signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.SBX:
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.X;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum!=signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.SBI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum!=signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.SBC:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				if(CPU.C)CPU.EAR++;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				signum = Integer.signum(CPU.A);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				CPU.A -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				if( signum!=signum1 && signum!=Integer.signum(CPU.A))CPU.C=true;
				break;
			case Opcodes.CMP:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				a = CPU.A;
				signum = Integer.signum(a);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				a -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(a);
				if( signum!=signum1 && signum!=Integer.signum(a))CPU.C=true;
				break;
			case Opcodes.CPI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				a=CPU.A;
				signum = Integer.signum(a);
				signum1 = Integer.signum(BitsManipulator.getShortFromTwoBytes(higher, lower));
				a -= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(a);
				if( signum!=signum1 && signum!=Integer.signum(a))CPU.C=true;
				break;
			case Opcodes.ANA:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				CPU.A &= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				CPU.C=false;
				break;
			case Opcodes.ANI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.A &= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				CPU.C=false;
				break;
			case Opcodes.ORA:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				CPU.A |= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				CPU.C=false;
				break;
			case Opcodes.ORI:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.A |= BitsManipulator.getShortFromTwoBytes(higher, lower);
				setFlags(CPU.A);
				CPU.C=false;
				break;
			case Opcodes.BRR:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BZE:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(CPU.Z)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BNZ:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(!CPU.Z)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BPZ:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(CPU.P)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BNG:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(!CPU.P)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BCC:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(!CPU.C)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.BCS:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				if(CPU.C)CPU.PC += BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			case Opcodes.JSR:
				CPU.IRl=mem[CPU.PC++];
				higher=BitsManipulator.getDataFromOpcode(CPU.IRh);
				lower=CPU.IRl;
				CPU.EAR = BitsManipulator.getShortFromTwoBytes(higher, lower) + CPU.PC;
				lower = mem[CPU.EAR];
				higher = mem[CPU.EAR + 1];
				CPU.SP--;
				mem[CPU.SP]=BitsManipulator.getHigherByte((short)CPU.PC);
				CPU.SP--;
				mem[CPU.SP]=BitsManipulator.getLowerByte((short)CPU.PC);
				CPU.PC=BitsManipulator.getShortFromTwoBytes(higher, lower);
				break;
			}
		}while(ps==CPUstatus.running);
	}
	public static void setFlags(int reg){
		CPU.Z = (reg==0);
		CPU.P = (reg>=0);
	}
	
	public static void interpret(String s){
		boolean tracing = false;
		String fileName;
		System.out.println("Trace execution (y/N/q)?");
		char t=Citaj.Char();
		if(!(t=='q'||t=='Q')){
			if(t=='y'||t=='Y')tracing=true;
			System.out.println("Enter data file name or STD for stdin..");
			fileName=Citaj.String();
			if(!fileName.toLowerCase().equals("std")){
				try{
					reader = new Citaj(fileName);
				}catch (FileNotFoundException fnf){
					System.out.println("File not found!");
					System.out.println("Taking data from stdin.");
					reader = new Citaj();
				}
			}else {
				reader = new Citaj();
				System.out.println("Taking data from stdin.");
			}
			Loader.load(s);
			emulate(tracing);
			System.out.println("Emulation successful!");
		}
	}
	public static void trace (int PC){
			System.out.println(String.format(" PC = %04x  A = %04x  ", PC, CPU.A));
			System.out.println(String.format(" X = %04x  SP = %04x  ", CPU.X, CPU.SP));
			System.out.println(String.format(" EAR = %04x ", CPU.EAR));
			System.out.println(String.format(" Z = %s  P = %s  C = %s ", CPU.Z, CPU.P, CPU.C));
			System.out.println(String.format(" OPCODE = %02x  MNEMONIC = %s", 
					BitsManipulator.getOpcode(CPU.IRh), Opcodes.getMnemonic(BitsManipulator.getOpcode(CPU.IRh))));
	}
	
	public static void memDump() {
		StringBuffer buf = new StringBuffer(String.format("#Mem (hex)%n"));
		for (int i = 0;i < toWrite ;i++) {
			buf.append(String.format("mem["+i+"]=%02x ", mem[i]));
				buf.append("\n");
			}
		System.out.print(new String(buf));
	}
}
