package MapEditor;

import MapEditor.Brushes.*;
import MapEditor.Event.IRepaintListener;
import MapEditor.Maps.*;
import java.awt.*;
import java.awt.event.*;
import javax.vecmath.*;

public class DesignCanvas extends Canvas implements IRepaintListener, IScrollable
{
	// Note: There are two coordinate systems active in a design canvas:
	// Coords:	The position of a point, in level coordinates
	// Pixels:	The position of a point, in pixels, on the canvas (measured from the top-left of the canvas)
	// We're going to use the suffices _Coords and _Pixels on the names of coordinate variables to make things
	// as clear as possible.
	// Note that in the rest of the program, everything is in Coords (unless explicitly stated otherwise).

	//################## CONSTANTS ##################//
	final private static int AXIS_H = 0;
	final private static int AXIS_V = 1;

	//################## PRIVATE VARIABLES ##################//
	// General stuff (which is primarily to do with how we do our rendering)
	private boolean m_bHasFocus = false;				// whether or not the canvas has the focus
	private Dimension m_dimension = null;				// the dimensions of the viewable area (in Pixels)
	private int m_gridSize_Coords = 16;					// the (fine) grid size in Coords
	private int m_visibleHeight;						// the height of the viewable area (in Coords)
	private int m_visibleWidth;							// the width of the viewable area (in Coords)
	private double m_zoomLevel = 1;						// the ratio of Pixels : Coords
	private Map m_map;									// the map we're editing using this canvas
	private Point2d m_topLeft_Coords = new Point2d();	// the top left of the canvas (in Coords)

	// AxisPair stuff
	private AxisPair m_axisPair;						// the (r,d) axis pair for this canvas
	private int m_vAxisSign;							// 1 if the d-axis increases downwards, -1 if not
	private int m_hAxisSign;							// 1 if the r-axis increases to the right, -1 if not

	// Double-buffering stuff
	private Dimension m_backDimension;
	private Graphics m_backGraphics;
	private Image m_backImage;

	// Initialisation - has this canvas already been initialised?
	// Note that when we say initialisation, we are not referring to the work done in the constructor
	// in this instance, we're talking about the initialisation done after things like the canvas's
	// size have been properly determined, namely the code in initialise().
	private boolean m_bInitialised = false;

	// Mouse stuff
	private Point m_mouseAnchor_Pixels;
	private Point m_snappedMouseAnchor_Pixels;
	private Point2d m_mouseAnchor_Coords;
	private Point2d m_snappedMouseAnchor_Coords;

	// Scrollbar stuff
	private Scrollbar m_horiz = null;
	private Scrollbar m_vert = null;

	// Renderer stuff
	IRenderer m_renderer = new IRenderer()
	{
		public Point2d add_pixel_offset(final Point2d p, int dx, int dy)
		{
			Point p_Pixels = coords_to_pixels(p);
			p_Pixels.x += dx;
			p_Pixels.y += dy;
			return pixels_to_coords(p_Pixels);
		}

		public int axis_h()
		{
			return AXIS_H;
		}

		public int axis_v()
		{
			return AXIS_V;
		}

		/**
		Returns the square of the distance (in Pixels) between the mappings of the two
		specified points onto the canvas associated with this renderer. Or, to give an
		operational specification, this method converts both points in Coords into
		points in Pixels using the relevant method from the canvas with which this
		renderer is associated and then calculates the square of the distance between
		the points in Pixels.

		@param p1	The first point
		@param p2	The second point
		@return		The square of the distance between their conversions into Pixels as a double
		*/
		public double distance_squared(final Point2d p1, final Point2d p2)
		{
			Point a = coords_to_pixels(p1);
			Point b = coords_to_pixels(p2);
			double x = a.x - b.x;
			double y = a.y - b.y;
			return x*x + y*y;
		}

		public void draw_line(final Point2d p1, final Point2d p2)
		{
			Point a = coords_to_pixels(p1);
			Point b = coords_to_pixels(p2);

			m_backGraphics.drawLine(a.x, a.y, b.x, b.y);
		}

		public void draw_line(final Point3d p1, final Point3d p2)
		{
			Point a = determine_canvas_location_in_pixels(p1);
			Point b = determine_canvas_location_in_pixels(p2);

			m_backGraphics.drawLine(a.x, a.y, b.x, b.y);
		}

		public void draw_oval(final Point2d p1, final Point2d p2)
		{
			Point a = coords_to_pixels(p1);
			Point b = coords_to_pixels(p2);

			m_backGraphics.drawOval(a.x, a.y, b.x-a.x, b.y-a.y);
		}

		public void draw_polyline(final Point2d[] ps)
		{
			int len = ps.length;
			int[] xPoints = new int[len];
			int[] yPoints = new int[len];
			for(int i=0; i<len; ++i)
			{
				Point p = coords_to_pixels(ps[i]);
				xPoints[i] = p.x;
				yPoints[i] = p.y;
			}
			m_backGraphics.drawPolyline(xPoints, yPoints, len);
		}

		public void draw_rectangle(final Point2d p1, final Point2d p2)
		{
			Point a = coords_to_pixels(p1);
			Point b = coords_to_pixels(p2);

			Util.draw_rectangle(m_backGraphics, a.x, a.y, b.x, b.y);
		}

		public Point2d find_nearest_grid_intersect_in_coords(final Point2d p)
		{
			return DesignCanvas.this.find_nearest_grid_intersect_in_coords(p);
		}

		public AxisPair get_axis_pair()
		{
			return m_axisPair;
		}

		public boolean is_inverted_axis(int axis)
		{
			return DesignCanvas.this.is_inverted_axis(axis);
		}

		public boolean option_set(String option)
		{
			return m_map.option_set(option);
		}

		public void set_colour(final Color c)
		{
			m_backGraphics.setColor(c);
		}

		public void set_cursor(final Cursor c)
		{
			DesignCanvas.this.setCursor(c);
		}

		public void set_stroke(final Stroke stroke)
		{
			Graphics2D g2D = (Graphics2D)m_backGraphics;
			g2D.setStroke(stroke);
		}
	};

	//################## PRIVATE METHODS ##################//
	/**
	Converts a size (e.g. the grid size) in Coords to a size in Pixels.

	@param coordSize	A size in Coords
	@return			The size in Pixels
	*/
	private int coord_size_to_pixel_size(double coordSize)
	{
		// Convert a size (e.g. the grid size) in Coords to a size in Pixels.
		return (int)Math.round(coordSize*m_zoomLevel);
	}

	/**
	Converts a point in Coords to a point in Pixels.

	@param p_Coords	The point in Coords
	@return	A Point containing the conversion of the specified point in Coords into Pixels
	*/
	private Point coords_to_pixels(final Point2d p_Coords)
	{
		double xOffset_Coords = m_hAxisSign*(p_Coords.x-m_topLeft_Coords.x);
		double yOffset_Coords = m_vAxisSign*(p_Coords.y-m_topLeft_Coords.y);
		return new Point(coord_size_to_pixel_size(xOffset_Coords), coord_size_to_pixel_size(yOffset_Coords));
	}

	/**
	TODO: Comment here.
	*/
	private Point determine_canvas_location_in_pixels(final Point3d p_3D)
	{
		Point2d p_Coords = m_axisPair.select_components(p_3D);
		return coords_to_pixels(p_Coords);
	}

	/**
	Draws a grid of the specified colour with the specified size onto the canvas.

	@param c		The colour of the grid
	@param gridSize_Pixels	The size of the grid in Pixels
	*/
	private void draw_grid(final Color c, int gridSize_Pixels)
	{
		Graphics g = m_backGraphics;	// g is shorter to type!

		Point gridOffset = find_nearest_grid_intersect_in_pixels(new Point(gridSize_Pixels,gridSize_Pixels),
									 gridSize_Pixels);

		gridOffset.x -= gridSize_Pixels;
		gridOffset.y -= gridSize_Pixels;

		g.setColor(c);
		for(int y=gridOffset.y; y<m_dimension.height; y += gridSize_Pixels)
		{
			g.drawLine(0,y,m_dimension.width-1,y);
		}
		for(int x=gridOffset.x; x<m_dimension.width; x += gridSize_Pixels)
		{
			g.drawLine(x,0,x,m_dimension.height-1);
		}
	}

	/**
	Draws two grids onto the canvas - a fine grid and a larger grid. The larger grid
	has squares which are fives times larger than those of the fine grid. We need
	both: the larger grid is there to make it easier to get the proportions of things
	right, whereas the fine grid is there so that we can edit things precisely. Of
	course, we can always adjust the grid size interactively, but having both grids
	is still helpful for editing purposes.
	*/
	private void draw_grids()
	{
		int gridSize_Pixels = coord_size_to_pixel_size(m_gridSize_Coords);

		// Draw the fine grid
		draw_grid(m_bHasFocus ? new Color(0,128,0) : new Color(0,0,128), gridSize_Pixels);

		// Draw the larger grid
		draw_grid(new Color(128,128,128), gridSize_Pixels*5);
	}

	private Point2d find_nearest_grid_intersect_in_coords(final Point2d p_Coords)
	{
		return find_nearest_grid_intersect_in_coords(p_Coords, m_gridSize_Coords);
	}

	private static Point2d find_nearest_grid_intersect_in_coords(final Point2d p_Coords, int gridSize_Coords)
	{
		// Note: Coords space is imagined to be such that x always increases to the right and y always
		// increases downwards. This explains the variable naming-scheme.

		// Note: Flooring the result of the division makes this work.
		int gridLineToLeft = ((int)(p_Coords.x/gridSize_Coords))*gridSize_Coords;
		int gridLineAbove = ((int)(p_Coords.y/gridSize_Coords))*gridSize_Coords;
		int vertGridLine = p_Coords.x-gridLineToLeft < gridSize_Coords/2 ? gridLineToLeft : gridLineToLeft+gridSize_Coords;
		int horizGridLine = p_Coords.y-gridLineAbove < gridSize_Coords/2 ? gridLineAbove : gridLineAbove+gridSize_Coords;
		return new Point2d(vertGridLine, horizGridLine);
	}

	private Point find_nearest_grid_intersect_in_pixels(final Point p_Pixels)
	{
		Point2d gi_Coords = find_nearest_grid_intersect_in_coords(pixels_to_coords(p_Pixels),
									  m_gridSize_Coords);
		return coords_to_pixels(gi_Coords);
	}

	private Point find_nearest_grid_intersect_in_pixels(final Point p_Pixels, int gridSize_Pixels)
	{
		Point2d gi_Coords = find_nearest_grid_intersect_in_coords(pixels_to_coords(p_Pixels),
									  (int)Math.round(pixel_size_to_coord_size(gridSize_Pixels)));
		return coords_to_pixels(gi_Coords);
	}

	/**
	Finds the nearest brush to the specified point, assuming there is one near enough. In this
	context, "nearest" means the one with the lowest selection metric, as returned by each brush's
	selection_metric method.

	@param p_Coords			The point mentioned above (generally speaking, where the user clicked on the canvas)
	@return					The nearest brush, if there was one, otherwise null
	@throws java.lang.Error	If p_Coords is null
	*/
	private IBrush find_nearest_nearby_brush_in_coords(final Point2d p_Coords)
	{
// FIXME: We may need to change the metric idea eventually. More thought's needed, at any rate.

		if(p_Coords == null) throw new java.lang.Error();

		// Brushes with metrics above this will definitely not be selected. As things
		// stand, the metric threshold is being calculated by working out how many
		// pixels from the centre we want the user to be able to click whilst still
		// selecting the brush, and squaring it. This relies on knowledge of how the
		// metric is calculated.
		final double METRIC_THRESHOLD = 36.0;

		double lowestMetric = Double.MAX_VALUE;
		IBrush brushWithLowestMetric = null;

		for(IBrush b: m_map.get_brushes())
		{
			double metric = b.selection_metric(p_Coords, m_renderer);
			if(metric <= METRIC_THRESHOLD)
			{
				if(metric < lowestMetric)
				{
					lowestMetric = metric;
					brushWithLowestMetric = b;
				}
			}
		}

		return brushWithLowestMetric;
	}

	/**
	Generate the bounding box for brushes created at the map's brush creation anchor.
	Note that the two corners have two components in common, but the third component
	is offset by one grid square to make manipulating the brush in the other canvases
	easier.

	@return	The bounding box described
	*/
	private AxisAlignedBox generate_brush_creation_bounds()
	{
		Point3d tl_3D = (Point3d)m_map.get_brush_creation_anchor().clone();
		Point3d br_3D = (Point3d)m_map.get_brush_creation_anchor().clone();
		m_axisPair.set_missing_component(br_3D, m_axisPair.get_missing_component(tl_3D)+m_gridSize_Coords);
		return new AxisAlignedBox(tl_3D, br_3D);
	}

	private void initialise()
	{
		m_dimension = getSize();
		update_scrollbars();

		if(m_vAxisSign == -1)
		{
			m_vert.setValue((int)Math.round(m_axisPair.select_components(m_map.dimensions()).y));
			set_vertical_scrollbar_value(m_vert.getValue());
		}
		if(m_hAxisSign == -1)
		{
			m_horiz.setValue((int)Math.round(m_axisPair.select_components(m_map.dimensions()).x));
			set_horizontal_scrollbar_value(m_horiz.getValue());
		}

		m_bInitialised = true;
	}

	/**
	Returns a boolean indicating whether or not the specified axis is inverted. In this instance, we say
	that the horizontal axis is inverted if values on it increase to the left and that the vertical axis
	is inverted if values on it increase upwards.

	@param axis				The axis we want to check for inversion (must be DesignCanvas.AXIS_H or
							DesignCanvas.AXIS_V)
	@return					A boolean indicating whether the specified axis is inverted
	@throws java.lang.Error	If the axis parameter is not one of the two values specified
	*/
	private boolean is_inverted_axis(int axis)
	{
		switch(axis)
		{
			case AXIS_H:
				return m_hAxisSign == -1;
			case AXIS_V:
				return m_vAxisSign == -1;
			default:
				throw new java.lang.Error();
		}
	}

	/**
	Converts a size (e.g. the grid size) in Pixels to a size in Coords.

	@param pixelSize	A size in Pixels
	@return			The size in Coords
	*/
	private double pixel_size_to_coord_size(int pixelSize)
	{
		
		return (double)pixelSize/m_zoomLevel;
	}

	/**
	Converts a point in Pixels to a point in Coords.

	@param p_Pixels	The point in Pixels
	@return	A Point2d containing the conversion of the specified point in Pixels into Coords
	*/
	private Point2d pixels_to_coords(final Point p_Pixels)
	{
		double xOffset_Coords = pixel_size_to_coord_size(p_Pixels.x);
		double yOffset_Coords = pixel_size_to_coord_size(p_Pixels.y);
		return new Point2d(xOffset_Coords/m_hAxisSign+m_topLeft_Coords.x,
				   yOffset_Coords/m_vAxisSign+m_topLeft_Coords.y);
	}

	/**
	Renders the brush creation anchor.
	*/
	private void render_brush_creation_anchor()
	{
		Graphics g = m_backGraphics;	// g is shorter to type!

		Point2d p2D = m_axisPair.select_components(m_map.get_brush_creation_anchor());
		Point p = coords_to_pixels(p2D);

		g.setColor(Color.yellow);
		Util.draw_rectangle(g, p.x-5, p.y-5, p.x+5, p.y+5);
	}

	/**
	Renders the map we're editing using this canvas.
	*/
	private void render_map()
	{
		IBrush selectedBrush = m_map.get_selected_brush();

		// Render all the brushes except for the currently selected brush (if there is one, obviously).
		for(IBrush b: m_map.get_brushes())
		{
			if(b != selectedBrush) b.render(m_renderer);
		}

		// Now render the selected brush. The point of rendering this after the other brushes is that
		// we want the selected brush to be unobscured, since the user is trying to edit it. Note also
		// that rendering the selected brush separately is essential because in the case of a SelectionBrush
		// it may not be part of the map.
		if(selectedBrush != null) selectedBrush.render_selected(m_renderer);

		if(m_map.option_set("Render Brush Creation Anchor")) render_brush_creation_anchor();
	}

	/**
	Updates the brush creation anchor, using the centre of this canvas as the 2D point with which to update.
	*/
	private void update_brush_creation_anchor()
	{
		Point2d p = new Point2d(m_topLeft_Coords.x + m_hAxisSign * m_visibleWidth/2,
					m_topLeft_Coords.y + m_vAxisSign * m_visibleHeight/2);
		if(m_map.option_set("Snap To Grid")) p = find_nearest_grid_intersect_in_coords(p);
		update_brush_creation_anchor(p);
		m_map.repaint();
	}

	/**
	Updates the brush creation anchor using the specified 2D point. This involves setting the relevant components
	of the 3D point to those of the 2D one, whilst leaving the remaining component (the missing component from
	this canvas's axis pair) unchanged.

	@param p	The 2D point with which to update the 3D position of the anchor
	*/
	private void update_brush_creation_anchor(Point2d p)
	{
		Point3d brushCreationAnchor = m_map.get_brush_creation_anchor();
		m_axisPair.set_relevant_components(brushCreationAnchor, p);
		m_map.set_brush_creation_anchor(brushCreationAnchor);
	}

	private void update_scrollbars()
	{
		// Note that the values on the scrollbars are in Coords. Depending on the orientation of the 
		// axes, the values on the scrollbars may be the coordinates of the top-left of the canvas, or
		// a given coordinate component may be derived by subtracting the value on the scrollbar from
		// the appropriate map dimension. See set_horizontal_scrollbar_value and set_vertical_scrollbar_value for the
		// code involved.

		m_visibleHeight = (int)Math.round(pixel_size_to_coord_size(m_dimension.height));
		m_visibleWidth = (int)Math.round(pixel_size_to_coord_size(m_dimension.width));

		// If these are 0, we've called this method accidentally before the canvas has been properly
		// initialised.
		if(m_visibleHeight == 0) throw new java.lang.Error();
		if(m_visibleWidth == 0) throw new java.lang.Error();

		Point2d dimensions = m_axisPair.select_components(m_map.dimensions());
		int maxHeight = (int)Math.round(dimensions.y);
		int maxWidth = (int)Math.round(dimensions.x);
		m_vert.setValues(m_vert.getValue(),m_visibleHeight,0,maxHeight);
		m_horiz.setValues(m_horiz.getValue(),m_visibleWidth,0,maxWidth);
	}

	//################## CONSTRUCTORS ##################//
	public DesignCanvas(Map map, AxisPair axisPair, int hAxisSign, int vAxisSign)
	{
		if(hAxisSign != 1 && hAxisSign != -1) throw new java.lang.Error();
		if(vAxisSign != 1 && vAxisSign != -1) throw new java.lang.Error();

		m_map = map;
		m_axisPair = axisPair;
		m_hAxisSign = hAxisSign;
		m_vAxisSign = vAxisSign;

		setBackground(Color.black);

		addFocusListener(new FocusAdapter()
		{
			public void focusGained(FocusEvent e)
			{
				m_bHasFocus = true;
				repaint();
			}

			public void focusLost(FocusEvent e)
			{
				m_bHasFocus = false;
				repaint();
			}
		});

		addKeyListener(new KeyAdapter()
		{
			public void keyPressed(KeyEvent e)
			{
				switch(e.getKeyCode())
				{
					case KeyEvent.VK_DOWN:
					{
						m_vert.setValue(m_vert.getValue()+m_gridSize_Coords);
						set_vertical_scrollbar_value(m_vert.getValue());
						repaint();
						break;
					}
					case KeyEvent.VK_LEFT:
					{
						m_horiz.setValue(m_horiz.getValue()-m_gridSize_Coords);
						set_horizontal_scrollbar_value(m_horiz.getValue());
						repaint();
						break;
					}
					case KeyEvent.VK_RIGHT:
					{
						m_horiz.setValue(m_horiz.getValue()+m_gridSize_Coords);
						set_horizontal_scrollbar_value(m_horiz.getValue());
						repaint();
						break;
					}
					case KeyEvent.VK_UP:
					{
						m_vert.setValue(m_vert.getValue()-m_gridSize_Coords);
						set_vertical_scrollbar_value(m_vert.getValue());
						repaint();
						break;
					}
					case KeyEvent.VK_ENTER:
					{
						m_map.deghost_selection();
						m_map.repaint();
						break;
					}
					case KeyEvent.VK_DELETE:
					{
						m_map.delete_brush(m_map.get_selected_brush());
						m_map.repaint();
						setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
						break;
					}
				}
			}

			public void keyTyped(KeyEvent e)
			{
				switch(e.getKeyChar())
				{
					case ']':
					{
						if(m_gridSize_Coords >= 2) m_gridSize_Coords/=2;
						repaint();
						break;
					}
					case '[':
					{
						m_gridSize_Coords*=2;
						repaint();
						break;
					}
					case '-':
					{
						if(m_zoomLevel < 0.5) break;

						// We want the reverse of the zooming-out process I
						// explained below. If we are seeing the area from (25,25)
						// to (75,75), the new area we want to see is from (0,0) to
						// (100,100). So we want to shift our view towards the
						// top-left of the screen by 50% of the (zoomed-in) view
						// dimensions.
						m_horiz.setValue(m_horiz.getValue() - m_visibleWidth/2);
						m_vert.setValue(m_vert.getValue() - m_visibleHeight/2);

						// We now change the zoom level and (importantly) update the
						// scrollbars, which sorts out the ranges their values can be
						// within. This essentially clamps the values we just set to
						// ones which are valid given that we've zoomed out, so that
						// when we set the coordinates of the top-left of the canvas
						// from the scrollbars, we get the clamped values as desired.
						m_zoomLevel/=2;
						update_scrollbars();

						set_horizontal_scrollbar_value(m_horiz.getValue());
						set_vertical_scrollbar_value(m_vert.getValue());

						repaint();
						break;
					}
					case '+':
					{
						// We want to zoom in on the centre of the view, not the
						// top-left. This involves shifting our view towards the
						// bottom-right of the screen by 25% of the view dimensions.
						// As an example, if we were seeing the area from (0,0) to
						// (100,100), we now want to see the area from (25,25) to
						// (75,75), not that from (0,0) to (50,50).
						m_horiz.setValue(m_horiz.getValue() + m_visibleWidth/4);
						m_vert.setValue(m_vert.getValue() + m_visibleHeight/4);
						set_horizontal_scrollbar_value(m_horiz.getValue());
						set_vertical_scrollbar_value(m_vert.getValue());

						m_zoomLevel*=2;

						repaint();
						break;
					}
				}
			}
		});

		addMouseListener(new MouseAdapter()
		{
			public void mousePressed(MouseEvent e)
			{
				m_mouseAnchor_Pixels = new Point(e.getX(), e.getY());
				m_mouseAnchor_Coords = pixels_to_coords(m_mouseAnchor_Pixels);

				if(m_map.option_set("Snap To Grid"))
				{
					m_snappedMouseAnchor_Pixels = find_nearest_grid_intersect_in_pixels(m_mouseAnchor_Pixels);
					m_snappedMouseAnchor_Coords = pixels_to_coords(m_snappedMouseAnchor_Pixels);
				}
				else
				{
					m_snappedMouseAnchor_Pixels = m_mouseAnchor_Pixels;
					m_snappedMouseAnchor_Coords = m_mouseAnchor_Coords;
				}

				switch(m_map.get_state())
				{
					case Map.STATE_CREATE:
					{
						// Update the brush creation anchor.
						update_brush_creation_anchor(m_snappedMouseAnchor_Coords);

						// Create a new brush of the current type using the bounding box
						// for brushes created at the map's brush creation anchor.

						IBrush b = null;
						switch(m_map.get_brush_creation_type())
						{
							case Map.BRUSHCREATIONTYPE_BLOCK:
							{
								b = PolygonalBrush.create_axis_aligned_block(generate_brush_creation_bounds());
								break;
							}
							case Map.BRUSHCREATIONTYPE_CYLINDER:
							{
								b = PolygonalBrush.create_cylinder(generate_brush_creation_bounds(), 32, m_axisPair);
								break;
							}
							case Map.BRUSHCREATIONTYPE_CONE:
							{
								b = PolygonalBrush.create_cone(generate_brush_creation_bounds(), 32, m_axisPair);
								break;
							}
							case Map.BRUSHCREATIONTYPE_UV_SPHERE:
							{
								int precision = 16;
								b = PolygonalBrush.create_uv_sphere(generate_brush_creation_bounds(), precision/2 - 1, precision, m_axisPair);
								break;
							}
						}
						m_map.select_brush(b);
						m_map.set_state(Map.STATE_EDITING);

						// Note that the default edit state for brushes under
						// creation should be resize (anchored at the point of
						// creation).
						break;
					}
					case Map.STATE_EDITING:
					{
						IBrush b = m_map.get_selected_brush();

						// Forward it on to the currently selected brush to deal
						// with. If the brush still wants to be selected when
						// b.mousePressed returns (signified by a return value of
						// true), then we break; if not, the brush wants to deselect
						// itself as a result of the mouse click and we hence want
						// to fall through into the STATE_NORMAL case.
						if(b.mousePressed(e, m_renderer, m_mouseAnchor_Coords)) break;
					}
					case Map.STATE_NORMAL:
					{
						// Update the brush creation anchor.
						update_brush_creation_anchor(m_snappedMouseAnchor_Coords);

						IBrush nearest = find_nearest_nearby_brush_in_coords(m_mouseAnchor_Coords);
						if(nearest != null)	// there was a nearby brush
						{
							m_map.select_brush(nearest);
							m_map.set_state(Map.STATE_EDITING);
							nearest.mousePressed(e, m_renderer, m_mouseAnchor_Coords);
						}
						else
						{
							IBrush b = new SelectionBrush(generate_brush_creation_bounds(), m_map.get_brushes());
							m_map.select_brush(b);
							m_map.set_state(Map.STATE_EDITING);
							b.mousePressed(e, m_renderer, m_mouseAnchor_Coords);
						}
						break;
					}
				}

				m_map.repaint();
			}

			public void mouseReleased(MouseEvent e)
			{
				switch(m_map.get_state())
				{
					case Map.STATE_EDITING:
					{
						// Forward it on to the currently selected brush to deal with.
						m_map.get_selected_brush().mouseReleased(m_renderer);
						break;
					}
				}

				m_map.repaint();
			}
		});

		addMouseMotionListener(new MouseMotionAdapter()
		{
			public void mouseDragged(MouseEvent e)
			{
				switch(m_map.get_state())
				{
					case Map.STATE_EDITING:
					{
						// Forward it on to the currently selected brush to deal with.
						Point2d p_Coords = pixels_to_coords(new Point(e.getX(), e.getY()));
						m_map.get_selected_brush().mouseDragged(e, m_renderer, p_Coords);
						break;
					}
				}

				m_map.repaint();
			}

			public void mouseMoved(MouseEvent e)
			{
				if(m_map.get_state() == Map.STATE_EDITING)
				{
					// Forward it on to the currently selected brush to deal with.
					Point2d p_Coords = pixels_to_coords(new Point(e.getX(), e.getY()));
					m_map.get_selected_brush().mouseMoved(m_renderer, p_Coords);
				}
			}
		});

		addMouseWheelListener(new MouseWheelListener()
		{
			public void mouseWheelMoved(MouseWheelEvent e)
			{
				m_vert.setValue(m_vert.getValue()+e.getWheelRotation()*m_gridSize_Coords);
				set_vertical_scrollbar_value(m_vert.getValue());
				repaint();
			}
		});
	}

	//################## PUBLIC METHODS ##################//
	// Methods which override ones in the AWT
	public void paint(Graphics g)
	{
		update(g);
	}

	public void update(Graphics frontGraphics)
	{
		if(!m_bInitialised) initialise();

		m_dimension = getSize();
		update_scrollbars();

		if((m_backGraphics == null) ||
		   (m_dimension.width != m_backDimension.width) ||
		   (m_dimension.height != m_backDimension.height))
		{
			m_backDimension = m_dimension;
			m_backImage = createImage(m_dimension.width, m_dimension.height);
			m_backGraphics = m_backImage.getGraphics();
		}

		Graphics g = m_backGraphics;	// g is shorter to type!

		g.clearRect(0, 0, m_dimension.width, m_dimension.height);

		// Draw the design grids
		draw_grids();

		render_map();

		g.setColor(Color.yellow);
		g.drawString("("+(m_hAxisSign == -1 ? "-" : "")+m_axisPair.get_hAxis_string()+","
				+(m_vAxisSign == -1 ? "-" : "")+m_axisPair.get_vAxis_string()+")",10,20);

		frontGraphics.drawImage(m_backImage, 0, 0, this);
	}

	// Scrollbar stuff
	public void set_horizontal_scrollbar_value(int i)
	{
		if(m_hAxisSign == 1) m_topLeft_Coords.x = i;
		else m_topLeft_Coords.x = m_axisPair.select_components(m_map.dimensions()).x - i;

		update_brush_creation_anchor();
	}

	public void set_scrollbars(Scrollbar vert, Scrollbar horiz)
	{
		m_vert = vert;
		m_horiz = horiz;
	}

	public void set_vertical_scrollbar_value(int i)
	{
		if(m_vAxisSign == 1) m_topLeft_Coords.y = i;
		else m_topLeft_Coords.y = m_axisPair.select_components(m_map.dimensions()).y - i;

		update_brush_creation_anchor();
	}
}
