package sim.lib.memory;

import java.awt.*;
import java.net.*;
import java.io.*;

import sim.*;
import sim.engine.*;
import sim.lib.memory.hex.*;

import sim.lib.wires.Junction;

public abstract class MemoryStructure extends WrapperPainted implements EngineModule, HexEditorParent {
	/* ==================================================================
	 Creation Part
	 ================================================================= */

	public Wrapper createWrapper() {
		return this.getCopy();
	}

	public Wrapper createWrapper(Point gridPosition) {
		Wrapper result = this.getCopy();
		result.setGridLocation(gridPosition);
		return result;
	}

	/* ==================================================================
	 GUI part
	 ================================================================= */
	protected String targetFile = MainWindow.WORKSPACE + File.separatorChar + "Untitled.mem";
	protected String addressToDraw = null;
	protected String dataToDraw = null;
	protected String componentName;

	public MemoryStructure() {
		super();
	}

	protected abstract Wrapper getCopy();

	public void initializeGridSize() {
		this.setGridSize(10, 7);
	}

	public String getComponentName() {
		return this.componentName;
	}

	public void setComponentName(String name) {
		this.componentName = name;
	}

	public String getFile() {
		return this.targetFile;
	}

	public void setFile(String name) {
		this.targetFile = name;
		this.initializeMemory();
	}

	/* ==================================================================
	 Maintanance Part
	 ================================================================= */
	protected Junction notCE = null;
	protected Junction notOE = null;
	protected Junction address = null;
	protected Junction data = null;
	protected int busSize = 8;
	protected int addressSpace = 256;
	protected int requiredAddress = 8;
	protected int wordLenght = 2;
	protected int addressLenght = 2;

	protected void setBusSize(int size) {
		this.busSize = size;
		this.wordLenght = Integer.toHexString((int) Math.pow(2, this.busSize) - 1).length();
	}

	protected void setAddressSpace(int size) {
		this.addressSpace = size;
		this.requiredAddress = (int) Math.ceil(Math.log(this.addressSpace) / Math.log(2));
		this.addressLenght = Integer.toHexString((int) Math.pow(2, this.requiredAddress) - 1).length();
	}

	/* ==================================================================
	 Simulation part
	 ================================================================= */
	// read cycle times
	protected double aa = 12;	// address access
	protected double acs = 12;	// chip select access
	protected double clz = 1;	// chip select to output
	protected double oe = 8;	// output enable to output valid
	protected double olz = 1;	// output enable to output
	protected double chz = 4;	// chip deselection to output
	protected double ohz = 4;	// chip disable to output
	protected double oh = 1;	// output hold from address change
	// simulation variables
	protected double notCE_goesLow;
	protected double notOE_goesLow;
	protected double notCE_goesHigh;
	protected double notOE_goesHigh;
	protected boolean pastNotCE;
	protected boolean pastNotOE;
	protected double startPutting;
	protected double startFloating;
	protected boolean isReading;
	protected double addressChange;
	protected boolean addressHandled;
	protected int oldAddress;
	protected char memoryContent[][] = null;

	public void reset() {
		if (this.editor != null) {
			this.editor.prepareForEditing();
		}

		this.addressToDraw = null;
		this.dataToDraw = null;
		CentralPanel.ACTIVE_GRID.paintComponent(this);
	}

	public Wrapper getParentWrapper() {
		return this;
	}

	public void initializeMemory() {
		int loop, index;

		this.memoryContent = new char[this.addressSpace][this.wordLenght];

		for (loop = 0; loop < this.addressSpace; loop++) {
			for (index = 0; index < this.wordLenght; index++) {
				this.memoryContent[loop][index] = '-';
			}
		}

		this.reloadMemory();

		System.gc();
	}

	protected void undefineOutputs(double when, EnginePeer ep) {
		for (int loop = 0; loop < this.busSize; loop++) {
			ep.clearUncomplitedTransaction(loop, new Signal(false, when, true, ep, loop));
		}

		if (this.editor != null) {
			this.editor.eliminateHighlights();
		}
	}

	protected void floatOutputs(double when, EnginePeer ep) {
		for (int loop = 0; loop < this.busSize; loop++) {
			ep.clearUncomplitedTransaction(loop, new Signal(when, ep, loop));
		}

		if (this.editor != null) {
			this.editor.eliminateHighlights();
		}
	}

	protected void setSignalsAt(int address, double when, EnginePeer ep) {
		int loop;

		try {
			if (address != -1) {
				if (address >= this.addressSpace) {
					address = this.addressSpace - 1;
				}

				if (this.editor != null) {
					this.editor.readFrom(address);
				}

				String value = (new Character(this.memoryContent[address][this.wordLenght - 1])).toString();

				for (loop = 1; loop < this.wordLenght; loop++) {
					value = value + this.memoryContent[address][this.wordLenght - 1 - loop];
				}

				this.putData(Integer.valueOf(value, 16).intValue(), when, ep);
			}
		} catch (NumberFormatException nfx) {
			this.undefineOutputs(when, ep);
		}
	}

	protected void putData(int data, double when, EnginePeer ep) {
		int loop;

		if (data >= 0) {
			String value = Integer.toBinaryString(data);

			for (loop = value.length(); loop < this.busSize; loop++) {
				value = "0" + value;
			}

			for (loop = 0; loop < this.busSize; loop++) {
				ep.clearUncomplitedTransaction(loop, new Signal(value.charAt(this.busSize - 1 - loop) == '1', when, false, ep, loop));
			}
		} else {
			this.undefineOutputs(when, ep);
		}
	}

	/* ==================================================================
	 Popup Part
	 ================================================================= */
	protected HexEditor editor;

	public boolean hasProperties() {
		return false;
	}

	public Component getPropertyWindow() {
		return null;
	}

	public void respondToChanges(Component property) {
	}

	public int getNumberOfMenuItems() {
		return 1;
	}

	public String getMenuItemName(int index) {
		return "Show Content...";
	}

	public void respondToMenuItem(String itemName) {
		if (this.editor == null) {
			this.editor = new HexEditor(this, this.memoryContent, this.targetFile, this.busSize, this.addressSpace, this.getFont(), MainWindow.MENU.STOP.isEnabled());
			this.editor.setVisible(true);
		} else {
			this.editor.requestFocus();
		}
	}

	public void restoreOriginalProperties() {
	}

	public void respondToClosing() {
		this.editor = null;
	}

	public void reloadMemory() {
		int loop;

		Reader inStream;

		if (MainWindow.MASTER == null) {
			try {
				inStream = new BufferedReader(new FileReader(this.targetFile));

				for (loop = 0; loop < this.addressSpace; loop++) {
					inStream.read(this.memoryContent[loop], 0, this.wordLenght);
				}

				inStream.close();
			} catch (IOException e) {
				// do nothing
			}
		} else {
			// handle internet dowloads
			try {
				inStream = new BufferedReader(new InputStreamReader((new URL(MainWindow.MASTER.getCodeBase().toString() + this.targetFile)).openStream()));

				for (loop = 0; loop < this.addressSpace; loop++) {
					inStream.read(this.memoryContent[loop], 0, this.wordLenght);
				}

				inStream.close();
			} catch (MalformedURLException mue) {
				// do nothing
			} catch (IOException e) {
				// do nothing
			}
		}
	}

	public char[][] clearContent() {
		int loop, index;

		this.memoryContent = new char[this.addressSpace][this.wordLenght];

		for (loop = 0; loop < this.addressSpace; loop++) {
			for (index = 0; index < this.wordLenght; index++) {
				this.memoryContent[loop][index] = '-';
			}
		}
		return this.memoryContent;
	}

	/* ==================================================================
	 Dipose Part
	 ================================================================= */
	public void removeNotify() {
		if (this.editor != null) {
			this.editor.setVisible(false);
			this.editor.dispose();
		}

		super.removeNotify();

		System.gc();
	}
}
