/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2001-2002 Vivid Solutions 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)
 *
 **********************************************************************/

#include <geos/geom/PrecisionModel.h>
#include <geos/geom/Coordinate.h>
#include <geos/util/IllegalArgumentException.h>
#include <geos/util/math.h>
#include <geos/util.h>

#include <sstream>
#include <string>
#include <cmath>
#include <iostream>

#ifndef GEOS_INLINE
# include <geos/geom/PrecisionModel.inl>
#endif

#ifndef GEOS_DEBUG
#define GEOS_DEBUG 0
#endif

using namespace std;

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

const double PrecisionModel::maximumPreciseValue=9007199254740992.0;

/*public*/
double
PrecisionModel::makePrecise(double val) const
{
#if GEOS_DEBUG
    cerr<<"PrecisionModel["<<this<<"]::makePrecise called"<<endl;
#endif

    if (modelType==FLOATING_SINGLE) {
        float floatSingleVal = static_cast<float>(val);
        return static_cast<double>(floatSingleVal);
    }
    if (modelType == FIXED) {
        // Use whatever happens to be the default rounding method
        const double ret = util::round(val*scale)/scale;
        return ret;
    }
    // modelType == FLOATING - no rounding necessary
    return val;
}

/*public*/
void
PrecisionModel::makePrecise(Coordinate& coord) const
{
	// optimization for full precision
	if (modelType==FLOATING) return;

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


/*public*/
PrecisionModel::PrecisionModel()
	:
	modelType(FLOATING),
	scale(0.0)
{
#if GEOS_DEBUG
	cerr<<"PrecisionModel["<<this<<"] ctor()"<<endl;
#endif
	//modelType=FLOATING;
	//scale=1.0;
}

/*public*/
PrecisionModel::PrecisionModel(Type nModelType)
	:
	modelType(nModelType),
	scale(1.0)
{
#if GEOS_DEBUG
	cerr<<"PrecisionModel["<<this<<"] ctor(Type)"<<endl;
#endif
	//modelType=nModelType;
	//if (modelType==FIXED) setScale(1.0);
	//else setScale(666); // arbitrary number for invariant testing
}


/*public (deprecated) */
PrecisionModel::PrecisionModel(double newScale, double newOffsetX, double newOffsetY)
		//throw(IllegalArgumentException *)
	:
	modelType(FIXED)
{
    ::geos::ignore_unused_variable_warning(newOffsetX);
    ::geos::ignore_unused_variable_warning(newOffsetY);

#if GEOS_DEBUG
	cerr<<"PrecisionModel["<<this<<"] ctor(scale,offsets)"<<endl;
#endif

	//modelType = FIXED;
	setScale(newScale);
}

/*public*/
PrecisionModel::PrecisionModel(double newScale)
		//throw (IllegalArgumentException *)
	:
	modelType(FIXED)
{
#if GEOS_DEBUG
	cerr<<"PrecisionModel["<<this<<"] ctor(scale)"<<endl;
#endif
	setScale(newScale);
}


PrecisionModel::PrecisionModel(const PrecisionModel &pm)
	:
	modelType(pm.modelType),
	scale(pm.scale)
{
#if GEOS_DEBUG
	cerr<<"PrecisionModel["<<this<<"] ctor(pm["<< &pm <<"])"<<endl;
#endif
}

/*public*/
bool
PrecisionModel::isFloating() const
{
	return (modelType == FLOATING || modelType == FLOATING_SINGLE);
}

/*public*/
int
PrecisionModel::getMaximumSignificantDigits() const
{
	int maxSigDigits = 16;
	if (modelType == FLOATING) {
		maxSigDigits = 16;
	} else if (modelType == FLOATING_SINGLE) {
		maxSigDigits = 6;
	} else if (modelType == FIXED) {

		double dgtsd = std::log(getScale()) / std::log(double(10.0));
		const int dgts = static_cast<int>(
			dgtsd > 0 ? std::ceil(dgtsd)
			          : std::floor(dgtsd)
		);
		maxSigDigits = dgts;
	}
	return maxSigDigits;
}


/*private*/
void
PrecisionModel::setScale(double newScale)
		// throw IllegalArgumentException
{
	if ( newScale <= 0 )
		throw util::IllegalArgumentException("PrecisionModel scale cannot be 0");
    scale = std::fabs(newScale);
}

/*public*/
double
PrecisionModel::getOffsetX() const
{
	return 0;
}

/*public*/
double
PrecisionModel::getOffsetY() const
{
	return 0;
}


string
PrecisionModel::toString() const
{
	ostringstream s;
  	if (modelType == FLOATING) {
  		s<<"Floating";
  	} else if (modelType == FLOATING_SINGLE) {
  		s<<"Floating-Single";
  	} else if (modelType == FIXED) {
		s <<"Fixed (Scale=" << getScale()
		  << " OffsetX=" << getOffsetX()
		  << " OffsetY=" << getOffsetY()
		  << ")";
	} else {
		s<<"UNKNOWN";
	}
	return s.str();
}

bool operator==(const PrecisionModel& a, const PrecisionModel& b) {
	return a.isFloating() == b.isFloating() &&
			a.getScale() == b.getScale();
}

/*public*/
int
PrecisionModel::compareTo(const PrecisionModel *other) const
{
	int sigDigits=getMaximumSignificantDigits();
	int otherSigDigits=other->getMaximumSignificantDigits();
	return sigDigits<otherSigDigits? -1: (sigDigits==otherSigDigits? 0:1);
}

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