import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.geotools.factory.FactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.crs.DefaultGeocentricCRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.factory.PropertyAuthorityFactory;
import org.geotools.referencing.factory.ReferencingFactoryContainer;
import org.geotools.referencing.operation.DefaultCoordinateOperationFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationNotFoundException;

import fr.ocelet.runtime.List;



public class Geometry {

	
	public Geometry(){
	//	readPropertyFile();
	}
	public void getPlanarCoordinates(){
		
	}
	
	public List<List<Double>> getLatLongCoordinates(List<List<Double>> planarCoordinates) throws OperationNotFoundException, FactoryException{
		
		List<List<Double>> latLongCoordinates = new List<List<Double>>();
		
		CoordinateReferenceSystem crs = null;
		CoordinateReferenceSystem targetCrs = null;
		DefaultCoordinateOperationFactory cof = new DefaultCoordinateOperationFactory();
		
     /*  try {
			targetCrs = CRS.decode("EPSG:4326");
			//crs = CRS.decode("EPSG:32622");
		} catch (NoSuchAuthorityCodeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FactoryException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		
		/*String wkt = "PROJCS[\"UTM_Zone_10N\", "
            + "GEOGCS[\"WGS 84\", "
                + "DATUM[\"WGS 84\", "
                + "SPHEROID[\"WGS84\", 6378137.0, 298.257223563]], "
                + "PRIMEM[\"Greenwich\", 0.0], "	
                + "UNIT[\"degree\",0.017453292519943295], "
                + "AXIS[\"Longitude\",EAST], "
                + "AXIS[\"Latitude\",NORTH]], "
            + "PROJECTION[\"Transverse_Mercator\"], "
            + "PARAMETER[\"semi_major\", 6378137.0], "
            + "PARAMETER[\"semi_minor\", 6356752.314245179], "
            + "PARAMETER[\"central_meridian\", -123.0], "
            + "PARAMETER[\"latitude_of_origin\", 0.0], "
            + "PARAMETER[\"scale_factor\", 0.9996], "
            + "PARAMETER[\"false_easting\", 500000.0], "
            + "PARAMETER[\"false_northing\", 0.0], "
            + "UNIT[\"metre\",1.0], "
            + "AXIS[\"x\",EAST], "
            + "AXIS[\"y\",NORTH]]";*/
		/*String wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS"+
		              "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORI"+
		              "TY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\""+
		              ";EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORIT"+
		              "Y[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]";*/
		
		
		String wkt2 = "PROJCS[\"WGS 84 / UTM zone 22N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS"+
		                                                    " 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORI"+
		                                                    "TY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["+
		                                                    "\";EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORIT"+
		                                                    "Y[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],"+
		                                                    "UNIT[\"m\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION["+
		                                                    "\";Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER"+
		                                                    "[\"central_meridian\",-51],PARAMETER[\"scale_factor\",0.9996],PARAME"+
		                                                    "TER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHOR"+
		                                                    "ITY[\"EPSG\",\"32622\"],AXIS[\"Easting\",EAST],AXIS[\"No"+
		                                                    "rthing\",NORTH]]";

		String wkt = "GEOGCS[" + "\"WGS 84\"," + "  DATUM[" + "    \"WGS_1984\","
        + "    SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
        + "    TOWGS84[0,0,0,0,0,0,0]," + "    AUTHORITY[\"EPSG\",\"6326\"]],"
        + "  PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
        + "  UNIT[\"DMSH\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9108\"]],"
        + "  AXIS[\"Lat\",NORTH]," + "  AXIS[\"Long\",EAST],"
        + "  AUTHORITY[\"EPSG\",\"4326\"]]";
       
		

		
		//CoordinateReferenceSystem ncrs = crsFactory.createFromWKT( wkt2 );

		//CRSFactory crsFactory = (CRSFactory) FactoryFinder.EMPTY_HINTS;
		
		//DefaultGeographicCRS defCrs = DefaultGeographicCRS.WGS84; 
		//crs = CRS.decode(wkt);
		CRSFactory crsFactory = ReferencingFactoryFinder.getCRSFactory(null);
		//CoordinateReferenceSystem crs = crsFactory.createFromWKT( wkt );
		
		//CoordinateReferenceSystem crsTest = CRS.parseWKT(wkt);
	// ????	MathTransform convert = CRS.findMathTransform( DefaultGeographicCRS.WGS84, DefaultGeocentricCRS.CARTESIAN);
		
		//targetCrs = CRS.parseWKT(wkt);

	/*	double[] x = new double[0];
		x[0] = 285028.9717982662;
		
		GeneralDirectPosition point = new GeneralDirectPosition(285028.9717982662, 596145.7107988701); 
		GeneralDirectPosition point2 = new GeneralDirectPosition(0.0, 0.0);
	
		
		CoordinateOperation coordinateOperation = cof.createOperation(crs, targetCrs);
		try {
	
			coordinateOperation.getMathTransform().transform(point, point2);
		} catch (TransformException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for(double d : point2.getCoordinate()){
			System.out.println(d);
		}*/
        return latLongCoordinates;
		
	}
	
	
	public void readPropertyFile(){
		
		
		/*	URL url = // application directory or user directory? where you are looking for epsg.properties
				 new URL(url, "epsg.properties"); //$NON-NLS-1$
				if ("file".equals(proposed.getProtocol())) { //$NON-NLS-1$
				    File file = new File(proposed.toURI());
				    if (file.exists()) {
				        epsg = file.toURI().toURL();
				    }
				}*/
				File file = new File("epsg.properties");
				// you may try several locations...
				URL epsg = null;
				try {
					epsg = file.toURI().toURL();
				} catch (MalformedURLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (epsg != null) {
				    Hints hints = new Hints(Hints.CRS_AUTHORITY_FACTORY, PropertyAuthorityFactory.class);
				    ReferencingFactoryContainer referencingFactoryContainer = ReferencingFactoryContainer
				                        .instance(hints);

				    PropertyAuthorityFactory factory = null;
					try {
						factory = new PropertyAuthorityFactory(
						                    referencingFactoryContainer, Citations.fromName("EPSG"), epsg);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} //$NON-NLS-1$

				    ReferencingFactoryFinder.addAuthorityFactory(factory);
				    ReferencingFactoryFinder.scanForPlugins(); // hook everything up
				
				}

		}
	
 public List<List<Double>> getClosiestSegment(List<List<Double>> line,List<Double> point){
		
		List<List<Double>> closiestSegment = new List<List<Double>>();
		
		List<Double> newPoint = line.get(0);
		List<Double> newSecondPoint = new List<Double>();
		
		Double distance = getDistance(point,line.get(0));
		
		for(List<Double> pointFixe : line){
			
			if(distance > getDistance(point,pointFixe)){
				distance = getDistance(point,pointFixe);
				newPoint = pointFixe;
			}
			
		}
		closiestSegment.add(newPoint);
		distance=getDistance(point,line.get(0));
		for(List<Double> pointFixe : line){
			
			if(pointFixe != newPoint){
				if(distance > getDistance(point,pointFixe)){
					distance = getDistance(point,pointFixe);
					newSecondPoint = pointFixe;
				}
			}
			
		}
		closiestSegment.add(newSecondPoint);
		
		return closiestSegment;
		
	}
	
	public Double getDistance(List<Double> firstPoint, List<Double> secondPoint){
		
		Double dist = 0.0;
		
		Double x1 = firstPoint.get(0);
		Double y1 = firstPoint.get(1);
		
		Double x2 = secondPoint.get(0);
		Double y2 = secondPoint.get(1);
		
		dist = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
				
		return dist;
	}
	
	public List<List<Double>> getMovedLineFromLine(List<List<Double>> lineToMove, List<List<Double>> line){
		
		List<List<Double>> moovedLine = new List<List<Double>>();
		
		List<Double> firstPoint = lineToMove.get(0);
		List<Double> secondPoint = lineToMove.get(lineToMove.size()-1);
		
		List<List<Double>> firstClosiestSegment = new List<List<Double>>();
		List<List<Double>> secondClosiestSegment = new List<List<Double>>();
		
		List<Double> firstNewPoint = new List<Double>();
		List<Double> secondNewPoint = new List<Double>();
		
		firstClosiestSegment = getClosiestSegment(line, firstPoint);
		secondClosiestSegment = getClosiestSegment(line, secondPoint);
		
	  firstNewPoint = getModifiedCoordinates(firstPoint, firstClosiestSegment);
	  secondNewPoint = getModifiedCoordinates(secondPoint, secondClosiestSegment);
		
	  moovedLine.add(firstNewPoint);
	  for(int i=1; i<lineToMove.size()-1;i++){
		  moovedLine.add(lineToMove.get(i));
	  }
	  moovedLine.add(secondNewPoint);
		return moovedLine;
	}
	
public List<Double> getModifiedCoordinates(List<Double> listToModify, List<List<Double>> list){
		
		List<Double> newList = new List<Double>();
		
		//point a modifier
		Double x1 = listToModify.get(0);
		Double y1 = listToModify.get(1);
		
		// premier point de la droite
		Double x2 = list.get(0).get(0);
		Double y2 = list.get(0).get(1);
		
		// deuxième point de la droite
		Double x3 = list.get(1).get(0);
		Double y3 = list.get(1).get(1);
		
		//nouveau point 
		Double x = listToModify.get(0);
		Double y = listToModify.get(1);
		
		Double dx = x3 - x2;
		Double dy = y3 - y2;
		
		if(dx == 0.0){
			x = x2;
		}
		if(dy == 0.0){
			y = y2;
		}
		
		if((dx != 0.0) && (dy != 0.0)){
			
			Double p = dy / dx;
			Double delta = -(p*p+1)/p;
			Double alpha = y2 - p*x2;
			Double beta = y1 + (dx/dy)*x1;
			
			x = (alpha - beta) / delta;
			y = (p*p*beta + alpha) / (p*p+1);			
			
		}		
		newList.add(x);
		newList.add(y);
		return newList;		
		
	}	



	public List<Double> inverseMercator(Double x, Double y){
	
		List<Double> coord = new List<Double>();
		Double lon = (x / 6378137.000) * 180;
		Double lat = (y / 6356752.3141) * 180; 
		 
		lat = 180/Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
		//lon = 180/Math.PI * (2 * Math.atan(Math.exp(lon * Math.PI / 180)) - Math.PI / 2);
		coord.add(lat);
		coord.add(lon);
		
		return coord;
	}

	public List<List<Double>> convertLineStringInLatLong(List<List<Double>> coordinates){
		
		List<List<Double>> newCoordinates = new List<List<Double>>();
		
		for(List<Double> list : coordinates){
			List<Double> newPoint = new List<Double>();
			
			newPoint = inverseMercator(list.get(0), list.get(1));
			newCoordinates.add(newPoint);
		}
		
		return newCoordinates;
	}

	public List<List<Double>> triListByY(List<List<Double>> coordinates){
		
		
		List<List<Double>> triCoordinates = new List<List<Double>>();
		List<List<Double>> pivot = new List<List<Double>>();
		
		
		List<Double> pivotCoord = coordinates.get(0);
		
		
		for(List<Double> coord : coordinates){
			if(coord.get(1) > pivotCoord.get(1)){
				pivotCoord = coord;
			}
			
		}
		
	triCoordinates.addAll(coordinates.subList(coordinates.indexOf(pivotCoord), coordinates.size()-1));
	triCoordinates.addAll(coordinates.subList(0, coordinates.indexOf(pivotCoord)-1));		
	
		return triCoordinates;
	}
	
	
 public List<List<Double>> getMovedCoordinates(List<List<Double>> coordinatesList, Double alpha, Double beta){
		
		List<List<Double>> finalCoordinates = new List<List<Double>>();
		
		
		Double gamma = (alpha/2)+(beta/5);
		
		for(int i = 0; i<coordinatesList.size();i++){
			List<Double> coordinates = coordinatesList.get(i);
			
			Double x = coordinates.get(0);
			Double y = coordinates.get(1);
			List<Double> finalCoordinate = new List<Double>();
			
			Double x2 = x + gamma;
			Double y2 = y + gamma;
			
			finalCoordinate.add(x2);
			finalCoordinate.add(y2);
			
			finalCoordinates.add(finalCoordinate);
		}
		
		/*for(List<Double> coordinates : coordinatesList){
			Double x = coordinates.get(0);
			Double y = coordinates.get(1);
			List<Double> finalCoordinate = new List<Double>();
			
			Double x2 = x + gamma;
			Double y2 = y + gamma;
			
			finalCoordinate.add(x2);
			finalCoordinate.add(y2);
			
			finalCoordinates.add(finalCoordinate);
		}*/
		
		return finalCoordinates;
	}
 
 public List<List<Double>> getMovedCoordinatesAndAdapt(List<List<Double>> coordinatesList,List<List<Double>> line, Double alpha, Double beta){
		
		List<List<Double>> finalCoordinates = new List<List<Double>>();
		
		
		Double gamma = (alpha/2)+(beta/5);
		
		for(int i = 0; i<coordinatesList.size();i++){
			List<Double> coordinates = coordinatesList.get(i);
			
			Double x = coordinates.get(0);
			Double y = coordinates.get(1);
			List<Double> finalCoordinate = new List<Double>();
			
			Double x2 = x + gamma;
			Double y2 = y + gamma;
			
			finalCoordinate.add(x2);
			finalCoordinate.add(y2);
			
			finalCoordinates.add(finalCoordinate);
		}
		
		/*for(List<Double> coordinates : coordinatesList){
			Double x = coordinates.get(0);
			Double y = coordinates.get(1);
			List<Double> finalCoordinate = new List<Double>();
			
			Double x2 = x + gamma;
			Double y2 = y + gamma;
			
			finalCoordinate.add(x2);
			finalCoordinate.add(y2);
			
			finalCoordinates.add(finalCoordinate);
		}*/
		finalCoordinates = stickToLine(finalCoordinates, line);
		return finalCoordinates;
	}

public List<List<Double>> getMovedCoordinatesWithConstantEndings(List<List<Double>> coordinatesList, Double alpha, Double beta){
	
	List<List<Double>> finalCoordinates = new List<List<Double>>();
	
	
	Double gamma = (alpha/2)+(beta/5);
	finalCoordinates.add(coordinatesList.get(0));
	for(int i = 1; i<coordinatesList.size()-1;i++){
		List<Double> coordinates = coordinatesList.get(i);
		Double x = coordinates.get(0);
		Double y = coordinates.get(1);
		List<Double> finalCoordinate = new List<Double>();
		
		Double x2 = x + gamma;
		Double y2 = y + gamma;
		
		finalCoordinate.add(x2);
		finalCoordinate.add(y2);
		
		finalCoordinates.add(finalCoordinate);
	}
	finalCoordinates.add(coordinatesList.get(coordinatesList.size()-1));
	/*for(List<Double> coordinates : coordinatesList){
		Double x = coordinates.get(0);
		Double y = coordinates.get(1);
		List<Double> finalCoordinate = new List<Double>();
		
		Double x2 = x + gamma;
		Double y2 = y + gamma;
		
		finalCoordinate.add(x2);
		finalCoordinate.add(y2);
		
		finalCoordinates.add(finalCoordinate);
	}*/
	
	return finalCoordinates;
}

public List<List<Double>> stickToLine(List<List<Double>> lineToStick, List<List<Double>> referenceLine){
	
	List<List<Double>> stickedLine = new List<List<Double>>();
	
	List<List<Double>> segment1 = getClosiestSegment(referenceLine, lineToStick.get(0));
	List<List<Double>> segment2 = getClosiestSegment(referenceLine, lineToStick.get(lineToStick.size()-1));
	
	stickedLine.add(segment1.get(0));
	for(int i=1;i<lineToStick.size()-1;i++){
		stickedLine.add(lineToStick.get(i));
	}
	stickedLine.add(segment2.get(0));
	
	return stickedLine;
	
}

	public List<List<Double>> listOrderByPoint(Double x,Double y,Integer decimal,List<List<Double>> coordinates){
		
		List<List<Double>> orderedList = new List<List<Double>>();
		int index = 0;
		for(List<Double> point : coordinates){
			
			if(arrondir(x,decimal).equals(arrondir(point.get(0),decimal)) && arrondir(y,decimal).equals(arrondir(point.get(1),decimal))){
				orderedList.addAll(coordinates.subList(index, coordinates.size()-1));
				orderedList.addAll(coordinates.subList(0, index));	
				
				return orderedList;
			}
			index++;
		}
		return orderedList;
		
	}
	
	 static public Double arrondir(Double value, int n) {

	        Double r = (Math.round(value * Math.pow(10, n))) / (Math.pow(10, n));
	        return r;
	    }
	 
	 public List<List<Double>> erosion(List<List<Double>> coordinates,Double coeffX, Double coeffY){
		 
		 List<List<Double>> erodedList = new List<List<Double>>();
		 
		 Double maxX = coordinates.get(0).get(0);
		 Double maxY = coordinates.get(0).get(1);
		 
		 Double lowX = coordinates.get(0).get(0);
		 Double lowY = coordinates.get(0).get(1);
		 
		 for(List<Double> points : coordinates){
			 if(points.get(0) > maxX){
				 maxX = points.get(0);
			 }
			 if(points.get(1) > maxY){
				 maxY = points.get(1);
			 }
			 if(points.get(0) < lowX){
				 lowX = points.get(0);
			 }
			 if(points.get(1) < lowY){
				 lowY = points.get(1);
			 }
		 }
		 
		 Double meanX = (maxX + lowX) / 2;
		 Double meanY = (maxY + lowY) / 2;
		 
		 for(List<Double> point : coordinates){
			 List<Double> newPoint = new List<Double>();
			 
			 if(point.get(0) > meanX){			 
								 
				 if(point.get(1) > meanY){
					 newPoint.add((point.get(0)-coeffX/1.2)+0.0001);
					 newPoint.add((point.get(1)-coeffY/1.3));
				 }else{
					 newPoint.add((point.get(0)-coeffX/1.2)+0.0001);
					 newPoint.add((point.get(1)+coeffY/1.3));
				 }
			 }else{
				
				 if(point.get(1) > meanY){
					 newPoint.add((point.get(0)+coeffX/2)+0.0001);
					 newPoint.add((point.get(1)-coeffY/2));
				 }else{
					 newPoint.add((point.get(0)+coeffX/2)+0.0001);
					 newPoint.add((point.get(1)+coeffY/1.2));
				 }
			 }			 
			 
			 erodedList.add(newPoint);
		 }		 
		 return erodedList;
	 }
	 
	 public List<List<Double>> getSubList(Integer begin, Integer end, List<List<Double>> list){
		 List<List<Double>> subList = new List<List<Double>>();
		 
		 for(int i=begin; i<end; i++){
			 subList.add(list.get(i));
		 }		 
		 return subList;
	 }
	 
	public List<List<Double>> mergeList(List<List<Double>> firstList,List<List<Double>> list){
		 firstList.addAll(list);
		 /*for(List<Double> sousList : list){
			 firstList.add(sousList);
		 }*/
		 return firstList;
	 }
	 

	public List<List<Double>> rightPartOf(List<List<Double>> list1){
		int size = list1.size();
		int halfSize = size/2;
		return getSubList(halfSize,size,list1);
	}
		
	public List<List<Double>> leftPartOf(List<List<Double>> list1){
		int size = list1.size();
		int halfSize = size/2;
		return getSubList(0,halfSize,list1);
	}
	
	public List<List<Double>> graduateMove(Double k1,Double coeff,List<List<Double>> coordinatesList, List<List<Double>> lineToAdapt){
		
		List<List<Double>> finalCoordinates = new List<List<Double>>();
		
		Double k = coeff;
		Double gamma = (k1/2);
		
		for(int i = 0; i<coordinatesList.size();i++){
			List<Double> coordinates = coordinatesList.get(i);
			Double x = coordinates.get(0);
			Double y = coordinates.get(1);
			List<Double> finalCoordinate = new List<Double>();
			
			Double x2 = x + gamma + coeff;
			Double y2 = y + gamma + coeff;
			
			finalCoordinate.add(x2);
			finalCoordinate.add(y2);
			
			finalCoordinates.add(finalCoordinate);
			coeff = coeff +k;
		
		}
		
		
		finalCoordinates = stickToLine(finalCoordinates,lineToAdapt);
		return finalCoordinates;
		
		
	}
	
	public Double getAngleFromCoeff(Double coeff){
		
		return Math.atan(coeff);
	}
	
	public Double getNormaleCoeff(Double coeff){
		
	return -1/coeff;
	}
	
	public List<Double> newPositionWithCoeff(List<Double> point, Double coeff, Double force){
		
		List<Double> newPoint = new List<Double>();
		Double y = coeff * ((point.get(0)+force) - point.get(0)) / point.get(1);
		newPoint.add(point.get(0)+force);
		newPoint.add(y);
		return newPoint;
	}
	
	public Double returnAngleVariation(Double angle1, Double angle2){
		
		Double angleD1 = angle1 * 180/Math.PI;
		Double angleD2 = angle2 * 180/Math.PI;
		
		Double angle = angleD1 - angleD2;
		
		if(angle > 45 && angle < 135){
			return 10.0;
			
		}
		if(angle > 45 && angle < 135 || angle < -45 && angle > -135 || angle > 225 && angle < 315 || angle < -225 && angle > -315){
			return 10.0;
			
		}else{
			return 70.0;
		}
		
		
		
		
	}
}
