package de.jeckle;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
public class JAP {
	private static final String VERSION = "Version 1.1";
	static JFrame frame;
	static Logger log;
	JTextArea javaSrc, assDst, status;
	File srcFile = null;
	public static void main(String[] args) throws Exception {
		log = Logger.getLogger(JAP.class.getName());
		log.setLevel(Level.OFF);
		//log.setLevel(Level.ALL);
		UIManager.setLookAndFeel(UIManager
				.getCrossPlatformLookAndFeelClassName());
		JAP app = new JAP();
		frame = new JFrame("Java Assembler Playground " + JAP.VERSION);
		frame.setLayout(new GridLayout(3, 1));
		app.createMenu();
		app.createJavaSrcPane();
		app.createJavaAssPane();
		app.createStatusPane();
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		frame.pack();
		frame.setVisible(true);
	}
	private void createMenu() {
		JMenuBar menuBar = new JMenuBar();
		frame.setJMenuBar(menuBar);
		JMenu fileMenu = new JMenu("File");
		menuBar.add(fileMenu);
		JMenuItem newMenuItem = new JMenuItem("New");
		fileMenu.add(newMenuItem);
		newMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
				ActionEvent.CTRL_MASK));
		JMenuItem loadMenuItem = new JMenuItem("Open");
		fileMenu.add(loadMenuItem);
		loadMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
				ActionEvent.CTRL_MASK));
		JMenuItem saveMenuItem = new JMenuItem("Save");
		fileMenu.add(saveMenuItem);
		saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
				ActionEvent.CTRL_MASK));
		JMenuItem saveAsMenuItem = new JMenuItem("Save as");
		fileMenu.add(saveAsMenuItem);
		JMenu buildMenu = new JMenu("Build");
		menuBar.add(buildMenu);
		JMenuItem compMenuItem = new JMenuItem("Compile");
		buildMenu.add(compMenuItem);
		JMenuItem cdMenuItem = new JMenuItem("Compile & Diassemble");
		cdMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B,
				ActionEvent.CTRL_MASK));
		buildMenu.add(cdMenuItem);
		JMenu aboutMenu = new JMenu("About");
		menuBar.add(aboutMenu);
		JMenuItem infoMenuItem = new JMenuItem("Info");
		aboutMenu.add(infoMenuItem);
		//action listeners
		loadMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				log.info("load invoked");
				JFileChooser chooser = new JFileChooser();
				chooser.showOpenDialog(frame);
				JAP.this.srcFile = chooser.getSelectedFile();
				JAP.this.assDst.setText(""); //clear text
				JAP.this.status.setText("loading file: " + JAP.this.srcFile
						+ "\n");
				FileInputStream fis = null;
				try {
					fis = new FileInputStream(JAP.this.srcFile);
				} catch (FileNotFoundException fnfe) {
					JAP.this.status.append("ERROR: file not found!" + "\n");
				}
				byte[] input = new byte[(int) JAP.this.srcFile.length()];
				try {
					int x = fis.read(input);
					log.info("read " + x + " bytes");
				} catch (IOException ioe) {
					JAP.this.status.append("ERROR: while reading file!" + "\n");
				}
				JAP.this.javaSrc.setText(new String(input));
			}
		});
		saveMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				saveFile();
			}
		});
		newMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JAP.this.status.append("clearing input buffer\n");
				JAP.this.srcFile = null;
				JAP.this.javaSrc.setText("");
				JAP.this.assDst.setText("");
			}
		});
		saveAsMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JAP.this.assDst.setText("");
				writeNewFile();
			}
		});
		compMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				compile();
			}
		});
		infoMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JAP.this.status.append("Java Assembler Playground\n");
				JAP.this.status.append(JAP.VERSION);
				JAP.this.status.append("(c) by Mario Jeckle 2004\n");
				JAP.this.status
						.append("http://www.jeckle.de/freeStuff/javaAssemblerPlayground/\n");
			}
		});
		cdMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				log.info("compile & diassamle invoked");
				saveFile();
				JAP.this.assDst.setText("");
				compile();
				log.info("compile process finished");
				JAP.this.status.append("diassembling in progress ...\n");
				File workingDir = new File(JAP.this.srcFile.getPath()
						.substring(
								0,
								JAP.this.srcFile.getPath().lastIndexOf(
										JAP.this.srcFile.getName())));
				Command command = getCommand((JAP.this.srcFile.getName())
						.substring(0, JAP.this.srcFile.getName().lastIndexOf(
								".java")), workingDir);
				Runtime rt = Runtime.getRuntime();
				Process p = null;
				log.info("diassembling file");
				for (int i = 0; i < command.command.length; i++)
					log.info("command:" + command.command[i]);
				try {
					p = rt.exec(command.command, null, workingDir);
				} catch (IOException ioe) {
					JAP.this.status
							.append("ERROR: cannot invoke javap command\n");
				}
				try {
					log.info("waiting");
					p.waitFor();
					log.info("waiting done");
				} catch (InterruptedException ie) {
					JAP.this.status.append("ERROR: processing aborted!\n");
				}
				log.info("return=" + p.exitValue());
				BufferedInputStream compErr = new BufferedInputStream(p
						.getErrorStream());
				BufferedInputStream compOut = new BufferedInputStream(p
						.getInputStream());
				byte err[] = null;
				byte out[] = null;
				try {
					out = new byte[compOut.available()];
					err = new byte[compErr.available()];
					compOut.read(out);
					compErr.read(err);
				} catch (IOException ioe) {
					log.info("error reading err stream");
				}
				JAP.this.status.append(new String(err) + "\n");
				JAP.this.assDst.append(new String(out) + "\n");
				//add diassembled result is stored in an external file
				if (command.tmpResult != null) {
					FileInputStream fis = null;
					try {
						fis = new FileInputStream(command.tmpResult);
					} catch (FileNotFoundException e1) {
						JAP.this.status.append("cannot read temporary file\n");
					}
					out = new byte[(int) command.tmpResult.length()];
					try {
						fis.read(out, 0, (int) command.tmpResult.length());
					} catch (IOException e2) {
						JAP.this.status
								.append("error while reading temporary file\n");
					}
					JAP.this.assDst.append(new String(out) + "\n");
					command.tmpResult.delete();
					command.tmpScript.delete();
				}
				//scroll back to top
				JAP.this.assDst.setCaretPosition(0);
				JAP.this.status.append("... done\n");
			}
		});
	}
	private void createJavaSrcPane() {
		this.javaSrc = createNamedScrollablePane("Java Source", true);
	}
	private void createJavaAssPane() {
		this.assDst = createNamedScrollablePane("Java Assembler Source", false);
	}
	private void createStatusPane() {
		this.status = createNamedScrollablePane("status", false);
		this.status.setFont(new Font(this.status.getFont().getName(),
				Font.PLAIN, 10));
	}
	private JTextArea createNamedScrollablePane(String name, boolean enableState) {
		JPanel thePanel = new JPanel();
		thePanel.setLayout(new BorderLayout());
		JTextArea theArea = new JTextArea();
		JScrollPane scrollPane = new JScrollPane(theArea);
		scrollPane.setPreferredSize(new Dimension(400, 100));
		thePanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
				.createTitledBorder(name), BorderFactory.createEmptyBorder(5,
				5, 5, 5)));
		thePanel.add(scrollPane);
		frame.add(thePanel);
		theArea.setEnabled(enableState);
		return theArea;
	}
	private void writeFile() {
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(this.srcFile);
		} catch (FileNotFoundException fnfe) {
			this.status.append("ERROR: cannot write file!\n");
		}
		byte[] output = new byte[this.javaSrc.getText().length()];
		output = this.javaSrc.getText().getBytes();
		try {
			fos.write(output);
		} catch (IOException ioe) {
			this.status.append("ERROR: while writing file!\n");
		}
	}
	void compile() {
		if (this.srcFile != null) {
			this.status.append("compiling ...\n");
			File oldClassFile = new File((this.srcFile.getAbsolutePath()
					.substring(0, this.srcFile.getAbsolutePath().lastIndexOf(
							".java")))
					+ ".class");
			log
					.info("deleting old class file"
							+ oldClassFile.getAbsolutePath());
			oldClassFile.delete();
			log.info("compiling");
			String cmd[] = new String[2];
			cmd[0] = "javac";
			cmd[1] = new String(this.srcFile.getAbsolutePath());
			Runtime rt = Runtime.getRuntime();
			Process p = null;
			try {
				p = rt.exec(cmd);
			} catch (IOException ioe) {
				this.status.append("ERROR: cannot invoke compiler command\n");
			}
			try {
				p.waitFor();
			} catch (InterruptedException ie) {
				this.status.append("ERROR: compilation aborted!\n");
			}
			log.info("return=" + p.exitValue());
			BufferedInputStream compErr = new BufferedInputStream(p
					.getErrorStream());
			BufferedInputStream compOut = new BufferedInputStream(p
					.getInputStream());
			byte err[] = null;
			byte out[] = null;
			try {
				out = new byte[compOut.available()];
				err = new byte[compErr.available()];
				compOut.read(out);
				compErr.read(err);
			} catch (IOException ioe) {
				log.info("error reading err stream");
			}
			this.status.append(new String(err) + "\n");
			this.status.append(new String(out) + "\n");
			this.status.append("... done!\n");
		} else {
			this.status.append("no input cannot compile\n");
		}
	}
	void writeNewFile() {
		JFileChooser chooser = new JFileChooser();
		chooser.showSaveDialog(frame);
		this.srcFile = chooser.getSelectedFile();
		this.status.append("saving file as " + this.srcFile + "\n");
		writeFile();
	}
	void saveFile() {
		this.status.append("saving file\n");
		this.assDst.setText("");
		if (this.srcFile != null)
			writeFile();
		else
			writeNewFile();
	}
	Command getCommand(String fileName, File workingDir) {
		Command command = new Command();
		String osName = System.getProperty("os.name");
		log.info("OSName=" + osName);
		command.command = null;
		if (osName.compareTo("Linux") == 0) {
			try {
				command.tmpScript = File.createTempFile("JAP-", ".cmd",
						workingDir);
				command.tmpResult = File.createTempFile("JAP-", ".asm",
						workingDir);
			} catch (IOException e) {
				e.printStackTrace();
			}
			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(command.tmpScript);
			} catch (FileNotFoundException e1) {
				this.status.append("cannot create temporary file\n");
			}
			PrintWriter pw = new PrintWriter(fos);
			pw.print("javap -c " + fileName + "> "
					+ command.tmpResult.getName());
			pw.close();
			try {
				fos.close();
			} catch (IOException e2) {
				this.status.append("cannot close temporary file\n");
			}
			command.command = new String[3];
			command.command[0] = "sh";
			command.command[1] = command.tmpScript.getName();
			command.command[2] = fileName;
		} else if (osName.contains("Windows")) {
			try {
				command.tmpScript = File.createTempFile("JAP-", ".cmd",
						workingDir);
				command.tmpResult = File.createTempFile("JAP-", ".asm",
						workingDir);
			} catch (IOException e) {
				e.printStackTrace();
			}
			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(command.tmpScript);
			} catch (FileNotFoundException e1) {
				this.status.append("cannot create temporary file\n");
			}
			PrintWriter pw = new PrintWriter(fos);
			pw.print("javap -c " + fileName + "> "
					+ command.tmpResult.getName());
			pw.close();
			try {
				fos.close();
			} catch (IOException e2) {
				this.status.append("cannot close temporary file\n");
			}
			command.command = new String[4];
			command.command[0] = "cmd";
			command.command[1] = "/D";
			command.command[2] = "/C";
			command.command[3] = workingDir + "\\"
					+ command.tmpScript.getName();
		} else {
			this.status
					.append("unknown operating system JAP will run in compatibility mode\n");
			command.command = new String[3];
			command.command[0] = "javac";
			command.command[1] = "-c";
			command.command[2] = fileName;
		}
		return command;
	}
	class Command {
		String[] command = null;
		File tmpScript = null;
		File tmpResult = null;
	}
}