package sim;

import java.awt.*;

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;
	
	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);
	}
	
	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)
	{
		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)
	{
		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"));
		}
	}
	
/* ==================================================================
	Storage Part
	================================================================= */
	public boolean isModified()
	{
		return false;
	}
	
	
	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"));
	}
}