/*
Copyright 2006 Jerry Huxtable

Copyright 2009 Martin Davis
 
Copyright 2012 Antoine Gourlay

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */
package org.jproj;

import java.text.DecimalFormat;

/**
 * Stores a the coordinates for a position  
 * defined relative to some {@link CoordinateReferenceSystem}.
 * The coordinate is defined via X, Y, and optional Z ordinates. 
 * Provides utility methods for comparing the ordinates of two positions and
 * for creating positions from Strings/storing positions as strings.
 * <p>
 * The primary use of this class is to represent coordinate
 * values which are to be transformed
 * by a {@link CoordinateTransform}.
 */
public class ProjCoordinate {

        private static final String DECIMAL_FORMAT_PATTERN = "0.0###############";
        // a DecimalFormat is not ThreadSafe, hence the ThreadLocal variable.
        private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<DecimalFormat>() {

                @Override
                protected DecimalFormat initialValue() {
                        return new DecimalFormat(DECIMAL_FORMAT_PATTERN);
                }
        };
        /**
         * The X ordinate for this point. 
         * <p>
         * Note: This member variable
         * can be accessed directly. In the future this direct access should
         * be replaced with getter and setter methods. This will require 
         * refactoring of the Proj4J code base.
         */
        public double x;
        /**
         * The Y ordinate for this point. 
         * <p>
         * Note: This member variable
         * can be accessed directly. In the future this direct access should
         * be replaced with getter and setter methods. This will require 
         * refactoring of the Proj4J code base.
         */
        public double y;
        /**
         * The Z ordinate for this point. 
         * If this variable has the value <tt>Double.NaN</tt>
         * then this coordinate does not have a Z value.
         * <p>
         * Note: This member variable
         * can be accessed directly. In the future this direct access should
         * be replaced with getter and setter methods. This will require 
         * refactoring of the Proj4J code base.
         */
        public double z;

        /**
         * Creates a ProjCoordinate with default ordinate values.
         *
         */
        public ProjCoordinate() {
                this(0.0, 0.0);
        }

        /**
         * Creates a ProjCoordinate with values copied from the given coordinate object.
         * @param pt a ProjCoordinate to copy
         */
        public ProjCoordinate(ProjCoordinate pt) {
                this(pt.x, pt.y, pt.z);
        }

        /**
         * Creates a ProjCoordinate using the provided double parameters.
         * The first double parameter is the x ordinate (or easting), 
         * the second double parameter is the y ordinate (or northing), 
         * and the third double parameter is the z ordinate (elevation or height).
         * 
         * Valid values should be passed for all three (3) double parameters. If
         * you want to create a horizontal-only point without a valid Z value, use
         * the constructor defined in this class that only accepts two (2) double
         * parameters.
         * 
         * @param argX 
         * @param argY 
         * @param argZ 
         * @see #ProjCoordinate(double argX, double argY)
         */
        public ProjCoordinate(double argX, double argY, double argZ) {
                this.x = argX;
                this.y = argY;
                this.z = argZ;
        }

        /**
         * Creates a ProjCoordinate using the provided double parameters.
         * The first double parameter is the x ordinate (or easting), 
         * the second double parameter is the y ordinate (or northing). 
         * This constructor is used to create a "2D" point, so the Z ordinate
         * is automatically set to Double.NaN. 
         * @param argX
         * @param argY  
         */
        public ProjCoordinate(double argX, double argY) {
                this.x = argX;
                this.y = argY;
                this.z = Double.NaN;
        }

        /** 
         * Create a ProjCoordinate by parsing a String in the same format as returned
         * by the toString method defined by this class.
         * 
         * @param argToParse the string to parse
         */
        public ProjCoordinate(String argToParse) {
                // Make sure the String starts with "ProjCoordinate: ".
                if (!argToParse.startsWith("ProjCoordinate: ")) {
                        throw new IllegalArgumentException("The input string was not in the proper format.");
                }

                // 15 characters should cut out "ProjCoordinate: ".
                String chomped = argToParse.substring(16);

                // Get rid of the starting and ending square brackets.

                String withoutFrontBracket = chomped.substring(1);

                // Calc the position of the last bracket.
                int length = withoutFrontBracket.length();
                int positionOfCharBeforeLast = length - 2;
                String withoutBackBracket = withoutFrontBracket.substring(0,
                        positionOfCharBeforeLast);

                // We should be left with just the ordinate values as strings, 
                // separated by spaces. Split them into an array of Strings.
                String[] parts = withoutBackBracket.split(" ");

                // Get number of elements in Array. There should be two (2) elements
                // or three (3) elements.
                // If we don't have an array with two (2) or three (3) elements,
                // then we need to throw an exception.
                if (parts.length != 2 && parts.length != 3) {
                        throw new IllegalArgumentException("The input string was not in the proper format.");
                }

                // Convert strings to doubles.
                this.x = "NaN".equals(parts[0]) ? Double.NaN : Double.parseDouble(parts[0]);
                this.y = "NaN".equals(parts[1]) ? Double.NaN : Double.parseDouble(parts[1]);

                // You might not always have a Z ordinate. If you do, set it.
                if (parts.length == 3) {
                        this.z = "NaN".equals(parts[2]) ? Double.NaN : Double.parseDouble(parts[2]);
                } else {
                        this.z = Double.NaN;
                }
        }

        /**
         * Sets the value of this coordinate to 
         * be equal to the given coordinate's ordinates.
         * 
         * @param p the coordinate to copy
         */
        public void setValue(ProjCoordinate p) {
                this.x = p.x;
                this.y = p.y;
                this.z = p.z;
        }

        /**
         * Returns a boolean indicating if the X ordinate value of the 
         * ProjCoordinate provided as an ordinate is equal to the X ordinate
         * value of this ProjCoordinate. Because we are working with floating
         * point numbers the ordinates are considered equal if the difference
         * between them is less than the specified tolerance.
         * @param argToCompare
         * @param argTolerance 
         * @return  
         */
        public boolean areXOrdinatesEqual(ProjCoordinate argToCompare, double argTolerance) {
                // Subtract the x ordinate values and then see if the difference
                // between them is less than the specified tolerance. If the difference
                // is less, return true.
                return argToCompare.x - this.x <= argTolerance;
        }

        /**
         * Returns a boolean indicating if the Y ordinate value of the 
         * ProjCoordinate provided as an ordinate is equal to the Y ordinate
         * value of this ProjCoordinate. Because we are working with floating
         * point numbers the ordinates are considered equal if the difference
         * between them is less than the specified tolerance.
         * @param argToCompare
         * @param argTolerance
         * @return  
         */
        public boolean areYOrdinatesEqual(ProjCoordinate argToCompare, double argTolerance) {
                // Subtract the y ordinate values and then see if the difference
                // between them is less than the specified tolerance. If the difference
                // is less, return true.
                return argToCompare.y - this.y <= argTolerance;
        }

        /**
         * Returns a boolean indicating if the Z ordinate value of the 
         * ProjCoordinate provided as an ordinate is equal to the Z ordinate
         * value of this ProjCoordinate. Because we are working with floating
         * point numbers the ordinates are considered equal if the difference
         * between them is less than the specified tolerance.
         * 
         * If both Z ordinate values are Double.NaN this method will return
         * true. If one Z ordinate value is a valid double value and one is
         * Double.Nan, this method will return false.
         * @param argToCompare
         * @param argTolerance
         * @return  
         */
        public boolean areZOrdinatesEqual(ProjCoordinate argToCompare, double argTolerance) {
                // We have to handle Double.NaN values here, because not every
                // ProjCoordinate will have a valid Z Value.
                if (Double.isNaN(z)) {
                        return Double.isNaN(argToCompare.z);
                        // if true, both the z ordinate values are Double.Nan.
                        // else, We've got one z ordinate with a valid value and one with
                        // a Double.NaN value.
                } // We have a valid z ordinate value in this ProjCoordinate object.
                else {
                        if (Double.isNaN(argToCompare.z)) {
                                // We've got one z ordinate with a valid value and one with
                                // a Double.NaN value. Return false.
                                return false;
                        }

                        // If we get to this point in the method execution, we have to
                        // z ordinates with valid values, and we need to do a regular 
                        // comparison. This is done in the remainder of the method.
                }

                // Subtract the z ordinate values and then see if the difference
                // between them is less than the specified tolerance. If the difference
                // is less, return true.
                return argToCompare.z - this.z <= argTolerance;
        }

        /**
         * Returns a string representing the ProjPoint in the format:
         * <tt>ProjCoordinate[X Y Z]</tt>.
         * <p>
         * Example: 
         * <pre>
         *    ProjCoordinate[6241.11 5218.25 12.3]
         * </pre>
         */
        @Override
        public String toString() {
                StringBuilder builder = new StringBuilder();
                builder.append("ProjCoordinate[");
                if (Double.isNaN(x)) {
                        builder.append("NaN");
                } else {
                        builder.append(x);
                }
                builder.append(" ");
                if (Double.isNaN(x)) {
                        builder.append("NaN");
                } else {
                        builder.append(y);
                }
                builder.append(" ");
                if (Double.isNaN(x)) {
                        builder.append("NaN");
                } else {
                        builder.append(z);
                }
                builder.append("]");

                return builder.toString();
        }

        /**
         * Returns a string representing the ProjPoint in the format:
         * <tt>[X Y]</tt> 
         * or <tt>[X, Y, Z]</tt>.
         * Z is not displayed if it is NaN.
         * <p>
         * Example: 
         * <pre>
         * 		[6241.11, 5218.25, 12.3]
         * </pre>
         * @return 
         */
        public String toShortString() {
                StringBuilder builder = new StringBuilder();
                builder.append("[");
                if (Double.isNaN(x)) {
                        builder.append("NaN");
                } else {
                        builder.append(DECIMAL_FORMAT.get().format(x));
                }
                builder.append(", ");
                if (Double.isNaN(y)) {
                        builder.append("NaN");
                } else {
                        builder.append(DECIMAL_FORMAT.get().format(y));
                }
                if (!Double.isNaN(z)) {
                        builder.append(", ");
                        builder.append(this.z);
                }
                builder.append("]");

                return builder.toString();
        }

        public boolean hasValidZOrdinate() {
                return !Double.isNaN(z);
        }

        /**
         * Indicates if this ProjCoordinate has valid X ordinate and Y ordinate
         * values. Values are considered invalid if they are Double.NaN or 
         * positive/negative infinity.
         * @return 
         */
        public boolean hasValidXandYOrdinates() {
                return !Double.isNaN(x) && !Double.isInfinite(x) && !Double.isNaN(y) && !Double.isInfinite(y);
        }
}
