package routines;

/*
 * user specification: the function's comment should contain keys as follows: 1. write about the function's comment.but
 * it must be before the "{talendTypes}" key.
 * 
 * 2. {talendTypes} 's value must be talend Type, it is required . its value should be one of: String, char | Character,
 * long | Long, int | Integer, boolean | Boolean, byte | Byte, Date, double | Double, float | Float, Object, short |
 * Short
 * 
 * 3. {Category} define a category for the Function. it is required. its value is user-defined .
 * 
 * 4. {param} 's format is: {param} <type>[(<default value or closed list values>)] <name>[ : <comment>]
 * 
 * <type> 's value should be one of: string, int, list, double, object, boolean, long, char, date. <name>'s value is the
 * Function's parameter name. the {param} is optional. so if you the Function without the parameters. the {param} don't
 * added. you can have many parameters for the Function.
 * 
 * 5. {example} gives a example for the Function. it is optional.
 */
public class GeoUtil {

private static LcagLogger log = LcagLogger.getLogger(LcagDateTime.class);
	
	public static class GeoCoord {
		public static char NOT_INITIALIZED = '-';
		public int degrees;
		public int minutes;
		public double seconds;
		public char direction;
		
		private GeoCoord() { direction = NOT_INITIALIZED; }
		
		@Override
		public String toString() {
			GeoCoord coord = this.roundToSeconds();
			String result = "";

			if (coord.direction == 'E' || coord.direction == 'W') {
				result = String.format("%03d.%02d.%02.0f%c", coord.degrees, coord.minutes, coord.seconds, coord.direction);
			} else if (coord.direction == 'N' || coord.direction == 'S') {
				result = String.format("%02d.%02d.%02.0f%c", coord.degrees, coord.minutes, coord.seconds, coord.direction);				
			}
	    	return result;
			
		}
		
		public GeoCoord(Integer degrees, Integer minutes, Double seconds, String direction) {
			this.direction = NOT_INITIALIZED;
			
			if (degrees != null && minutes != null && seconds != null && "EWNS".contains(direction)) {
				this.degrees = degrees;
				this.minutes = minutes;
				this.seconds = seconds;
				this.direction = direction.substring(0,1).toUpperCase().charAt(0);
			}
		}
		
		public GeoCoord(double aValue, boolean isLongitude) {
			double f = Math.abs(aValue);
			this.degrees = (int)Math.floor(f);
			this.minutes = (int)Math.floor( (f * 60.0f) % 60.0f);
			this.seconds = (f * 3600.0f) % 60.0f;
			
			if (isLongitude) {
				this.direction = (aValue < 0) ? 'W' : 'E';
			} else {
				this.direction = (aValue < 0) ? 'S' : 'N';				
			}
		}
		
		public GeoCoord(String aCoord) {
			if (aCoord == null)
					throw new IllegalArgumentException("null argument not allowed in GeoCoord constructor");
			
			int len = aCoord.length();
			String[] dms = aCoord.substring(0,len-1).split("[\\.,]");
						
			this.degrees = Integer.parseInt(dms[0]);
			this.minutes = Integer.parseInt(dms[1]);
			this.seconds = (dms.length > 2) ? Double.parseDouble(dms[2]) : 0f;
			
			this.direction = aCoord.substring(len - 1).toUpperCase().charAt(0);
			
			if ("EWSN".indexOf(this.direction) < 0 || this.degrees < 0 || this.minutes < 0 || this.seconds < 0) {
				throw new IllegalArgumentException(String.format("geo coordinate '%s'is not well-formed", aCoord));
			}
		}
		

		public Double toDecimal() {
			Double result = null; 
			
			if (this.direction != NOT_INITIALIZED) {
				double res = new Double(this.degrees + this.minutes/60.0 + this.seconds / 3600.0);
			
				switch(this.direction) {
				case 'S':
				case 'W':
					res = -res;
					break;
				case 'E':
				case 'N':
					break;
				default: throw new IllegalStateException("Ill geo direction: " + this.direction);
				}
				
				result = new Double(res);
			}

			return result;
		}

		@Override
		public GeoCoord clone() {
			GeoCoord result = new GeoCoord();

			result.seconds = this.seconds;
			result.minutes = this.minutes;
			result.degrees = this.degrees;
			result.direction = this.direction;
			
			return result;
		}

		/**
		 * @return new Object where seconds are rounded up to full seconds
		 */
		public GeoCoord roundToSeconds() {
			GeoCoord result = this.clone();
			
			result.seconds = Math.round(this.seconds);

			if (result.seconds == 60f) {
				result.seconds = 0f;
				
				result.minutes += 1;
				if (result.minutes == 60) {
					result.minutes = 0;
					result.degrees += 1;
				}
			}
			return result;
		}
		
	}
	
  	
    /**
     * formatCoordinate: Formats a geo-location to DDD.MM.SSd where d is one of the directions 'E', 'W', 'N', 'S'  
     * 
     * @param	aCoord - the coordinate to format (may be a longitude or a latitude)
     *  
     * @return  standard formatted geo position as String
     *  
     * {talendTypes} String
     * 
     * {Category} LCAG
     * 
     * {param} String(aCoord) aCoord:  the coordinate to format (may be a longitude or a latitude)
     * 
     */    
	public static String formatCoordinate(String aCoord) 
    {
		String result = null;
		
		if (aCoord != null) {
	    	GeoCoord coord = new GeoCoord(aCoord);   	
	    	result = coord.toString();
		}
		
    	return result;
    }

    /**
     * formatLongitude: Formats a geo-location to DDD.MM.SSd where d is one of the directions 'E', 'W', 'N', 'S'  
     * 
     * @param	aCoord - the coordinate decimal value to format
     *  
     * @return  standard formatted geo position as String
     *  
     * {talendTypes} String
     * 
     * {Category} LCAG
     * 
     * {param} Double(aCoord) aCoord:  the coordinate decimal value to format
     * 
     */    
	public static String formatLongitude(Double aCoord) 
    {
		String result = null;
		
		if (aCoord != null) {
			GeoCoord coord = new GeoCoord(aCoord, true);   	
			result = coord.toString();
		}
		
    	return result;
    }

    /**
     * formatLatitude: Formats a geo-location to DDD.MM.SSd where d is one of the directions 'E', 'W', 'N', 'S'  
     * 
     * @param	aCoord - the coordinate decimal value to format
     *  
     * @return  standard formatted geo position as String
     *  
     * {talendTypes} String
     * 
     * {Category} LCAG
     * 
     * {param} Double(aCoord) aCoord:  the coordinate decimal value to format
     * 
     */    
	public static String formatLatitude(Double aCoord) 
    {
		String result = null;
		
		if (aCoord != null) {
	    	GeoCoord coord = new GeoCoord(aCoord, false);   	
	    	result = coord.toString();
		}
	    	
    	return result;
    }
	
	   /**
     * formatCoordinate: Formats a geo-location in MaD format to DDD.MM.SSd where d is one of the directions 'E', 'W', 'N', 'S'  
     * 
     * @param	deg - degrees
     * @param	min - minutes
     * @param	direction - any direction 'E', 'W', 'N', 'S'
     *  
     * @return  standard formatted geo position as String
     *  
     * {talendTypes} String
     * 
     * {Category} LCAG
     * 
     * {param} Integer(deg) deg:  degrees
     * 
     */    
	public static String formatCoordinate(Integer deg, Integer min, String direction) 
    {
		String result = null;
		
		if (deg != null && min != null && direction != null) {
			GeoCoord coord = new GeoCoord(deg.intValue(), min.intValue(), 0.0, direction);   	
			result = coord.toString();
		}
		
    	return result;
    }

}
