/*
 * 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 java.util.Collection;
import java.util.Set;

import javax.measure.quantity.Angle;
import javax.measure.quantity.Time;
import javax.measure.quantity.Length;
import javax.measure.Quantity;
import javax.measure.Unit;

import javolution.util.FastSet;

import usda.weru.gis.latlong.CoordinatesConverter;
import usda.weru.gis.latlong.Coordinates;

import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.extent.Extent;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.util.InternationalString;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.util.GenericName;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.cs.RangeMeaning;

/**
 * This class represents an arbitrary system of reference for which 
 * {@link Coordinates coordinates} of same significance can be stated.
 * 
 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 3.0, February 13, 2006
 */
public abstract class CoordinateReferenceSystem<C extends Coordinates<?>>
        implements org.opengis.referencing.crs.CoordinateReferenceSystem {

    
    /**
     * This class represents an absolute position (can be be extended)
     */
    protected static class AbsolutePosition {

        /**
         * Holds the Geodetic Latitude (WGS84 Ellipsoid).
         */
        public Quantity<Angle> latitudeWGS84;

        /**
         * Holds the Geodetic Longitude (WGS84 Ellipsoid).
         */
        public Quantity<Angle> longitudeWGS84;

        /**
         * Holds the WGS84 Ellipsoidal Height.
         */
        public Quantity<Length> heightWGS84;

        /**
         * Holds the Time since midnight, January 1, 1970 UTC. 
         */
        public Quantity<Time> timeUTC;
    }

    /**
     * Returns the converter between this coordinate reference system 
     * and the one specified.
     * 
     * @param  that the coordinate reference system to convert to.
     * @return the corresponding coordinates converter.
     * @throws ConversionException if the conversion is not possible
     *         (e.g. geographic to temporal).
     */
    public <T extends Coordinates<?>> CoordinatesConverter<C, T> getConverterTo(
            CoordinateReferenceSystem<T> that) {
        return new GeneralConverter<T>(that);
    }

    // General implementation using absolute position as intermediary.
    private class GeneralConverter<T extends Coordinates<?>> implements
            CoordinatesConverter<C, T> {
        private final CoordinateReferenceSystem<T> _toCRS;

        private GeneralConverter(CoordinateReferenceSystem<T> toCRS) {
            _toCRS = toCRS;
        }

        public T convert(C source) {
            AbsolutePosition position = positionOf(source,
                    new AbsolutePosition());
            return _toCRS.coordinatesOf(position);
        }
    }

    /**
     * Returns the coordinates in this reference system of the specified 
     * absolute position.
     * 
     * @param position the absolute position for which the coordinates  
     *        in this reference system is returned.
     * @return the coordinates for the specified absolute position.
     * @throws ConversionException if a conversion error occurs.
     */
    protected abstract C coordinatesOf(AbsolutePosition position);

    /**
     * Returns the absolute position from the coordinates in
     * this reference system. This update may require information already 
     * supplied by the position. For example, the height for a pressure 
     * altitude might depends upon the latitude/longitude and the time.
     * 
     * @param coordinates the coordinates for which the absolute position 
     *        is adjusted.
     * @param position the position object to update.
     * @return the corresponding absolute position. 
     * @throws ConversionException if a conversion error occurs.
     */
    protected abstract AbsolutePosition positionOf(C coordinates,
            AbsolutePosition position);

    /**
     * Returns the OpenGIS coordinate system associated to this 
     * coordinate reference system.
     * 
     * @return the corresponding coordinate system. 
     */
    public abstract CoordinateSystem getCoordinateSystem();

    /////////////
    // OpenGIS //
    /////////////
    
    /**
     * OpenGIS&reg; - Area for which the (coordinate) reference system is valid.
     *
     * @return coordinate reference system valid area, 
     *         or {@code null} (default) if not available.
     */
    public Extent getValidArea() {
        return null;
    }

    /**
     * OpenGIS&reg; - Description of domain of usage, or limitations of usage,
     * for which this (coordinate) reference system object is valid.
     */
    public InternationalString getScope() {
        throw new UnsupportedOperationException();
    }

    /**
     * OpenGIS&reg; - The primary name by which this object is identified.
     * 
     * @return an identifier holding the class name.
     */
    public ReferenceIdentifier getName() {
        throw new UnsupportedOperationException();
        //return new Name(CoordinateReferenceSystem.this.getClass().getName());
    }

    /**
     * OpenGIS&reg; - An alternative name by which this object is identified.
     *
     * @return The aliases, or an empty collection if there is none.
     */
    public Collection<GenericName> getAlias() {
        throw new UnsupportedOperationException();
        //return EMPTY_SET;
    }

    /**
     * OpenGIS&reg;  - An identifier which references elsewhere the object's defining information.
     * Alternatively an identifier by which this object can be referenced.
     *
     * @return This object identifiers, or an empty set if there is none.
     */
    public Set<ReferenceIdentifier> getIdentifiers() {
        throw new UnsupportedOperationException();
        //return EMPTY_SET;
    }

    /**
     * OpenGIS&reg; - Comments on or information about this object, including 
     * data source information.
     * 
     * @return <code>null</code> (default).
     */
    public InternationalString getRemarks() {
        return null;
    }

    /**
     * OpenGIS&reg; - Returns a <cite>Well Known Text</cite> (WKT)</A> for 
     * this object. This operation may fails if an object is too complex for
     * the WKT format capability (for example an engineering CRS} with different
     * unit for each axis).
     *
     * @return The Well Know Text for this object.
     * @throws UnsupportedOperationException If this object can't be formatted 
     *         as WKT (default).
     */
    public String toWKT() throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    // Default coordinates axis.
    static class Axis implements CoordinateSystemAxis {

        private final Name _name;

        private final String _abbreviation;

        private final Unit<?> _unit;

        private final AxisDirection _direction;

        public Axis(final String name, String abbreviation, Unit<?> unit,
                AxisDirection direction) {
            _name = new Name(name);
            _abbreviation = abbreviation;
            _unit = unit;
            _direction = direction;
        }

        public final ReferenceIdentifier getName() {
            return _name;
        }

        public final String getAbbreviation() {
            return _abbreviation;
        }

        public final Unit<?> getUnit() {
            return _unit;
        }

        public final AxisDirection getDirection() {
            return _direction;
        }

        public Collection<GenericName> getAlias() {
            throw new UnsupportedOperationException();
            //return EMPTY_SET;
        }

        public Set<ReferenceIdentifier> getIdentifiers() {
            throw new UnsupportedOperationException();
            //return EMPTY_SET;
        }

        public InternationalString getRemarks() {
            throw new UnsupportedOperationException();
        }

        public String toWKT() throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }
        
        public RangeMeaning getRangeMeaning() {
            throw new UnsupportedOperationException();
        }

        public double getMinimumValue() {
            throw new UnsupportedOperationException();
        }
        
        public double getMaximumValue() {
            throw new UnsupportedOperationException();
        }
    }

    // Default coordinates axis.
    //Used to implement Identifier, it should now implement ReferenceIdentifier instead
    static class Name implements ReferenceIdentifier {
        final String _value;

        public Name(String value) {
            _value = value;
        }

        public String getCode() {
            return _value;
        }
        
        //Newer version of getCode() that was introduced with the move to ReferenceIdentifier
        public String getCodeSpace() {
            return _value;
        }

        public Citation getAuthority() {
            throw new UnsupportedOperationException();
        }

        public String getVersion() {
            throw new UnsupportedOperationException();
        }
    }

    static final FastSet<Object> EMPTY_SET = new FastSet<Object>();
}