/*
 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
 * Copyright (C) 2006 - JScience (http://jscience.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */

/*
 * This class has been adapted from JScience to use 
 * JSR-363 and newer versions of opengis by Daryn 
 * Butler, Engineering Technician at the United States
 * Department of Agriculture, 2019
 */

package usda.weru.gis.latlong;

import tec.uom.se.quantity.Quantities;
import javax.measure.UnitConverter;
import javax.measure.quantity.Angle;
import systems.uom.common.USCustomary; //DEGREE_ANGLE
//import si.uom.NonSI; //DEGREE_ANGLE?
import si.uom.SI; //.RADIAN
import javax.measure.Unit;

import javolution.context.ObjectFactory;
import javolution.xml.XMLFormat;
import javolution.xml.stream.XMLStreamException;

import usda.weru.gis.latlong.GeographicCRS;

import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.metadata.extent.Extent;
import org.opengis.geometry.DirectPosition;

/**
 * This class represents the {@link GeographicCRS geographic} latitude/longitude
 * coordinates onto the WGS84 ellipsoid.
 * 
 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 3.0, February 13, 2006
 */
public final class LatLong extends Coordinates<GeographicCRS<?>> {

    /**
     * Holds the coordinate reference system for all instances of this class. 
     */
    public static final GeographicCRS<LatLong> CRS = new GeographicCRS<LatLong>() {

        @Override
        public Extent getDomainOfValidity() {
            throw new UnsupportedOperationException();
        }
        
        @Override
        protected LatLong coordinatesOf(AbsolutePosition position) {
            return LatLong.valueOf(position.latitudeWGS84.to(USCustomary.DEGREE_ANGLE).getValue().doubleValue(),
                    position.longitudeWGS84.to(USCustomary.DEGREE_ANGLE).getValue().doubleValue(), USCustomary.DEGREE_ANGLE);
        }

        @Override
        protected AbsolutePosition positionOf(LatLong coordinates,
                AbsolutePosition position) {
            position.latitudeWGS84 = Quantities.getQuantity(coordinates._latitude,
                    USCustomary.DEGREE_ANGLE);
            position.longitudeWGS84 = Quantities.getQuantity(coordinates._longitude,
                    USCustomary.DEGREE_ANGLE);
            return position;
        }

        @Override
        public CoordinateSystem getCoordinateSystem() {
            return GeographicCRS.LATITUDE_LONGITUDE_CS;
        }

    };

    /**
     * Holds converter from degree to radian. 
     */
    //private static final UnitConverter DEGREE_TO_RADIAN = USCustomary.DEGREE_ANGLE
            //.getConverterTo(SI.RADIAN);

    /**
     * Holds converter from radian to degree. 
     */
    //private static final UnitConverter RADIAN_TO_DEGREE = DEGREE_TO_RADIAN
            //.inverse();

    /**
     * Holds the latitude in degrees. 
     */
    private double _latitude;

    /**
     * Holds the longitude in degrees. 
     */
    private double _longitude;

// MEH I think these are backwards
//    public static Unit<Angle> radToDeg(Unit<Angle> rad) {
//        return rad.multiply(2.0).multiply(Math.PI).divide(360.0);
//    }
//    
//    public static Unit<Angle> degToRad(Unit<Angle> deg) {
//        return deg.multiply(360.0).divide(Math.PI).divide(2.0);
//    }
    
    public static double radToDegNum(double rad) {
//        double deg = rad * 2.0;
//        deg *= Math.PI;
//        deg /= 360.0;
//        return deg;
        double deg = rad * 360;
        deg /= Math.PI;
        deg /= 2.0;
        return deg;
    }
    
    public static double degToRadNum(double deg) {
//        double rad = deg * 360;
//        rad /= Math.PI;
//        rad /= 2.0;
//        return rad;
        double rad = deg * 2.0;
        rad *= Math.PI;
        rad /= 360.0;
        return rad;
    }
    /**
     * Returns the surface position corresponding to the specified coordinates.
     * 
     * @param latitude the latitude value stated in the specified unit.
     * @param longitude the longitude value stated in the specified unit.
     * @param unit the angle unit in which the coordinates are stated
     *        ({@link javax.measure.unit.NonSI#DEGREE_ANGLE Degree} typically).
     * @return the corresponding surface position.
     */
    public static LatLong valueOf(double latitude, double longitude,
            Unit<Angle> unit) {
        LatLong latLong = FACTORY.object();
        if (unit.equals(USCustomary.DEGREE_ANGLE)) {
            //System.err.println("It's a degree");
            latLong._latitude = latitude;
            latLong._longitude = longitude;
        } else if (unit.equals(SI.RADIAN)) {
            System.err.println("It's a radian");
//            latLong._latitude = RADIAN_TO_DEGREE.convert(latitude);
//            latLong._longitude = RADIAN_TO_DEGREE.convert(longitude);
            latLong._latitude = radToDegNum(latitude);
            latLong._longitude = radToDegNum(longitude);
        } else { // Other angle unit.
            System.err.println("It's something else");
            UnitConverter toDegree = unit.getConverterTo(USCustomary.DEGREE_ANGLE);
            latLong._latitude = toDegree.convert(latitude);
            latLong._longitude = toDegree.convert(longitude);
        }
        return latLong;
    }

    private static final ObjectFactory<LatLong> FACTORY = new ObjectFactory<LatLong>() {

        @Override
        protected LatLong create() {
            return new LatLong();
        }
    };

    private LatLong() {
    }

    /**
     * Returns the latitude value as <code>double</code>
     * 
     * @param unit the angle unit of the latitude to return.
     * @return the latitude stated in the specified unit.
     */
    public final double latitudeValue(Unit<Angle> unit) {
        return (unit == USCustomary.DEGREE_ANGLE) ? _latitude
//                : (unit == SI.RADIAN) ? DEGREE_TO_RADIAN.convert(_latitude)
                  : (unit == SI.RADIAN) ? degToRadNum(_latitude)
                        : USCustomary.DEGREE_ANGLE.getConverterTo(unit).convert(_latitude);
    }

    /**
     * Returns the longitude value as <code>double</code>
     * 
     * @param unit the angle unit of the longitude to return.
     * @return the longitude stated in the specified unit.
     */
    public final double longitudeValue(Unit<Angle> unit) {
        return (unit == USCustomary.DEGREE_ANGLE) ? _longitude
//                : (unit == SI.RADIAN) ? DEGREE_TO_RADIAN.convert(_longitude)
                  : (unit == SI.RADIAN) ? degToRadNum(_longitude)
                        : USCustomary.DEGREE_ANGLE.getConverterTo(unit).convert(_longitude);
    }

    @Override
    public GeographicCRS<LatLong> getCoordinateReferenceSystem() {
        return CRS;
    }

    // OpenGIS Interface.
    @Override
    public int getDimension() {
        return 2;
    }

    // OpenGIS Interface.
    @Override
    public double getOrdinate(int dimension) throws IndexOutOfBoundsException {
        Unit<Angle> u;
        switch(dimension) {
            //An alternative to asType(Angle.class) might be casting (Unit<Angle>)u in the returns. We'll see if this works
            case 0:
                u = GeographicCRS.LATITUDE_LONGITUDE_CS.getAxis(0).getUnit().asType(Angle.class);
                return USCustomary.DEGREE_ANGLE.getConverterTo(u).convert(_latitude);
            case 1:
                u = GeographicCRS.LATITUDE_LONGITUDE_CS.getAxis(1).getUnit().asType(Angle.class);
                return USCustomary.DEGREE_ANGLE.getConverterTo(u).convert(_longitude);
            default:
                throw new IndexOutOfBoundsException();
        }
    }

    // Implements Realtime.
    @Override
    public LatLong copy() {
        return LatLong.valueOf(_latitude, _longitude, USCustomary.DEGREE_ANGLE);
    }

    // Default serialization.
    //

    /**
     * An naming update to the getCoordinates method; will not be used in our codebase
     * 
     * OpenGIS&reg; - Returns the sequence of numbers that hold the coordinate 
     * of this position in its reference system.
     * 
     * @return a copy of the coordinates. Changes in the returned array will 
     *         not be reflected back in this {@code DirectPosition} object.
     */
    @Override
    public final double[] getCoordinate() {
        double[] coordinates = new double[getDimension()];
        for (int i = 0; i < coordinates.length; i++) {
            coordinates[i] = getOrdinate(i);
        }
        return coordinates;
    }
    
    @Override
    public DirectPosition getDirectPosition() {
        throw new UnsupportedOperationException();
    }
    
    static final XMLFormat<LatLong> XML = new XMLFormat<LatLong>(LatLong.class) {

        @Override
        public LatLong newInstance(Class<LatLong> cls, InputElement xml)
                throws XMLStreamException {
            return FACTORY.object();
        }

        public void write(LatLong latLong, OutputElement xml)
                throws XMLStreamException {
            xml.setAttribute("latitude", latLong._latitude);
            xml.setAttribute("longitude", latLong._longitude);
        }

        public void read(InputElement xml, LatLong latLong)
                throws XMLStreamException {
            latLong._latitude = xml.getAttribute("latitude", 0.0);
            latLong._longitude = xml.getAttribute("longitude", 0.0);
        }
    };

    private static final long serialVersionUID = 1L;
}