/*
 * Copyright Kilo project team, 2004. All rights reserved.
 */
package server;

import shared.Constants;
import shared.Position;
import shared.Renderer;
import shared.MapImage;
import shared.NotLinkedException;
import java.awt.image.*;
import java.awt.*;
import org.apache.batik.transcoder.*;//See Apache Batik, SVG Java library.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * Child class of Renderer for the generation of a MapImage for display by the Applet as a background image
 * 
 * @author Alaric
 * @version 1.1
 */
public class MapRenderer extends Renderer
{
	//Map data
	private server.Map map;
	
	private Color defaultBG = Constants.DEFAULT_BG;
	private Color defaultFG = Constants.DEFAULT_FG;
	
	//street colours
	private Color pedestrianStreetColour =Constants.PEDESTRIAN_STREET_COLOUR;
	private Color motorwayColour = Constants.MOTORWAY_COLOUR;
	private Color cyclePathColour = Constants.CYCLE_PATH_COLOUR;
	
	//SVG stuff
	/*private String svgSrc;
	private String roadPathDefsSVGSrc;
	private String roadSVGSrc;
	*/
	private String[] svgRoadPath = new String[6];
	private String[] svgRoadText = new String[6];
	private String[] svgRoadLines = new String[6];
	
	private String[] svgVertices = new String[6];
	
	private String backgroundSVG="";
	private String backgroundSVGSrc=Constants.SVG_DATA_FILE;
	
	//NEW CACHE
	private java.util.Map cacheMap;
	private final int MAPIMAGE_CACHE_SIZE=Constants.RENDERER_CACHE_SIZE;
	
	//OLD CACHE
	public MapImage[] cache = new MapImage[6];
	private int indexIntoCache = 0; //used as an index to use it as a simple cyclic list
	
	
	//private BufferedImage renderOutImg;
	
	private void makeCache(){
		java.util.Map ncache = new LinkedHashMap(MAPIMAGE_CACHE_SIZE+1,0.75f,true){
			//add a method over riding
			public boolean removeEldestEntry(java.util.Map.Entry eldest){
				return size() > MAPIMAGE_CACHE_SIZE;
			}
		};
		this.cacheMap=(java.util.Map)Collections.synchronizedMap(ncache);
	}
	
	public MapRenderer(server.Map map, Color bg, Color fg){
		this.bgColour=bg;
		this.fgColour=fg;
		this.map = map;
		initialise_arrays();
		genSVG();
		makeCache();
	}
	public MapRenderer(server.Map map){
		this.bgColour=defaultBG;
		this.fgColour=defaultFG;
		this.map = map;
		//now generate the SVG file
		initialise_arrays();
		genSVG();
		makeCache();
	}
	private void initialise_arrays(){
		for(int l = 0; l < this.svgRoadPath.length; l++){
			this.svgRoadPath[l]="";
		}
		for(int l = 0; l < this.svgRoadLines.length; l++){
			this.svgRoadLines[l]="";
		}
		for(int l = 0; l < this.svgRoadText.length; l++){
			this.svgRoadText[l]="";
		}
	}
	
	public static float[] reverseZoomLevels(){
		float[] zooms = new float[Constants.ZOOM_LEVELS.length];
		
		for(int i = 0; i < Constants.ZOOM_LEVELS.length; i++){
			zooms[((Constants.ZOOM_LEVELS.length-1)-i)]=Constants.ZOOM_LEVELS[i];
		}
		return zooms;
	}

	/**
	 * A method to determine the type of roads, can be called twice within max() with the edge's complement to determine the road type that takes priority
	 * Format of the road types
	 * <ul>
	 * <li>0 = footpath</li>
	 * <li>1 = Pedestrianised Street</li>
	 * <li>2 = Cycle lane</li>
	 * <li>3 = Road (one way)</li>
	 * <li>4 = Road</li>
	 * <li>5 = Motorway</li>
	 * </ul>
	 * There is some redundancy to allow for future extensibility, currently only 1,2,4 and 5 are reachable.
	 * 
	 * @since 1.1
	 * @param one an {@link Edge} to test its type
	 * @return an int describing the type of road, for use with colouring the roads
	 */
	protected int determineRoadType(Edge one){
		int Type=4; //default to road, although if it goes wrong we may print spurious roads
		
		if(one.getType().car()){
			//car can travel down
			if(one.getType().bike()){//bike can travel
				if(one.getType().pedestrian()){//pedestrian can
					//normal road
					Type=4;
				}//end pedestrian can
				else{//pedestrian can't
					
				}//end pedestrian can't
			}//end bike can travel
			else{//bike can't travel
				if(one.getType().pedestrian()){//pedestrian can
					//no cycling zone?
				}//end pedestrian can
				else{//pedestrian can't
					//we've hit the M11!
					Type=5;
				}//end pedestrian can't
			}//end else bike can't
		}//end car can
		else{//car can't
			if(one.getType().bike()){//bike can travel
				if(one.getType().pedestrian()){//pedestrian can
					Type=1;
				}//end pedestrian can
				else{//pedestrian can't
					//cycle path
					Type=2;
				}//end pedestrian can't
			}//end bike can travel
			else{//bike can't travel
				if(one.getType().pedestrian()){//pedestrian can
					//pedestrian street
					Type=1;
				}//end pedestrian can
				else{//pedestrian can't
					//HMMM
					//NOBODY CAN TRAVEL
					//EEEK
					//we'll just convienently ignore this situation... *whistles*
				}//end pedestrian can't
			}//end else bike can't
		}//end car can't
		return Type;
	}
	
	protected void genSVG(){
		//Map map = (Map)o;
		//if(map==null) return null;
		Edge[] allEdges = this.map.getEdges();
		int charWidth = 10; //DEFINE THE  WIDTH OF THE CHARACTERS FOR CHOPPING OFF SECTIONS OF ROAD NAME
		//TEST TEST TEST
		/*boolean test=false;
		for(int k = 0; k < allEdges.length; k++){
			try{
				System.out.println("FROM: ("+allEdges[k].getFromNode().getPosition().getX()+","+allEdges[k].getFromNode().getPosition().getY()+") TO: ("+allEdges[k].getToNode().getPosition().getX()+","+allEdges[k].getToNode().getPosition().getY()+")");
				test=true;
			}
			catch(NotLinkedException e){}
		}*/
		//if(test) return null;
		//</TEST>
		//this.svgSrc="";
		
		this.backgroundSVG="";
		for(int i = 0; i < allEdges.length; i++){
			try{
				//check each edge from the nodes
				int fromNodeType=0;int fromNodeNotability=0;
				for(int z = 0; z < allEdges[i].getFromNode().getEdges().length; z++){
					fromNodeType=Math.max(fromNodeType, Math.max(determineRoadType(allEdges[i].getFromNode().getEdges()[z]),determineRoadType(allEdges[i].getFromNode().getEdges()[z].getEdgeComplement())));
					fromNodeNotability=Math.max(fromNodeNotability, Math.max(allEdges[i].getFromNode().getEdges()[z].getRoad().getNotability(),allEdges[i].getFromNode().getEdges()[z].getEdgeComplement().getRoad().getNotability()));
				}
				Color nodeColour = (fromNodeType==1)?this.pedestrianStreetColour:((fromNodeType==2)?this.cyclePathColour:((fromNodeType==5)?this.motorwayColour:this.fgColour));
				this.drawVertex(allEdges[i].getFromNode().getPosition(),nodeColour,fromNodeNotability);
				
				int toNodeType=0;int toNodeNotability=0;
				for(int z = 0; z < allEdges[i].getToNode().getEdges().length; z++){
					toNodeType=Math.max(toNodeType, Math.max(determineRoadType(allEdges[i].getToNode().getEdges()[z]),determineRoadType(allEdges[i].getToNode().getEdges()[z].getEdgeComplement())));
					toNodeNotability=Math.max(toNodeNotability, Math.max(allEdges[i].getToNode().getEdges()[z].getRoad().getNotability(),allEdges[i].getToNode().getEdges()[z].getEdgeComplement().getRoad().getNotability()));
				}
				nodeColour = (toNodeType==1)?this.pedestrianStreetColour:((toNodeType==2)?this.cyclePathColour:((toNodeType==5)?this.motorwayColour:this.fgColour));
				this.drawVertex(allEdges[i].getToNode().getPosition(),nodeColour,toNodeNotability);
				
				//do the subnodes too
				int subNodeType = Math.max(determineRoadType(allEdges[i]),determineRoadType(allEdges[i].getEdgeComplement()));
				int subNodeNotability = Math.max(allEdges[i].getRoad().getNotability(),allEdges[i].getEdgeComplement().getRoad().getNotability());
				Color subnodeColour = (subNodeType==1)?this.pedestrianStreetColour:((subNodeType==2)?this.cyclePathColour:((subNodeType==5)?this.motorwayColour:this.fgColour));
				for(int j=0; j < allEdges[i].getSubnodes().length; j++){
					this.drawVertex(allEdges[i].getSubnodes()[j].getPosition(), subnodeColour, subNodeNotability);
				}
				
				int roadType = Math.max(determineRoadType(allEdges[i]),determineRoadType(allEdges[i].getEdgeComplement()));
				//now for roads
				Color edgeColour=this.fgColour;
				edgeColour=(roadType==1)?this.pedestrianStreetColour:((roadType==2)?this.cyclePathColour:((roadType==5)?this.motorwayColour:this.fgColour));
				
				if(allEdges[i].getSubnodes()==null||allEdges[i].getSubnodes().length==0){
					//System.out.println("NOT = Ooops road: "+allEdges[i].getRoad().getName()+" has no subnodes");
					this.drawRoad(allEdges[i].getFromNode().getPosition(),allEdges[i].getToNode().getPosition(), edgeColour, allEdges[i].getRoad().getNotability(), (edgeColour.equals(this.pedestrianStreetColour))? false : true);
				}
				else{
					//System.out.println("HAS = Road: "+allEdges[i].getRoad().getName()+" has subnodes");
					for(int l = 0; l <= allEdges[i].getSubnodes().length; l++){
						if(l==0){
							this.drawRoad(allEdges[i].getFromNode().getPosition(),allEdges[i].getSubnodes()[l].getPosition(), edgeColour, allEdges[i].getRoad().getNotability(), (edgeColour.equals(this.pedestrianStreetColour))? false : true);
						}
						else if(l==(allEdges[i].getSubnodes().length)){
							this.drawRoad(allEdges[i].getSubnodes()[l-1].getPosition(),allEdges[i].getToNode().getPosition(), edgeColour, allEdges[i].getRoad().getNotability(), (edgeColour.equals(this.pedestrianStreetColour))? false : true);
						}
						else{
							this.drawRoad(allEdges[i].getSubnodes()[l-1].getPosition(),allEdges[i].getSubnodes()[l].getPosition(), edgeColour, allEdges[i].getRoad().getNotability(), (edgeColour.equals(this.pedestrianStreetColour))? false : true);
						}
					}
				}
				//OLD//this.drawRoad(allEdges[i].getFromNode().getPosition(),allEdges[i].getToNode().getPosition(), edgeColour, (edgeColour.equals(this.pedestrianStreetColour))? false : true);
			}
			catch(NotLinkedException e){}
			
		}
		
		//NOW DO ROADS
		//roadPathDefsSVGSrc="";
		//roadSVGSrc="";
		Hashtable roadHashTable=new Hashtable();
		
		for(int i = 0; i < allEdges.length; i++){//iterate to generate roads
			//try{
				ArrayList listOfPositions = (ArrayList)roadHashTable.get(allEdges[i].getRoad().getShortName());
				if(listOfPositions==null){//not got any positions defined for that road name yet.
					ArrayList justCreatedArrayList=new ArrayList();
					justCreatedArrayList.add(new Integer(i));
					//justCreatedArrayList.add(allEdges[i].getFromNode().getPosition());
					//justCreatedArrayList.add(allEdges[i].getToNode().getPosition());
					roadHashTable.put(allEdges[i].getRoad().getShortName(), justCreatedArrayList);
				}
				else{//there are already positions... append the new ones
					listOfPositions.add(new Integer(i));
					//listOfPositions.add(allEdges[i].getToNode().getPosition());
					roadHashTable.put(allEdges[i].getRoad().getShortName(), listOfPositions);
				}
			//}
			//catch(NotLinkedException e){}
		}//end road iteration loop
		
		for (Enumeration e = roadHashTable.keys() ; e.hasMoreElements() ;) {
			String RoadName = (String)e.nextElement();
			ArrayList thisRoadArrayList = (ArrayList)roadHashTable.get(RoadName);
			
			//order the roads to be something meaningful, should kill duplicate roads too [ that's UNDOCUMENTED & UNTESTED though - rock and roll baby]
			
			ArrayList orderedRoadEdges = orderedRoad(allEdges, thisRoadArrayList, RoadName);
			ArrayList positionPath = new ArrayList();
			Object[] edgeArray = orderedRoadEdges.toArray();
			for(int j=0; j < edgeArray.length; j++){
				try{
					if(j==0){
						positionPath.add(((Edge)edgeArray[j]).getFromNode().getPosition());
					}
					//deal with subnodes
					for(int k = 0; k < ((Edge)edgeArray[j]).getSubnodes().length; k++){
						positionPath.add(((((Edge)edgeArray[j]).getSubnodes())[k]).getPosition());
					}
					positionPath.add(((Edge)edgeArray[j]).getToNode().getPosition());
				}
				catch(NotLinkedException eee){}
			}
			
			try{
				if(((Edge)edgeArray[0]).getFromNode().getPosition().getX()>((Edge)edgeArray[(edgeArray.length-1)]).getToNode().getPosition().getX()){
					Collections.reverse(positionPath);
				}
			}
			catch(NotLinkedException eee){}

			Object[] routePath = positionPath.toArray();
			
			//System.out.println(e.nextElement());
			//System.out.println(RoadName);
			String pathDef="<path id=\""+RoadName.replaceAll("\\("," ").replaceAll("\\)"," ")+"\" d=\"M ";
			
			double lengthOfPath=0.0;
			
			for(int i = 0; i<routePath.length; i++){
				//System.out.println(""+((Integer)routePath[i]).toString());
				if(i!=0) pathDef+=" L";
				pathDef += ((Position)routePath[i]).getX()+" "+((Position)routePath[i]).getY();
				//System.out.println(RoadName+" "+((Position)routePath[i]).toString());
				//add up the length too.
				if(i!=(routePath.length-1)){
					lengthOfPath+=Math.sqrt(Math.abs(((Position)routePath[i]).getX()-((Position)routePath[i+1]).getX())*Math.abs(((Position)routePath[i]).getX()-((Position)routePath[i+1]).getX())+Math.abs(((Position)routePath[i]).getY()-((Position)routePath[i+1]).getY())*Math.abs(((Position)routePath[i]).getY()-((Position)routePath[i+1]).getY()));
				}
				
				//System.out.println("("+((Position)routePath[i]).getX()+","+((Position)routePath[i]).getY()+")");
			}
			pathDef+="\" />\n";
			int indexIntoStorageArrays = ((Edge)edgeArray[0]).getRoad().getNotability();
			if(RoadName.length()*charWidth > lengthOfPath) continue;
			this.svgRoadPath[indexIntoStorageArrays]+=pathDef;
			//System.out.println(lengthOfPath);
			//System.out.println("");
			
			String textOnPathDef="";
			textOnPathDef="<text style=\"font-family:Verdana; font-size:15; font-weight:200; fill:blue; text-anchor:middle\" dy=\"+5\">\n\t<textPath xlink:href=\"#"+RoadName.replaceAll("\\("," ").replaceAll("\\)"," ")+"\" startOffset=\"50%\" >\n\t\t<tspan style=\"fill:black\">"+RoadName.toUpperCase()+"</tspan>\n\t</textPath>\n</text>\n\n";
			this.svgRoadText[indexIntoStorageArrays]+=textOnPathDef;
		}
		
	}
	
	private ArrayList orderedRoad(Edge[] allEdges, ArrayList edgesOnRoad, String roadName){
	try{
		Object[] edgeIndexes = edgesOnRoad.toArray();
		
		Edge newWorkingEdge = allEdges[(((Integer)(edgeIndexes[0])).intValue())];
		
		Edge workingEdge;
		do
		{
			workingEdge = newWorkingEdge;
			for(int i=0;i<workingEdge.getFromNode().getEdges().length;i++)
			{
				if(workingEdge.getFromNode().getEdges()[i].getRoad().getShortName().equals(roadName) && workingEdge.getFromNode().getEdges()[i].getToNode()!=workingEdge.getToNode())
				{ 
					newWorkingEdge = workingEdge.getFromNode().getEdges()[i].getEdgeComplement();
					//System.out.println("ACK! Edge name:"+workingEdge.getRoad().getShortName()+" "+roadName);
					//break;
				}
			}
		}
		while (newWorkingEdge != workingEdge);
		workingEdge = newWorkingEdge;
		// workingEdge should now be the first Edge
		
		ArrayList validEdges = edgesOnRoad;
		
		Edge[] orderedEdges = new Edge[validEdges.size()];
		for(int i=0; i<validEdges.size(); i++)
		{
			orderedEdges[i] = workingEdge;
			
			findNextEdge: for(int j=0; j<workingEdge.getToNode().getEdges().length; j++)
			{
				if(workingEdge.getToNode().getEdges()[j] != workingEdge.getEdgeComplement() && workingEdge.getToNode().getEdges()[j].getRoad().getShortName().equals(roadName))
				{
					workingEdge = workingEdge.getToNode().getEdges()[j];
					break findNextEdge;
				}
			}
		}
		orderedEdges[validEdges.size()-1] = workingEdge;

		ArrayList finalOrder = new ArrayList();
		for(int j = 0; j < orderedEdges.length; j++){
			finalOrder.add(orderedEdges[j]);
		}
		return finalOrder;
	}
	catch(NotLinkedException e){return new ArrayList();}
		
	}
	

	
	public static String genFileName(Position origin, float scale){
		scale = 2 * scale;
		int intDoubleScale = Math.round(scale);
		return Constants.IMAGE_DIRECTORY+"image_"+origin.getX()+"_"+origin.getY()+"_"+intDoubleScale+".gz";
	}
	
	public static String genMapImageHash(Position origin, float scale){
		scale = 2 * scale;
		int intDoubleScale = Math.round(scale);
		return origin.getX()+"_"+origin.getY()+"_"+intDoubleScale;
	}

	private boolean validMapRequest(Position origin, float scale){
		double zOverZ0=scale/(2.0*(reverseZoomLevels()[0]));
		if(((origin.getX()%(zOverZ0*Constants.MAP_MAX_X))==0)&&((origin.getY()%(zOverZ0*Constants.MAP_MAX_Y))==0)) return true;
		return false;
	}

	/**
	 * Definition of a previous abstract method, used as the method to draw the map, takes a map object and returns the map back
	 * 
	 * 
	 * @param o an object containing the map data, each child class will define its own methods of interpreting this object
	 * @param origin intended origin of the map to be rendered a a Position object
	 * @param scale intended scale of the map to be renderd as an int
	 * @return a MapImage object containing all the necessary data
	 */
	public MapImage render(Object o, Position origin, float scale){
		//if(this.svgSrc==null || this.svgSrc.equals("")) return null;//something is broken!
		
		//check cache first
		String hashKey = genMapImageHash(origin,scale);
		MapImage mI = (MapImage)this.cacheMap.get(hashKey);
		if(mI!=null&&cacheMap.containsKey(hashKey)){
			if(mI.getOrigin().getX()==origin.getX()&&mI.getOrigin().getY()==origin.getY()&&mI.getScale()==scale){
				return mI;
			}
		}
		//then check if file exists, and output it if it does
		String fileToGet = genFileName(origin, scale);
		//System.out.println(fileToGet);
		File gzFile = new File(fileToGet);
		if(gzFile.canRead()){
			if(validMapRequest(origin, scale)){
				//valid map request... keep going
				//System.out.println("Valid Map");
				
			}
			else{
				//System.out.println("b0rkd map request - malicious?");
				return null;
			}
			//we can read it woo hoo!
			//grab it and return it!
			//check it's a valid MapImage first
			try{
				FileInputStream fileInput = new FileInputStream(gzFile);
				GZIPInputStream gzipInput = new GZIPInputStream(fileInput);
				ObjectInputStream objectStream = new ObjectInputStream(gzipInput);
				MapImage m = (MapImage) objectStream.readObject();
				objectStream.close();
				fileInput.close();
				if(m.getOrigin().getX()==origin.getX()&&m.getOrigin().getY()==origin.getY()&&m.getScale()==scale){
					//ADD TO THE CACHE
					this.cacheMap.put(genMapImageHash(origin,scale),m);
					return m;
				}
				else{
					//pritn an error
					System.out.println("not the correct image... WTF");
				}
			}
			catch(IOException ioE){
				//we probably want to do something with these
				//render another... leave it open do not return a value
			}
			catch(ClassNotFoundException cnfE){
				//not a MapImage or corrupted.
				//either way we want to render another
			}
		}
		//else generate the map
		String newSVG="";
		//set origin and scale
		if(origin!=null&!(scale<0.5)){
			this.mapOrigin=origin;
			this.mapScale=scale;
			
		}
		else return null;
		
		int cutOffPointNames = 5;
		if(scale > 2.0){
			//take off some roads
			cutOffPointNames=1;
		}
		//path defs
		newSVG+="<defs>\n";
		//newSVG+=this.roadPathDefsSVGSrc;
		for(int i = 0; i < this.svgRoadPath.length; i++){
			if(i == 0||i == 1) continue;
			if((i<=cutOffPointNames)&&this.svgRoadPath[i]!=null){
				newSVG+=this.svgRoadPath[i];
			}
		}
		newSVG+="</defs>\n\n";
		
		
		//ENTER SVG FILE INCLUDE BIT
		if(this.backgroundSVG==null||this.backgroundSVG.equals("")){
			File f = new File(this.backgroundSVGSrc);
			if(f.canRead()){
				try {
			        BufferedReader in = new BufferedReader(new FileReader(this.backgroundSVGSrc));
			        String str;
			        while ((str = in.readLine()) != null) {
			            this.backgroundSVG+=str;
			        }
		        	in.close();
		    	}
				catch (IOException e) {
				}
			}
		}
		//END SVG FILE INCLUDE BIT

		newSVG+=this.backgroundSVG;
		
		//Vertices
		for(int i=0; i < this.svgVertices.length; i++){
			if(this.svgVertices[i]!=null){
				//newSVG+="\n<g style=\"fill:rgb("+this.fgColour.getRed()+","+this.fgColour.getGreen()+","+this.fgColour.getBlue()+");\">\n";
				//newSVG+=this.svgVertices[i].replaceAll("<RADIUS>",""+((getRoadWidth(true)/2.0)-1)).replaceAll("<RADIUSSMALL>",""+((getRoadWidth(false)/2.0)-1));
				newSVG+=this.svgVertices[i].replaceAll("<RADIUS>",""+((getRoadWidth(true)/2.0))).replaceAll("<RADIUSSMALL>",""+((getRoadWidth(false)/2.0)));
				//newSVG+="\n</g>\n";
			}
		}
		
		//Road lines
		
		if(this.svgRoadLines[0]!=null){
			newSVG+="<g style=\"stroke:rgb("+this.fgColour.getRed()+","+this.fgColour.getGreen()+","+this.fgColour.getBlue()+");stroke-width:"+getRoadWidth(false)+"\">";
			newSVG+=this.svgRoadLines[0];
			newSVG+="</g>";
		}
		for(int i = 1; i < this.svgRoadLines.length-1; i++){
			if(this.svgRoadLines[i]!=null){
				newSVG+="<g style=\"stroke:rgb("+this.fgColour.getRed()+","+this.fgColour.getGreen()+","+this.fgColour.getBlue()+");stroke-width:"+getRoadWidth(true)+"\">";
				newSVG+=this.svgRoadLines[i];
				newSVG+="</g>";
			}
		}
		
		
		//<test>
		//whatever you do don't print it out at this size in the final model! CONTROLS text styles, use the style attribute of <g>
		newSVG+="<g>\n";
		for(int i = 0; i < this.svgRoadText.length; i++){
			if(i == 0||i == 1) continue;
			if((i<=cutOffPointNames)&&this.svgRoadText[i]!=null){
				newSVG+=this.svgRoadText[i];
			}
		}
		//+this.roadSVGSrc
		newSVG+="</g>\n\n";
		//</test>
		
		//set viewing bounds
		newSVG = "<svg width=\""+(MAPX)+"\" height=\""+(MAPY)+"\" viewBox=\""+origin.getX()+" "+origin.getY()+" "+(int)(scale*MAPX)+" "+(int)(scale*MAPY)+"\">"+newSVG;//add openning bracket
		
		
		newSVG = "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20000303 Stylable//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"+newSVG; //add the doctype declaration
		newSVG = "<?xml version=\"1.0\" standalone=\"no\" ?>"+newSVG; //add start of the SVG doc
		
		//append road names at appropriate zoom levels
		
		//clean up
		newSVG += "</svg>";//add closing bracket
		//<TEST>
		//System.out.println(newSVG);
		//</TEST>
		Reader svgInput = new StringReader(newSVG);	
		TranscoderInput input = new TranscoderInput(svgInput);
		BufferedImageTranscoder bit = new BufferedImageTranscoder();
		
		//SET some hints
		//use to set size and zoom!
		
		//finished setting hints
		try{
			bit.transcode(input, null);
		}
		catch(TranscoderException e){
			//do something with it?
			//must be an invalid SVG?
			System.out.println("TranscoderException, malformed SVG? "+e);
		}
		//the result should now be in the image
		//get with bit.getImage() -> BufferedImage
		//return something sensible
		//add it to cache too
		//System.out.println(bit.getImage());


		//
		MapImage generatedImage = new MapImage(origin, scale, bit.getImage());
		//write it to disk
		if(validMapRequest(origin,scale)){
			try{
				//System.out.println("Writing :"+fileToGet+" to disk");
				FileOutputStream fileOut = new FileOutputStream(gzFile);
				GZIPOutputStream gzOut = new GZIPOutputStream(fileOut);
				ObjectOutputStream objectOut = new ObjectOutputStream(gzOut);
				objectOut.writeObject(generatedImage);
				objectOut.flush();
				objectOut.close();
				fileOut.close();
			}
			catch(FileNotFoundException fnfE){System.out.println("FileNotFoundException "+fnfE);}
			catch(IOException ioE){System.out.println("IOException "+ioE);}
		} 
		//else{
		//	System.out.println("NOT WRITABLE");
		//}
		//add it to cache
		this.cacheMap.put(genMapImageHash(origin,scale),generatedImage);
		return generatedImage;
	}
	


	/**
	 * Reimplementation of drawRoad method to generate SVG into the specified index of an array.
	 * 
	 * @param one the first Position to be rendered
	 * @param two the second Position to be rendered
	 * @param c the Color of the road to be rendered
	 * @param index an int to index into the storage array for the SVG, used to classify different types of road
	 * @param majorMinor a boolean describing if it's a wide or narrow road, narrow roads are foot paths
	 */
	protected void drawRoad(Position one, Position two, Color c, int index, boolean majorMinor){
		//over ride previous method.
		if(one==null||two==null) return;
		//append the <line> tag
		this.svgRoadLines[index] += "<line x1=\""+one.getX()+"\" y1=\""+one.getY()+"\" x2=\""+two.getX()+"\" y2=\""+two.getY()+"\" style=\"stroke:rgb("+c.getRed()+","+c.getGreen()+","+c.getBlue()+")\"/>\n";
		
	}
	
	/**
	 * Reimplementation of the drawVertex method to generate SVG
	 * 
	 * @param v a Position object describing the location of the vertex to be rendered
	 * @param c the Color of the vertex to be rendered
	 * @param index an int to index into the storage array for the SVG, used to classify different types of vertex
	 */
	protected void drawVertex(Position v, Color c, int index){
		//just for nodes
		
		if(v==null) return;
		if(index==0) this.svgVertices[index] += "<circle cx=\""+v.getX()+"\" cy=\""+v.getY()+"\" r=\"<RADIUSSMALL>\" style=\"fill:rgb("+c.getRed()+","+c.getGreen()+","+c.getBlue()+")\"  />\n";
		else this.svgVertices[index] += "<circle cx=\""+v.getX()+"\" cy=\""+v.getY()+"\" r=\"<RADIUS>\" style=\"fill:rgb("+c.getRed()+","+c.getGreen()+","+c.getBlue()+")\"  />\n";
	}
	
	//UGLY HACK ALERT
	//Inner class over riding one of Batik's classes... don't ask. Seriously.
	
	protected class BufferedImageTranscoder extends org.apache.batik.transcoder.image.ImageTranscoder {
		
		BufferedImage renderOutImg;
		
		public BufferedImage createImage(int w, int h) {
			return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		}

		public void writeImage(BufferedImage img, TranscoderOutput output)
		throws TranscoderException {
			this.renderOutImg = img;
		}
		
		public BufferedImage getImage(){
			return this.renderOutImg;
		}
	}
}

