package ga050426;

import ga050426.PAddresses.addressKinds;

import java.util.regex.*;
import java.io.*;
import java.util.StringTokenizer;

// Main class - Assembler
public final class Asm {
	static final int EXIT_FAILURE = 1;

	public static int lc = 0;
	public static boolean end = false;
	public static OLEntries objLine = new OLEntries();
	public static int segmentsCounter=0;//only for second pass

	public static void main(String[] args) {
		if (args.length != 1) {
			System.out.println("Error in program argument(s)!");
			System.exit(Asm.EXIT_FAILURE);
		}
		firstPass(args[0]);
		if (Errors.errors.err() > 0) {
			System.out.println("Error(s): " + Errors.errors);
			System.exit(EXIT_FAILURE);
		}
		else {			
			secondPass();
		}
		if (Errors.errors.err() > 0) {
			System.out.println("Error(s): " + Errors.errors);
			System.exit(EXIT_FAILURE);
		}
	}
	
	
	
	public static void firstPass(String arg){
		BufferedReader file = null;
		String input = null;
		short srcLine = 0;
		Asm.lc=0;
		Asm.end=false;
		
		try {                                                           // Trying to open specified file
			file = new BufferedReader(new FileReader(arg));
		} catch (FileNotFoundException e) {
			System.out.println("Error: Specified file not found!");
			System.exit(EXIT_FAILURE);
		}                                                               // Open file
	
		try {
			while ((input = file.readLine()) != null && !Asm.end) {
				try{
																		// coment
					if (Pattern.compile(Grammar.comment).matcher(input).matches())continue;	
					ULEntries suorceLine= new ULEntries();
					srcLine++;
					Parser.parse(suorceLine, input, srcLine);
					UnpackedLines.instance.enter(suorceLine.labelled, suorceLine.labField, suorceLine.mnemonic, suorceLine.address);
					Asm.objLine.location=Asm.lc;
					Asm.objLine.address=0;
					Asm.objLine.opcode=Opcodes.getOpcode(suorceLine.mnemonic);
					if(Asm.objLine.opcode>Opcodes.JSR){//Directives
						switch(Asm.objLine.opcode){
						case Opcodes.BEG:
							Asm.lc=0;
							break;
						case Opcodes.END:
							Segments.instance.end(Asm.lc);
							Asm.end=true;
							break;	
						case Opcodes.DC:
							if(Segments.currentSegmentReference==null)
								throw new IllegalInstructionException(input + Exceptions.DSNOTINDATA);
							else if (!Segments.currentSegmentReference.name.equals("dat"))
								throw new IllegalInstructionException(input + Exceptions.DSNOTINDATA);								
							if(suorceLine.labelled)Symbols.instance.enter(suorceLine.labField, (short)Asm.lc);
							Asm.lc++;	
							break;
						case Opcodes.DS:
							if(Segments.currentSegmentReference==null)
								throw new IllegalInstructionException(input + Exceptions.DCNOTINDATA);
							else if (!Segments.currentSegmentReference.name.equals("bss"))
								throw new IllegalInstructionException(input + Exceptions.DCNOTINDATA);				
							if(suorceLine.labelled)Symbols.instance.enter(suorceLine.labField, (short)Asm.lc);
							Asm.objLine.address=Asm.evaluate(suorceLine.address);
							Asm.lc+=Asm.objLine.address;
							break;
						case Opcodes.EQU:
							Asm.objLine.address=Asm.evaluate(suorceLine.address);
							Symbols.instance.enter(suorceLine.labField, Asm.objLine.address);
							break;
						case Opcodes.TXT:
							Segments.instance.add("txt", Asm.lc);
							break;
						case Opcodes.BSS:
							Segments.instance.add("bss", Asm.lc);
							break;
						case Opcodes.DAT:
							Segments.instance.add("dat", Asm.lc);
							break;
							
						case Opcodes.DEF:
							if (Segments.currentSegmentReference != null)
								throw new IllegalInstructionException(input + Exceptions.USEDEF);
							StringTokenizer st = new StringTokenizer(input, " \t,");
							st.nextToken();
							while (st.hasMoreTokens()) {
								String label = st.nextToken();
								Symbols.instance.enter(label, true, false);
							}					
							break;
						case Opcodes.USE:
							if (Segments.currentSegmentReference != null)
								throw new IllegalInstructionException(input + Exceptions.USEDEF);
							StringTokenizer str = new StringTokenizer(input, " \t,");
							str.nextToken();
							while (str.hasMoreTokens()) {
								String label = str.nextToken();
								Symbols.instance.enter(label, false, true);
							}	
							break;
						}
					}
					else{
						if(Segments.currentSegmentReference==null)
							Segments.instance.add("txt", Asm.lc);
						if(Segments.currentSegmentName.equals("bss")||Segments.currentSegmentName.equals("dat"))
							throw new IllegalInstructionException(input + Exceptions.TXTONLY);
						if(suorceLine.labelled)
							Symbols.instance.enter(suorceLine.labField, (short)Asm.lc);
						if( Asm.objLine.opcode>Opcodes.HLT && Asm.objLine.opcode<Opcodes.BEG)
							Asm.lc+=2;
						else Asm.lc++;
					}
					ObjectLines.instance.enter(Asm.objLine.location, Asm.objLine.opcode, Asm.objLine.address);
				
				
				} catch (IllegalInstructionException iie) {
					System.out.println(String.format(
							"[Error %2s]@Line %-2d: %s", Errors.errors.inc(),
							srcLine, iie));
				} catch (SymbolLeavedUndefinedException iie) {
					System.out.println(String.format(
							"[Error %2s]@Line %-2d: %s", Errors.errors.inc(),
							srcLine, iie));
				
				} catch (SymbolDefinedException sde) {
					System.out.println(String.format(
							"[Error %2s]@Line %-2d: %s", Errors.errors.inc(),
							srcLine, sde));
					
					
				}//end of try
			}//end of while
			if(!Asm.end) throw new PrematureEndOfSource(); 
		
		} catch (IOException ioe) {
			System.exit(EXIT_FAILURE);
		} catch (PrematureEndOfSource sde) {
				System.out.println(String.format(
						"[Error %2s] %s", Errors.errors.inc(),
						sde));

		} finally {
			try {
				file.close();
				if (!end)
					Segments.instance.end(Asm.lc);
			} catch (IOException e) {
			}
		}	
	}
	

	
	
	public static void secondPass(){
		ULEntries suorceLine= new ULEntries();
		Asm.end=false;
		Asm.segmentsCounter=0;
		int srcLineNum=0;
		try{
			while(suorceLine!=null && Asm.objLine!=null && Asm.end!=true){
		
					srcLineNum++;
					suorceLine=UnpackedLines.instance.getNextEntrie();
					Asm.objLine=ObjectLines.instance.getNextEntrie();
					if(suorceLine==null || Asm.objLine==null)throw new SymbolDefinedException();
					assembleLine(suorceLine, Asm.objLine);
			}
		}catch(SymbolDefinedException sdfe){
			
		}catch(IllegalInstructionException iie){
			

		}catch(SymbolLeavedUndefinedException iie){
			System.out.println(String.format(
					"[Error %2s]@Line %-2d: %s", Errors.errors.inc(),
					srcLineNum, iie));
	    }
		
		
		if (Errors.errors.err() > 0) {
			System.out.println("Error(s): " + Errors.errors);
			System.exit(EXIT_FAILURE);
		}	
		System.out.println(String.format("LINK%n%d %d %d",
				Segments.instance.defs, Symbols.instance.defs,
				Relocations.instance.rels));

		System.out.print(Segments.instance.getDefs());
		System.out.print(Symbols.instance);
		System.out.print(Relocations.instance);
		System.out.print(Segments.instance.getSegs());
	}
	
	public static short evaluate(PAddresses address)throws
	SymbolLeavedUndefinedException{
		if (address.kind.equals(addressKinds.numeric))return address.value;
		else if(address.kind.equals(addressKinds.star))return (short)Asm.lc;
		else if (address.kind.equals(addressKinds.alphameric) && (Symbols.instance.getValueOf(address.name).defined))
				return Symbols.instance.getValueOf(address.name).value;
		else {
			System.out.println("Error: Symbol " + address.name
					+ " referenced in byte " + Asm.objLine.location 
					+ " never defined or declared!");
			Errors.errors.inc();
			return -1;
		}
	}
	
	public static void assembleLine(ULEntries uline, OLEntries oline)
		throws IllegalInstructionException, SymbolLeavedUndefinedException{
		Asm.lc=oline.location;
		if(oline.opcode>Opcodes.JSR){
			switch(oline.opcode){
			case Opcodes.END:
				Asm.end=true;
				break;
			case Opcodes.DC:
				oline.address=evaluate(uline.address);
				if(uline.address.kind.equals(addressKinds.alphameric))
					Relocations.instance.enter(Asm.lc, Asm.segmentsCounter, uline.address.name,uline.mnemonic);
				if(uline.address.kind.equals(addressKinds.star))
					Relocations.instance.enter(Asm.lc, Asm.segmentsCounter,"", uline.mnemonic);
				DataSection.buff.insert((byte)oline.address, Asm.lc++);
				break;
			case Opcodes.TXT:
				Asm.segmentsCounter++;
				break;
			case Opcodes.BSS:
				Asm.segmentsCounter++;				
				break;
			case Opcodes.DAT:
				Asm.segmentsCounter++;
				break;
			}
		}
		else if(oline.opcode>Opcodes.HLT && oline.opcode<Opcodes.BEG){
			if(Asm.segmentsCounter==0)Asm.segmentsCounter++; 
			oline.address=evaluate(uline.address);
			if(uline.address.kind.equals(addressKinds.alphameric))
				Relocations.instance.enter(Asm.lc, Asm.segmentsCounter, uline.address.name, uline.mnemonic);
			if(uline.address.kind.equals(addressKinds.star))
				Relocations.instance.enter(Asm.lc, Asm.segmentsCounter, "", uline.mnemonic);
			short ir=BitsManipulator.makeIRWord(oline.opcode, oline.address);
			DataSection.buff.insert(BitsManipulator.getHigherByte(ir), Asm.lc++);
			DataSection.buff.insert(BitsManipulator.getLowerByte(ir), Asm.lc++);
			}
		else{
			short ir=BitsManipulator.makeIRWord(oline.opcode);
			DataSection.buff.insert(BitsManipulator.getHigherByte(ir), Asm.lc++);
		}
	}
}
