/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2006 Refractions Research Inc.
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************
 *
 * Last port: geom/PrecisionModel.java r378 (JTS-1.12)
 *
 **********************************************************************/

#pragma once

#include <geos/geom/Coordinate.h>
#include <geos/export.h>

#include <cassert>
#include <string>

// Forward declarations
namespace geos {
namespace io {
class Unload;
}
namespace geom {
class Coordinate;
}
}

namespace geos {
namespace geom { // geos::geom

/**
 * \class PrecisionModel geom.h geos.h
 *
 * \brief Specifies the precision model of the Coordinate in a Geometry.
 *
 * In other words, specifies the grid of allowable points for a <code>Geometry</code>.
 * A precision model may be <b>floating</b> (PrecisionModel::Type::FLOATING or
 * PrecisionModel::Type::FLOATING_SINGLE), in which case normal floating-point value semantics apply.
 *
 * For a PrecisionModel::Type::FIXED precision model the
 * makePrecise(geom::Coordinate) method allows rounding a coordinate to
 * a "precise" value; that is, one whose precision is known exactly.
 *
 * Coordinates are assumed to be precise in geometries.
 * That is, the coordinates are assumed to be rounded to the
 * precision model given for the geometry.
 * All internal operations
 * assume that coordinates are rounded to the precision model.
 * Constructive methods (such as boolean operations) always round computed
 * coordinates to the appropriate precision model.
 *
 * Three types of precision model are supported:
 * - FLOATING - represents full double precision floating point.
 *   This is the default precision model used in JTS
 * - FLOATING_SINGLE - represents single precision floating point.
 * - FIXED - represents a model with a fixed number of decimal places.
 *   A Fixed Precision Model is specified by a scale factor.
 *   The scale factor specifies the grid which numbers are rounded to.
 *   Input coordinates are mapped to fixed coordinates according to the
 *   following equations:
 *   - jtsPt.x = round( inputPt.x * scale ) / scale
 *   - jtsPt.y = round( inputPt.y * scale ) / scale
 *
 * For example, to specify 3 decimal places of precision, use a scale factor
 * of 1000. To specify -3 decimal places of precision (i.e. rounding to
 * the nearest 1000), use a scale factor of 0.001.
 *
 * It is also supported to specify a precise grid size
 * by providing it as a negative scale factor.
 * For example, to specify rounding to the nearest 1000 use a scale factor of -1000.
 *
 * Coordinates are represented internally as Java double-precision values.
 * Java uses the IEEE-394 floating point standard, which
 * provides 53 bits of precision. (Thus the maximum precisely representable
 * integer is 9,007,199,254,740,992).
 *
 */
class GEOS_DLL PrecisionModel {
    friend class io::Unload;

public:

    /// The types of Precision Model which GEOS supports.
    typedef enum {

        /**
         * Fixed Precision indicates that coordinates have a fixed
         * number of decimal places.
         * The number of decimal places is determined by the log10
         * of the scale factor.
         */
        FIXED,

        /**
         * Floating precision corresponds to the standard Java
         * double-precision floating-point representation, which is
         * based on the IEEE-754 standard
         */
        FLOATING,

        /**
         * Floating single precision corresponds to the standard Java
         * single-precision floating-point representation, which is
         * based on the IEEE-754 standard
         */
        FLOATING_SINGLE

    } Type;

    /// Creates a PrecisionModel with a default precision of FLOATING.
    PrecisionModel(void);

    /// Creates a PrecisionModel specifying an explicit precision model type.
    ///
    /// If the model type is FIXED the scale factor will default to 1.
    ///
    /// @param nModelType the type of the precision model
    ///
    PrecisionModel(Type nModelType);

    /** \brief
     * Creates a <code>PrecisionModel</code> with Fixed precision.
     *
     * Fixed-precision coordinates are represented as precise internal
     * coordinates, which are rounded to the grid defined by the
     * scale factor.
     *
     * @param  newScale  amount by which to multiply a coordinate after
     *                   subtracting the offset, to obtain a precise coordinate
     * @param  newOffsetX  not used.
     * @param  newOffsetY  not used.
     *
     * @deprecated offsets are no longer supported, since internal
     * representation is rounded floating point
     */
    PrecisionModel(double newScale, double newOffsetX, double newOffsetY);

    /**
     * \brief
     * Creates a PrecisionModel with Fixed precision.
     *
     * Fixed-precision coordinates are represented as precise
     * internal coordinates which are rounded to the grid defined
     * by the scale factor.
     * The provided scale may be negative, to specify an exact grid size.
     * The scale is then computed as the reciprocal.
     *
     * @param newScale amount by which to multiply a coordinate
     * after subtracting the offset, to obtain a precise coordinate. Must be non-zero.
     */
    PrecisionModel(double newScale);

    /// The maximum precise value representable in a double.
    ///
    /// Since IEE754 double-precision numbers allow 53 bits of mantissa,
    /// the value is equal to 2^53 - 1.
    /// This provides <i>almost</i> 16 decimal digits of precision.
    ////
    static const double maximumPreciseValue;

    /** \brief
     * Rounds a numeric value to the PrecisionModel grid.
     *
     * Asymmetric Arithmetic Rounding is used, to provide
     * uniform rounding behaviour no matter where the number is
     * on the number line.
     *
     * <b>Note:</b> Java's <code>Math#rint</code> uses the "Banker's Rounding" algorithm,
     * which is not suitable for precision operations elsewhere in JTS.
     */
    double makePrecise(double val) const;

    /// Rounds the given Coordinate to the PrecisionModel grid.
    void makePrecise(CoordinateXY& coord) const
    {
        // optimization for full precision
        if(modelType == FLOATING) {
            return;
        }

        coord.x = makePrecise(coord.x);
        coord.y = makePrecise(coord.y);
    };

    void makePrecise(CoordinateXY* coord) const
    {
        assert(coord);
        return makePrecise(*coord);
    };

    /// Tests whether the precision model supports floating point
    ///
    /// @return <code>true</code> if the precision model supports
    /// floating point
    ///
    bool isFloating() const;

    /// \brief
    /// Returns the maximum number of significant digits provided by
    /// this precision model.
    ///
    /// Intended for use by routines which need to print out precise
    /// values.
    ///
    /// @return the maximum number of decimal places provided by this
    /// precision model
    ///
    int getMaximumSignificantDigits() const;

    /// Gets the type of this PrecisionModel
    ///
    /// @return the type of this PrecisionModel
    ///
    Type getType() const
    {
        return modelType;
    };

    /// Returns the multiplying factor used to obtain a precise coordinate.
    double getScale() const
    {
        assert(!(scale < 0));
        return scale;
    };

    /**
    * Computes the grid size for a fixed precision model.
    * This is equal to the reciprocal of the scale factor.
    * If the grid size has been set explicitly (via a negative scale factor)
    * it will be returned.
    *
    * @return the grid size at a fixed precision scale.
    */
    double getGridSize() const
    {
        if (isFloating())
           return DoubleNotANumber;

        if (gridSize != 0)
            return gridSize;

        return 1.0 / scale;
    };

    /// Returns the x-offset used to obtain a precise coordinate.
    ///
    /// @return the amount by which to subtract the x-coordinate before
    ///         multiplying by the scale
    /// @deprecated Offsets are no longer used
    ///
    double getOffsetX() const;

    /// Returns the y-offset used to obtain a precise coordinate.
    ///
    /// @return the amount by which to subtract the y-coordinate before
    ///         multiplying by the scale
    /// @deprecated Offsets are no longer used
    ///
    double getOffsetY() const;

    /*
     *  Sets ´internal` to the precise representation of `external`.
     *
     * @param external the original coordinate
     * @param internal the coordinate whose values will be changed to the
     *                 precise representation of <code>external</code>
     * @deprecated use makePrecise instead
     */
    //void toInternal(const Coordinate& external, Coordinate* internal) const;

    /*
     *  Returns the precise representation of <code>external</code>.
     *
     *@param  external  the original coordinate
     *@return
     *	the coordinate whose values will be changed to the precise
     *	representation of <code>external</code>
     * @deprecated use makePrecise instead
     */
    //Coordinate* toInternal(const Coordinate& external) const;

    /*
     *  Returns the external representation of <code>internal</code>.
     *
     *@param  internal  the original coordinate
     *@return           the coordinate whose values will be changed to the
     *      external representation of <code>internal</code>
     * @deprecated no longer needed, since internal representation is same as external representation
     */
    //Coordinate* toExternal(const Coordinate& internal) const;

    /*
     *  Sets <code>external</code> to the external representation of
     *  <code>internal</code>.
     *
     * @param  internal  the original coordinate
     * @param  external
     *	the coordinate whose values will be changed to the
     *	external representation of <code>internal</code>
     * @deprecated no longer needed, since internal representation is same as external representation
     */
    //void toExternal(const Coordinate& internal, Coordinate* external) const;

    std::string toString() const;

    /// \brief
    /// Compares this PrecisionModel object with the specified object
    /// for order.
    ///
    /// A PrecisionModel is greater than another if it provides greater
    /// precision.
    /// The comparison is based on the value returned by the
    /// getMaximumSignificantDigits method.
    /// This comparison is not strictly accurate when comparing floating
    /// precision models to fixed models;
    /// however, it is correct when both models are either floating or
    /// fixed.
    ///
    /// @param other the PrecisionModel with which this PrecisionModel
    ///      is being compared
    /// @return a negative integer, zero, or a positive integer as this
    ///      PrecisionModel is less than, equal to, or greater than the
    ///      specified PrecisionModel.
    ///
    int compareTo(const PrecisionModel* other) const;

private:

    /** \brief
     * Sets the multiplying factor used to obtain a precise coordinate.
     *
     * This method is private because PrecisionModel is intended to
     * be an immutable (value) type.
     *
     */
    void setScale(double newScale);
    // throw IllegalArgumentException

    /** \brief
     * Snaps a value to nearest integer, if within tolerance.
     */
    static double snapToInt(double val, double tolerance);

    Type modelType;

    /**
    * The scale factor which determines the number of decimal places in fixed precision.
    */
    double scale;

    /**
    * If non-zero, the precise grid size specified.
    * In this case, the scale is also valid and is computed from the grid size.
    * If zero, the scale is used to compute the grid size where needed.
    */
    double gridSize = 0.0;

};

// Equality operator for PrecisionModel, deprecate it ?
//inline bool operator==(const PrecisionModel& a, const PrecisionModel& b);

} // namespace geos::geom
} // namespace geos