package com.polyfx.jssembly;

import java.nio.ByteBuffer;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.Lexer;
import org.antlr.runtime.RecognitionException;

import static com.polyfx.jssembly.platforms.Architecture.*;

import com.polyfx.jssembly.platforms.Architecture;
import com.polyfx.jssembly.platforms.Assembler;
import com.polyfx.jssembly.platforms.x64.x64Assembler;
import com.polyfx.jssembly.platforms.x64.x64Lexer;

public class Block {
	private Architecture architecture;
	private byte[] instructions;
	private String program = "";
	
	public Block(byte[] instr) {
		this.instructions = instr;
		this.architecture = raw;
	}

	public Block(Architecture arch) {
		this.architecture = arch;
		Lexer lex = null;
		Assembler assembler = null;
		
		switch (architecture) {
			// no parsing necessary if we're writing bytes to memory
			case raw:
				return;
			// arm architectures trickle down
			case armv7:
			case armv9:
				// lex = new armLexer(new ANTLRStringStream(program));
				// assembler = new armAssembler(new CommonTokenStream(lex));
				break;
			case x64:
				lex = new x64Lexer(new ANTLRStringStream(program));
				assembler = new x64Assembler(new CommonTokenStream(lex));
				break;
			case x86:
				break;
			default:
				break;
		}
		
		try {
			if (assembler != null) {
				assembler.start();
				this.instructions = assembler.getMachineCode();
			} else {
				throw new JssemblyException("Assembler not found for architecture: " + this.architecture.name());
			}
		} catch (RecognitionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/*
	 * Direct bytes go directly in the buffer (and to memory via the JNI bridge)
	 * 
	 * NOTE: we use ints here otherwise we need to do annoying casting in the caller
	 */
	public void __asm(int ... bytes) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
		for (int i : bytes) {
			byteBuffer.put((byte) i);
		}
		this.instructions = byteBuffer.array();
	}

	/*
	 * One line at a time
	 */
	public void __asm(String line) {
		this.program += (line + '\n');
	}

	/*
	 * Multiple lines
	 */
	public void __asm(String... lines) {
		for (String line : lines) {
			this.__asm(line);
		}

	}

	/*
	 * 
	 */
	public String getProgram() {
		return this.program;
	}
	
	/*
	 * 
	 */
	public void invoke(Object... args) {
		Jssembly.invoke(this.instructions, args);
	}
}