package sim;

import java.awt.*;

//import javax.swing.JScrollPane;

import gjt.BulletinLayout;

import sim.util.BufferedContainer;

import sim.lib.wires.Wire;
import sim.lib.wires.Junction;
import sim.lib.wires.SplitterModule;

public class Grid extends BufferedContainer {

	public static final int SIZE = 16;	// width of grid in pixels
	public static final Dimension INITIAL_SIZE = new Dimension(50, 50);
	private int zoom;
	private Dimension gridSize;
	private GridBackground back;
	private ScrollCanvas scroller;
	private int wires;
	private int components;
	private int junctions;
	private int splitters;
	private boolean isMod;

	public Grid(ScrollCanvas sp, String fileName) {
		super();

		this.zoom = 100;
		this.gridSize = new Dimension(Grid.INITIAL_SIZE);

		this.setLayout(new BulletinLayout());
		this.setSize(this.getCurrentSize());

		CentralPanel.ACTIVE_GRID = this;

		this.back = new GridBackground();

		this.scroller = sp;

		this.wires = 0;
		this.components = 0;
		this.junctions = 0;
		this.splitters = 0;

		this.setName(fileName);
		this.setUnMod();
	}

	public Grid() {
		super();

		this.zoom = 100;
		this.gridSize = new Dimension(Grid.INITIAL_SIZE);

		this.setLayout(new BulletinLayout());
		this.setSize(this.getCurrentSize());

		this.back = new GridBackground();

		this.wires = 0;
		this.components = 0;
		this.junctions = 0;
		this.splitters = 0;
		this.setUnMod();
	}

	public ScrollPane getScroller() {
		return this.scroller;
	}

	public GridBackground getGridBackground() {
		return this.back;
	}

	public void addModule(Wrapper comp) {
		this.addModule(comp, true);
	}

	public void addModuleAndPaint(Wrapper comp, boolean update) {
		this.setUnMod();
		if (comp instanceof Junction) {
			super.add(comp, 0);
			this.junctions++;
		} else if (comp instanceof Wire) {
			super.add(comp, this.junctions);
			this.wires++;
		} else if (comp instanceof SplitterModule) {
			super.add(comp, this.wires + this.junctions);
			this.splitters++;
		} else {
			super.add(comp, this.wires + this.junctions + this.splitters);
			this.components++;
		}

		this.paintComponent(comp, update);
	}

	public void addModule(Wrapper comp, boolean paint) {
		this.setMod();
		if (comp instanceof Junction) {
			super.add(comp, 0);
			this.junctions++;
		} else if (comp instanceof Wire) {
			super.add(comp, this.junctions);
			this.wires++;
		} else if (comp instanceof SplitterModule) {
			super.add(comp, this.wires + this.junctions);
			this.splitters++;
		} else {
			super.add(comp, this.wires + this.junctions + this.splitters);
			this.components++;
		}

		if (paint) {
			this.paintComponent(comp);
		}
	}

	public void addNonModule(Wrapper comp, boolean update) {
		super.add(comp, this.wires + this.components + this.junctions + this.splitters);

		if (update) {
			this.paintComponent(comp);
		}
	}

	public void addNonModule(Wrapper comp) {
		this.addNonModule(comp, true);
	}

	public int getNumberOfWires() {
		return this.wires;
	}

	public int getNumberOfComponents() {
		return this.components;
	}

	public int getNumberOfJunctions() {
		return this.junctions;
	}

	public int getNumberOfSplitters() {
		return this.splitters;
	}

	public void removeModuleAndPaint(Wrapper comp, boolean update) {
		if (comp instanceof Junction) {
			this.junctions--;
		} else if (comp instanceof Wire) {
			this.wires--;
		} else if (comp instanceof SplitterModule) {
			this.splitters--;
		} else {
			this.components--;
		}

		super.remove(comp);

		this.eraseComponent(comp, update);
	}

	public void removeModule(Wrapper comp, boolean paint) {
		if (comp instanceof Junction) {
			this.junctions--;
		} else if (comp instanceof Wire) {
			this.wires--;
		} else if (comp instanceof SplitterModule) {
			this.splitters--;
		} else {
			this.components--;
		}

		super.remove(comp);

		if (paint) {
			this.eraseComponent(comp);
		}
	}

	public void removeModule(Wrapper comp) {
		this.removeModule(comp, true);
	}

	public void removeNonModule(Wrapper comp) {
		this.eraseComponent(comp);
		super.remove(comp);
	}

	public Dimension getCurrentSize() {
		int currentGap = this.getCurrentGridGap();

		return (new Dimension(this.gridSize.width * currentGap, this.gridSize.height * currentGap));
	}

	public Dimension getGridSize() {
		return this.gridSize;
	}

	public int getCurrentGridGap() {
		return (Grid.SIZE * this.zoom / 100);
	}

	public void moveToFree(Wrapper comp, Point gridPosition) {
		if ((comp.getGridLocation().x != gridPosition.x) || (comp.getGridLocation().y != gridPosition.y)) {
			Rectangle oldBounds = comp.getBounds();

			super.eraseComponent(comp, false);
			comp.setGridLocation(gridPosition);
			super.paintComponent(comp, false);

			this.blitWorkplaceToScreen(oldBounds.union(comp.getBounds()));
		}
	}

	public void addNotify() {
		super.addNotify();

		this.addNonModule(this.back);
	}

	public Point convertToGrid(int x, int y) {
		int gridGap = this.getCurrentGridGap();

		return (new Point(x / gridGap, y / gridGap));
	}

	public Point convertToGrid(Point position) {
		return this.convertToGrid(position.x, position.y);
	}

	public Point convertToNormal(int gridX, int gridY) {
		int gridGap = this.getCurrentGridGap();

		return (new Point(gridX * gridGap, gridY * gridGap));
	}

	public Point convertToNormal(Point gridPosition) {
		return this.convertToNormal(gridPosition.x, gridPosition.y);
	}

	public void blitWorkplaceToScreen(Rectangle clip) {
		super.buffers.blitWorkplaceToScreen(clip);
	}

	public void paintAll() {
		this.eraseComponent(this.back);
	}

	public int getZoom() {
		return this.zoom;
	}

	public void changeZoom(int scale) throws SimException {
		if (scale != this.zoom) {
			int oldZoom = this.zoom;

			try {
				this.zoom = scale;
				this.setSize(this.getCurrentSize());
				this.updateBuffers();

				int end = this.junctions + this.wires + this.components + this.splitters;
				Wrapper w;

				for (int loop = 0; loop < end; loop++) {
					w = (Wrapper) this.getComponent(loop);
					w.setGridLocation(w.getGridLocation());
					w.setGridSize(w.getGridSize());
					w.scale();
				}

				this.back.setGridSize(this.getGridSize());
				this.back.scale();

				this.paintComponent(this.back, false);
				this.eraseComponent(this.back, false);

				this.scroller.validate();

				ModuleButton active = WestPanel.MODE_CONTROL.getCurrnetActive();

				if (active != WestPanel.MODE_CONTROL.getEditButton()) {
					WestPanel.MODE_CONTROL.changeStructure(active);
				}

				this.scroller.realUpdate();

				System.gc();
			} catch (Throwable err) {
				this.zoom = oldZoom;
				this.setSize(this.getCurrentSize());

				throw (new SimException("fail to allocate memory for screen buffering"));
			}
		}
	}

	public void changeGridVisibility(boolean hide) {
		this.back.setHide(hide);

		this.paintComponent(this.back);
		this.eraseComponent(this.back);
	}

	public void changeGridSize(int newWidth, int newHeight) throws SimException {
		int oldWidth = this.gridSize.width;
		int oldHeight = this.gridSize.height;

		this.gridSize.width = newWidth;
		this.gridSize.height = newHeight;

		boolean proceed = true;

		int end = this.junctions + this.wires + this.components + this.splitters;

		for (int index = this.junctions; (index < end) && proceed; index++) {
			proceed = ((Wrapper) this.getComponent(index)).canDrop();
		}

		if (proceed) {
			try {
				this.back.setGridSize(newWidth, newHeight);

				this.setSize(this.getCurrentSize());
				this.updateBuffers();

				this.paintComponent(this.back);
				this.eraseComponent(this.back);

				this.scroller.validate();
				this.scroller.realUpdate();
			} catch (Throwable err) {
				this.gridSize.width = oldWidth;
				this.gridSize.height = oldHeight;
				this.back.setGridSize(oldWidth, oldHeight);
				this.setSize(this.getCurrentSize());

				throw (new SimException("fail to allocate memory for screen buffering"));
			}
		} else {
			this.gridSize.width = oldWidth;
			this.gridSize.height = oldHeight;

			throw (new SimException("some components will be out of specified bounds"));
		}
	}

	public boolean isModified() {
		return isMod;
	}

	public void setMod() {
		isMod = true;
	}

	public void setUnMod() {
		isMod = false;
	}
	/* ==================================================================
	 Storage Part
	 ================================================================= */

	public String getParameters() {
		String result;

		// Size
		result = Integer.toString(this.gridSize.width) + Wrapper.SEPARATOR + Integer.toString(this.gridSize.height) + Wrapper.SEPARATOR;

		// Zoom
		result = result + Integer.toString(this.zoom) + Wrapper.SEPARATOR;

		// Visisbility
		result = result + this.back.isHidden() + Wrapper.SEPARATOR;

		// Simulation
		result = result + SimulationProperties.STEP_SIZE + Wrapper.SEPARATOR + SimulationProperties.SPEED + Wrapper.SEPARATOR;
		result = result + SimulationProperties.SHOW_FLOW + Wrapper.SEPARATOR + SimulationProperties.getColorName(SimulationProperties.ONE_COLOR) + Wrapper.SEPARATOR + SimulationProperties.getColorName(SimulationProperties.ZERO_COLOR) + Wrapper.SEPARATOR;

		return result;
	}

	public int getNumberOfParameters() {
		return 9;
	}

	public void loadGrid(String[] parameters) throws SimException {
		if (parameters.length == 9) {
			try {
				this.changeGridSize(Integer.valueOf(parameters[0]).intValue(), Integer.valueOf(parameters[1]).intValue());
				NorthPanel.GRID_SHORTCUT.setZoom(parameters[2] + "%");
				NorthPanel.GRID_SHORTCUT.setGridVisibility(!Boolean.valueOf(parameters[3]).booleanValue());

				SimulationProperties.STEP_SIZE = Double.valueOf(parameters[4]).doubleValue();
				SimulationProperties.SPEED = Long.valueOf(parameters[5]).longValue();
				SimulationProperties.SHOW_FLOW = Boolean.valueOf(parameters[6]).booleanValue();
				SimulationProperties.ONE_COLOR = SimulationProperties.getColor(parameters[7]);
				SimulationProperties.ZERO_COLOR = SimulationProperties.getColor(parameters[8]);
			} catch (NumberFormatException e) {
				throw (new SimException("incorrect parameter type"));
			}
		} else {
			throw (new SimException("incorrect number of parameters"));
		}
	}
}
