/*
 * Graph.java
 * 2008/12/11
 * 
 * Copyright (c) 2008 Potkuri-group
 */

package calculation;

import general.DataCollection;
import filecontroller.WeatherMap;
import java.util.ArrayList;
import general.Parameters;

/**
 * This class presents the net of vertices.
 * 
 * @author Potkuri-group
 * @version 1
 */
public class Graph {
	/** The storm limit of the weather matrix. */
	static final int STORM = 1;
	/** graph width. */
	static final int MAP_WIDTH = 500;
	/** graph height. */
	static final int MAP_HEIGHT = 500;
	/** matrix scale to graph. */
	static final int SCALE = 2;
	
	/** Safety zone distance for storms (how many graph nodes). */
	private int safety = 5;

	/** Address of the weather matrix. */
	private int[][] mapMatrix;
	/** Address of the WeatherMap class. */
	private WeatherMap map;

	/** Variables for the corner of the map. */
	private double cornerlat;
	/** Variables for the corner of the map. */
	private double cornerlon;
	
	/** Arc point sector start angle. */
	private double start;
	/** Arc point sector end angle. */
	private double end;
	/** Number of start points for planes. */
	private double startPointCount;
	
	/** graph matrix has a point (0,0) at left bottom corner. */
	private Vertex[][] graph;
	/** Mergepoint candidates, that is arc points. */
	private ArrayList[] arcpoints;
	
	/** Vertex to be null. */
	private Vertex nullVertex;
	/** Vertex acting as airport. */
	private Vertex airportVertex;

	/** reference to dataCollection interface. */
	private DataCollection dataCollection;
	/** reference to Parameters class. */
	private Parameters param;
	
	/**
	 * This is the constructor for the graph. It creates the nodes, and 
	 * sets the initial values.
	 * 
	 * @param data the DataCollection address as as parameter.
	 * @param width is the weather matrix width.
	 * @param height is the weather matrix height.           
	 */
	public Graph(DataCollection data, int width, int height) {

		this.dataCollection = data;
		this.graph = createGraph(width, height);
		this.airportVertex = graph[width / 2][height / 2];
		
		// Pre-calculate arcPoints
		if (data != null) {
			this.param = this.dataCollection.getParameterClass();
			
			this.safety = Integer.valueOf(this.param.getParameterValue("StormSafetyDistance"));
			
			this.start = Double.valueOf(param
					.getParameterValue("SectorStartDeg")); 
			
			this.end =  Double.valueOf(param
					.getParameterValue("SectorEndDeg"));
			
			this.startPointCount = Double.valueOf(param
					.getParameterValue("NumberOfStartPoints"));
			
			this.cornerlat = Double.valueOf(
					data.getParameterClass()
					.getParameterValue("LocationCornerLatitude")).doubleValue();
			
			this.cornerlon = Double.valueOf(
					data.getParameterClass()
					.getParameterValue("LocationCornerLongitude")).doubleValue();
				
			//if (this.startPointCount <= 8) {
				this.calculateArcPoints(this.start, this.end, this.startPointCount);
			//}				
		}				
	}

	/**
	 * a Constructor for testing purposes only.
	 * @param gRaph Self made graph to class.
	 */
	public Graph(Vertex[][] gRaph) {
		this.graph = gRaph;
	}
	
	/**
	 * a Method to set self defined arc points for testing.
	 * @param arcPoints An ArrayList[] of arc points to set.
	 */
	public void setArcPoints(ArrayList[] arcPoints) {
		this.arcpoints = arcPoints;
	}
	
	/**
	 * A graph creation method. Creates vertices and sets them up.
	 * @param width width of graph.
	 * @param height heigth of graph.
	 * @return returns the address of created graph. Null if failure.
	 */
	public Vertex[][] createGraph(int width, int height) {
		Vertex[][] net;
		Vertex vertex;
		int x, y, scale;
		double lat, lon;
		
		int origoX, origoY;
		origoX = width / 2;
		origoY = height / 2;
		double radius = java.lang.Math.pow(width / 2, 2);
		
		net = new Vertex[width][height];

		nullVertex = new Vertex(10000, 10000, 10000, 10000, false, null);
		scale = 1;
		
		// Create nodes
		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {
				// Check edge- area
				if ((java.lang.Math.pow((x - origoX), 2) 
						+ (java.lang.Math.pow((y - origoY), 2)) >= radius)) {
					net[x][y] = nullVertex;
					
				} else {
					// Calculate coordinates
					lon = cornerlon + (x * scale);
					lat = cornerlat + (y * scale);

					net[x][y] = new Vertex(lon, lat, x, y, true,
						nullVertex);
				}
			}
		}
		// Create adjacent lists
		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {
				vertex = net[x][y];

				if (vertex == null) {
					System.out.println("Null vertex found.");
					net = null;
					break;
				}

				if (y != width - 1) {
					vertex.setAdjacent(Vertex.EAST, net[x][y + 1]);
				}
				if (x != 0 && y != width - 1) {
					vertex.setAdjacent(Vertex.SOUTHEAST, net[x - 1][y + 1]);
				}
				if (x != 0) {
					vertex.setAdjacent(Vertex.SOUTH, net[x - 1][y]);
				}
				if (x != 0 && y != 0) {
					vertex.setAdjacent(Vertex.SOUTHWEST, net[x - 1][y - 1]);
				}
				if (y != 0) {
					vertex.setAdjacent(Vertex.WEST, net[x][y - 1]);
				}
				if (x != height - 1 && y != 0) {
					vertex.setAdjacent(Vertex.NORTHWEST, net[x + 1][y - 1]);
				}
				if (x != height - 1) {
					vertex.setAdjacent(Vertex.NORTH, net[x + 1][y]);
				}
				if (y != width - 1 && x != height - 1) {
					vertex.setAdjacent(Vertex.NORTHEAST, net[x + 1][y + 1]);
				}
			}
		}
		// Adjacent lists ready, net created.
		return net;
	}
	
	/**
	 * This method updates the graph, by the weatherdata included in the
	 * matrix.
	 * @param scale The scale by which the map will be scaled to graph size.
	 * @param matrix a two dimensional matrix, holding the weather data.
	 * @param graph is the graph to update.
	 * @param safety safety border size to storms.
 	 * @return returns true if graph was updated, else false.
 	 */
	public boolean graphUpdate(int scale, int[][] matrix, Vertex[][] graph,
			int safety) {
		int x, y;
		Vertex ap;
		
		ap = getAirport();
		
		if (graph == null || matrix == null || ap == null) {
			return false;
		}
		
		for (x = 0; x < graph.length; x++) {
			for (y = 0; y < graph[x].length; y++) {
				if (checkSquare(scale, x * scale, y * scale, matrix)) {
					graph[x][y].setAvailable(false);
					graph[x][y].setStorm(true);
					if (graph[x][y].distanceTo(ap) >= 18.52) {
						setSafezone(false, graph[x][y], safety);
					}
				}
			}
		}	
		return true;
	}
	
	/**
	 * This method sets nodes around a storm not available, thus setting 
	 * an unavailable perimeter around the bad weather.
	 * @param state boolean value to set to node.
	 * @param node node to start from
	 * @param width size of the unavailable zone
	 */
	public void setSafezone(boolean state, Vertex node, int width) {
		int i;
		Vertex[] adj;
		
		adj = node.getAdjacents();
		
		for (i = 0; i < adj.length && width > 0; i++) {
			if (adj[i].isAvailable() == !state) {
				adj[i].setAvailable(state);
				if (!state) {
					adj[i].setStormSafety(true);
				} else {
					adj[i].setStormSafety(false);
				}
				setSafezone(state, adj[i], width - 1);
			}			
		}
			
	}
	
	/**
	 * Checks a certain size square in a two dimensional matrix, if it 
	 * has a value larger than something (STORM).
	 * @param size the size of the square to check
	 * @param x coordinate in the matrix
	 * @param y coordinate in the matrix
	 * @param matrix a two dimensional matrix
	 * @return boolean value true if a storm is found, false if not.
	 */
	public boolean checkSquare(int size, int x,  int y, int[][] matrix) {
		int i, j;
		
		i = x;
		while (i < x + size && i < matrix.length) {
			j = (matrix.length - 1) - y;	
			while (j > (matrix.length - 1) - y - size && j >= 0) {
				if (matrix[i][j] >= STORM) {				
					return true;
				}
				j--;
			}
			i++;
		}
		
		return false;
	}
	
	/**
	 * Resets the graph.
	 * @return true if resetting went ok, else false.
	 * @param net The graph to reset.
	 */
	public boolean resetGraph(Vertex[][] net) {
		int x, y;
		
		if (net == null) {
			return false;
		}
		
		for (x = 0; x < net.length; x++) {
			for (y = 0; y < net[x].length; y++) {
				net[x][y].setAvailable(true);
				net[x][y].setStorm(false);
				net[x][y].setStormSafety(false);
				net[x][y].setPathSafety(0);
			}
		}
		return true;
	}

	/**
	 * Returns the current graph.
	 * @return two dimensional array of Vertexes
	 */
	public Vertex[][] getGraph() {
		return graph;
	}

	/**
	 * Return Airport vertex.
	 * @return address of the Vertex that is an airport
	 */
	public Vertex getAirport() {
		return this.airportVertex;
	}
	
	/**
	 * Return ArcPoints.
	 * @return arcPoints
	*/
	public ArrayList<Vertex>[] getArcPoints() {
		return this.arcpoints;
	}
	/**
	 * Returns ArcPoints of outermost arc.
	 * @return ArrayList of Vertex instances in the outermost arc
	 *  
	 */
	public ArrayList<Vertex> getOuterArc() {
		int len;
		
		len = arcpoints.length - 1;
	
		return 	this.arcpoints[len];
	}
	
	/**
	 * The update method for Graph class. Looks for new weatherdata, and updates
	 * the graph.
	 * @return true if update was successful
	 */
	public boolean update() {
		boolean success;
		success = true;
		
		if (dataCollection == null) { // datacollection not found.
			System.out.println("DataCollection class not found.");
			return false;
		}
		map = dataCollection.getWeatherMapClass();
		
		if (map == null) { // map not found.
			System.out.println("Could not get WeatherMap.");
			return false;
		} 
		mapMatrix = map.getMapMatrix(); // Get weather matrix
		resetGraph(this.graph);
		if (!graphUpdate(SCALE, mapMatrix, graph, safety)) {
			return false;
		}
		getAirport().setAvailable(true);
		setSafezone(true, getAirport(), 7);
		success = true;
		
		return success;
	}
	
	/** 
	 * Public method to test private method calculateArcPoints().
	 * @param start start angle of arrival tree sector.
	 * @param end angle of arrival tree sector.
	 * @param num number of startpoints in the secotr.
	 */
	public void testCalculateArcPoints(double start, double end 
			, double num) {
		this.calculateArcPoints(start, end, num);
	}
	
	/**
	 * Set merge points.
	 * @param start start angle of arrival tree sector.
	 * @param end angle of arrival tree sector.
	 * @param startPointCount number of startpoints in the secotr.
	 */
	public void calculateArcPoints(double start, double end, double startPointCount) {
		int x, y, xCheck, yCheck, originX, originY;
		double angle, radius;
		
		// Radius, distances to arcs.
		double[] nmiToVertex = { 18.52, 55.56, 111.12};
		
		int circleCount = 4;
		int currentCircle = 0;

		ArrayList<Vertex>[] circles = new ArrayList[circleCount];
		ArrayList<Vertex> circle;
		Vertex vertexTemp;
	
		// Initialize local variables
		originX = 250;
		originY = 250;
		xCheck = -1;
		yCheck = -1;
 		
		// Generate arcPoints.
		for (int i = 0; i < (circleCount - 1); i++) {
			circle = new ArrayList<Vertex>();
			radius = nmiToVertex[i];
			angle = 0;
			
			// Values are optimized for this radius. If radius change, then you must change these also.
			for (int j = 0; j < 60000; j++) {
				angle = j * 0.006;
				
				y = originY + (int) (radius * Math.sin(Math.toRadians(angle)));
				x = originX + (int) (radius * Math.cos(Math.toRadians(angle)));
				
				if (j == 0) {
					// First round.
					xCheck = x;
					yCheck = y;
				}

				if (xCheck != x || yCheck != y) {
					// Next vertex
					
					if ((xCheck - x) != 0 && (yCheck - y) != 0) {
						// This is for aStar: If we not move straight,
						// then we have to add extra mergepoint next to the old mergepoint.

						if (x < originX) {
							vertexTemp = graph[xCheck + 1][yCheck];
						} else {
							vertexTemp = graph[xCheck - 1][yCheck];						
						}
						circle.add(vertexTemp);
					}
					
					xCheck = x;
					yCheck = y;
					
					// Add new mergepoint to circle.
					vertexTemp = graph[x][y];
					circle.add(vertexTemp);
					
				}
			}
			
			// Add current circle to circles- array. This is used 
			// in BuildTree- class.
			circles[currentCircle] = circle;
			currentCircle++;
		}
		
		// Calculate start -points to outermost circle.
		circle = new ArrayList<Vertex>();
		
		radius = 249.99;
		
		for (int j = 0; j < startPointCount; j++) {
			angle = start + j * Math.abs((end - start) / (startPointCount - 1));
			
			y = originY + (int) (radius * Math.sin(Math.toRadians(angle)));
			x = originX + (int) (radius * Math.cos(Math.toRadians(angle)));
			vertexTemp = graph[x][y];
			circle.add(vertexTemp);
		}
		circles[currentCircle] = circle;

		this.arcpoints = circles;
	}
	
	/**
	 * Ascii presentation of the graph!
	 *
	 */
	public void printgraph() {
		int x, y, parx, pary;
		
		parx = this.graph.length;
		pary = this.graph.length;
		x = parx;
		y = pary;

		for (x = parx - 1; x >= 0; x--) {
			System.out.print(x + "|");
			
			for (y = 0; y < pary; y++) {
				if (this.graph[x][y].isAvailable()) {
					System.out.print(" A |");
				} else {
					if (graph[x][y] == nullVertex) {
						System.out.print(" n |");
					} else {
						System.out.print(" - |");
					}
				}
			} 
			System.out.print("\n");
			
		}
		System.out.print("  ");
		y = 0;
		while (y < pary) {
			System.out.print(" " + y + "  ");
			y++;
		}
		System.out.println();
	}
	
}
