package sim;

import java.io.*;
import java.lang.reflect.*;
import java.awt.*;
import java.net.URL;

import gjt.Util;

public class GuiFileLink {

	private int numOfComponents = 0;

	public static Image getImage(String path) {
		Image result = null;
		try {
			URL url = Thread.currentThread().getContextClassLoader().getResource(path);
			if (MainWindow.MASTER == null) {
				Toolkit access = MainWindow.MAIN_WINDOW.getToolkit();
				result = access.getImage(url);
				Util.waitForImage(MainWindow.MAIN_WINDOW, result);
			} else {
				URL cb = MainWindow.MASTER.getCodeBase();
				result = MainWindow.MASTER.getImage(cb, url.toString());
				Util.waitForImage(MainWindow.MAIN_WINDOW, result);
			}
		} catch (Exception e) {
			System.err.println("Could not access resource " + path);
		}
		return result;
	}
	private static char BLANK = '#';
	private static String RELATIVE_WORKSPACE = "<WORKSPACE>";
	private static String LIBRARY = "RETRO_Library";

	/* ==================================================================
	 Load Provided to SaveLoadShortcut
	 ================================================================= */
	public void load(Reader in, Grid g) throws SimException {
		String[] basics, specifics;
		String className, componentName, readIn;
		Wrapper created;

		readIn = this.extractParameter(in);

		// Check Version
		if (readIn.equals(MainWindow.VERSION)) {
			// Read in Grid information
			this.readBlank(in);
			basics = this.extractParameters(g.getNumberOfParameters(), in);
			g.loadGrid(basics);

			// Read number of components
			this.readBlank(in);
			int size = Integer.valueOf(this.extractParameter(in)).intValue();

			for (int index = 0; index < size; index++) {
				this.readBlank(in);

				className = this.extractParameter(in);
				componentName = this.extractParameter(in);
				created = this.getWrapper(className);

				basics = this.extractParameters(created.getNumberOfBasicParameters(), in);
				specifics = this.extractParameters(created.getNumberOfSpecificParameters(), in);
				created.initialiseWrapper(basics, specifics);
				g.addModule(created);

				if (created.canDrop()) {
					this.numOfComponents++;
					created.droped();
					created.setName(componentName);
				} else {
					String msg = "(" + this.numOfComponents
							+ ")Illegal component position:\n\n"
							+ "Class name: " + className + "\n"
							+ "Compnent name: " + componentName + "\n"
							+ "Parameters: ";
					for (int i = 0; i < basics.length; i++) {
						msg += basics[i] + ", ";
					}
					throw (new SimException(msg));
				}
			}
		} else {
			throw (new SimException("not valid file"));
		}
	}

	public void loadMod(Reader in, Grid g) throws SimException {
		String[] basics, specifics;
		String className, componentName, readIn;
		Wrapper created;

		readIn = this.extractParameter(in);

		// Check Version
		if (readIn.equals(MainWindow.VERSION)) {
			// Read in Grid information
			this.readBlank(in);
			basics = this.extractParameters(g.getNumberOfParameters(), in);
			g.loadGrid(basics);

			// Read number of components
			this.readBlank(in);
			int size = Integer.valueOf(this.extractParameter(in)).intValue();

			for (int index = 0; index < size; index++) {
				this.readBlank(in);

				className = this.extractParameter(in);
				componentName = this.extractParameter(in);
				created = this.getWrapper(className);

				basics = this.extractParameters(created.getNumberOfBasicParameters(), in);
				specifics = this.extractParameters(created.getNumberOfSpecificParameters(), in);
				created.initialiseWrapper(basics, specifics);
				g.addModule(created, false);

				if (created.canDrop()) {
					created.droped();
					created.setName(componentName);
				} else {
					throw (new SimException("illegal component position"));
				}
			}
		} else {
			throw (new SimException("not valid file"));
		}
	}


	/* ==================================================================
	 Load Library Provided to WestPanel
	 ================================================================= */
	public ModulePanel loadLibrary(Reader in) throws SimException {
		ModulePanel result = new ModulePanel();
		String className, readIn;
		CreationModule created;
		readIn = this.extractParameter(in);

		// Check whether this is library file
		if (readIn.equals(GuiFileLink.LIBRARY)) {
			// Read name of the library
			this.readBlank(in);
			result.setName(this.extractParameter(in));

			// Read number of components
			this.readBlank(in);
			int size = Integer.valueOf(this.extractParameter(in)).intValue();

			for (int index = 0; index < size; index++) {
				this.readBlank(in);
				className = this.extractParameter(in);
				created = this.getCreationModule(className);
				result.add(new ModuleButton(created));
			}
		} else {
			throw (new SimException("not valid file"));
		}

		return result;
	}

	public void readBlank(Reader in) throws SimException {
		try {
			byte readIn[] = new byte[1];
			String converted;

			readIn[0] = (byte) in.read();
			if (readIn[0] == -1) {
				throw (new SimException("incorrect file format"));
			}
			converted = new String(readIn);

			if (!converted.equals((new Character(GuiFileLink.BLANK)).toString())) {
				throw (new SimException("incorrect component arrangement"));
			}
		} catch (IOException e) {
			throw (new SimException("IO exception has occured"));
		}
	}

	public String[] extractParameters(int size, Reader in) throws SimException {
		String[] result = new String[size];

		for (int index = 0; index < size; index++) {
			result[index] = this.extractParameter(in);
		}

		return result;
	}

	public String extractParameter(Reader in) throws SimException {
		String result = null;
		String wSeparator = (new Character(Wrapper.SEPARATOR)).toString();

		try {
			boolean finished = false;
			byte readIn[] = new byte[1];
			String converted;

			readIn[0] = (byte) in.read();
			if (readIn[0] == -1) {
				throw (new SimException("incorrect file format"));
			}
			converted = new String(readIn);

			if (!converted.equals(wSeparator)) {
				result = converted;
			} else {
				finished = true;
			}

			while (!finished) {
				readIn[0] = (byte) in.read();
				if (readIn[0] == -1) {
					throw (new SimException("incorrect file format"));
				}
				converted = new String(readIn);

				if (!converted.equals(wSeparator)) {
					result = result + converted;
				} else {
					finished = true;
				}
			}
		} catch (IOException e) {
			throw (new SimException("IO exception has occured"));
		}

		//restore a relative path to absolute path
		if (result != null && result.contains(GuiFileLink.RELATIVE_WORKSPACE)) {
			result = result.replace('\\', File.separatorChar);
			result = result.replace('/', File.separatorChar);
			result = result.replace(GuiFileLink.RELATIVE_WORKSPACE, MainWindow.WORKSPACE);
		}

		return result;
	}

	public Wrapper getWrapper(String name) throws SimException {
		Object result = null;

		try {
			Class comp = Class.forName(name);
			Constructor con = comp.getConstructor((Class[]) null);
			result = con.newInstance((Object[]) null);
		} catch (ClassNotFoundException e) {
			// from Class.forName(name)
			throw (new SimException("component does not exist: "
					+ e.toString()));
		} catch (NoSuchMethodException e) {
			// from comp.getConstructor(parameters)
			throw (new SimException("component does not have required constructor"));
		} catch (SecurityException e) {
			// from comp.getConstructor(parameters)
			throw (new SimException("access to constructor was denied"));
		} catch (InstantiationException e) {
			// from con.newInstance(arguments)
			throw (new SimException("component is abstract"));
		} catch (IllegalAccessException e) {
			// from con.newInstance(arguments)
			throw (new SimException("illegal constructor access"));
		} catch (IllegalArgumentException e) {
			// from con.newInstance(arguments)
			throw (new SimException("illegal arguments passed to constructor"));
		} catch (InvocationTargetException e) {
			// from con.newInstance(arguments)
			throw (new SimException("constructor generated exception"));
		}

		if (!(result instanceof Wrapper)) {
			throw (new SimException("incorrect component type"));
		}

		return (Wrapper) result;
	}

	private CreationModule getCreationModule(String name) throws SimException {
		Object result = null;
		try {
			Class comp = Class.forName(name);
			Constructor con = comp.getConstructor((Class[]) null);
			result = con.newInstance((Object[]) null);
		} catch (ClassNotFoundException e) {
			// from Class.forName(name)
			throw (new SimException("component does not exist: "
					+ e.toString()));
		} catch (NoSuchMethodException e) {
			// from comp.getConstructor(parameters)
			throw (new SimException("component does not have required constructor"));
		} catch (SecurityException e) {
			// from comp.getConstructor(parameters)
			throw (new SimException("access to constructor was denied"));
		} catch (InstantiationException e) {
			// from con.newInstance(arguments)
			throw (new SimException("component is abstract"));
		} catch (IllegalAccessException e) {
			// from con.newInstance(arguments)
			throw (new SimException("illegal constructor access"));
		} catch (IllegalArgumentException e) {
			// from con.newInstance(arguments)
			throw (new SimException("illegal arguments passed to constructor"));
		} catch (InvocationTargetException e) {
			// from con.newInstance(arguments)
			throw (new SimException("constructor generated exception"));
		}

		if (!(result instanceof CreationModule)) {
			throw (new SimException("incorrect component type"));
		}

		return (CreationModule) result;
	}

	/* ==================================================================
	 Save Provided to SaveLoadShortcut
	 ================================================================= */
	public void save(Writer out, Grid g) throws SimException {
		try {
			out.write(this.getData(g));
		} catch (IOException e) {
			throw (new SimException("IO exception has occured"));
		}
	}

	public String getData(Grid target) {
		int start = target.getNumberOfJunctions();
		int end = start + target.getNumberOfComponents() + target.getNumberOfWires() + target.getNumberOfSplitters();
		Wrapper toSave;

		// Version of the program
		String toStore = MainWindow.VERSION + Wrapper.SEPARATOR + GuiFileLink.BLANK;

		// Grid properties
		toStore = toStore + target.getParameters() + GuiFileLink.BLANK;

		// Number of components
		toStore = toStore + Integer.toString(end - start) + Wrapper.SEPARATOR + GuiFileLink.BLANK;

		// Components
		for (int index = start; index < end; index++) {
			// Type and Name
			toSave = (Wrapper) target.getComponent(index);
			toStore = toStore + toSave.getClass().getName() + Wrapper.SEPARATOR;
			toStore = toStore + toSave.getName() + Wrapper.SEPARATOR;

			// Parameters
			// Replace any absolute path to relative path
			String current = toSave.getParameters();
			if (current.contains(MainWindow.WORKSPACE)) {
				current = current.replace(MainWindow.WORKSPACE, GuiFileLink.RELATIVE_WORKSPACE);
			}

			toStore = toStore + current;
			toStore = toStore + GuiFileLink.BLANK;
		}

		return toStore;
	}

	//Additions [TO BE REMOVED]
	public void loadModule(Reader in, Grid g) throws SimException {
		String[] basics, specifics;
		String className, componentName, readIn;
		Wrapper created;

		readIn = SaveLoadShortcut.GUI_FILE_LINK.extractParameter(in);

		// Check Version
		if (readIn.equals(MainWindow.VERSION)) {
			// Read in Grid information
			this.readBlank(in);
			basics = this.extractParameters(g.getNumberOfParameters(), in);
			//g.loadGrid(basics);

			// Read number of components
			this.readBlank(in);
			int size = Integer.valueOf(this.extractParameter(in)).intValue();

			for (int index = 0; index < size; index++) {
				this.readBlank(in);

				className = this.extractParameter(in);
				componentName = this.extractParameter(in);
				created = this.getWrapper(className);

				basics = this.extractParameters(created.getNumberOfBasicParameters(), in);
				specifics = this.extractParameters(created.getNumberOfSpecificParameters(), in);
				created.initialiseWrapper(basics, specifics);
				g.addModule(created, false); //add component but don't draw it

				if (created.canDrop()) {
					created.droped();
					created.setName(componentName);
				} else {
					throw (new SimException("illegal component position"));
				}
			}
		} else {
			throw (new SimException("not valid file"));
		}
	}
}
