/*
 * ScaledIntegerNode.cpp - implementation of public functions of the ScaledIntegerNode class.
 *
 * Original work Copyright 2009 - 2010 Kevin Ackley (kackley@gwi.net)
 * Modified work Copyright 2018 - 2020 Andy Maloney <asmaloney@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/// @file ScaledIntegerNode.cpp

#include "ScaledIntegerNodeImpl.h"
#include "StringFunctions.h"

using namespace e57;

// Put this function first so we can reference the code in doxygen using @skip
/*!
@brief Check whether ScaledIntegerNode class invariant is true
@copydetails IntegerNode::checkInvariant()
*/
void ScaledIntegerNode::checkInvariant( bool /*doRecurse*/, bool doUpcast ) const
{
   // If destImageFile not open, can't test invariant (almost every call would throw)
   if ( !destImageFile().isOpen() )
   {
      return;
   }

   // If requested, call Node::checkInvariant
   if ( doUpcast )
   {
      static_cast<Node>( *this ).checkInvariant( false, false );
   }

   // If value is out of bounds
   if ( rawValue() < minimum() || rawValue() > maximum() )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }

   // If scale is zero
   if ( scale() == 0 )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }

   // If scaled value is not calculated correctly
   if ( scaledValue() != rawValue() * scale() + offset() )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }
}

/*!
@class e57::ScaledIntegerNode

@brief An E57 element encoding a fixed point number.

@details
An ScaledIntegerNode is a terminal node (i.e. having no children) that holds a fixed point number
encoded by an integer @c rawValue, a double precision floating point @c scale, an double precision
floating point @c offset, and integer minimum/maximum bounds.

The @c minimum attribute may be a number in the interval [-2^63, 2^63).

The @c maximum attribute may be a number in the interval [minimum, 2^63).

The @c rawValue may be a number in the interval [minimum, maximum].

The @c scaledValue is a calculated double precision floating point number derived from: scaledValue
= rawValue*scale + offset.

See Node class discussion for discussion of the common functions that StructureNode supports.

@section ScaledIntegerNode_invariant Class Invariant
A class invariant is a list of statements about an object that are always true before and after any
operation on the object. An invariant is useful for testing correct operation of an implementation.
Statements in an invariant can involve only externally visible state, or can refer to internal
implementation-specific state that is not visible to the API user. The following C++ code checks
externally visible state for consistency and throws an exception if the invariant is violated:

@dontinclude ScaledIntegerNode.cpp
@skip void ScaledIntegerNode::checkInvariant
@until ^}

@see Node
*/

/*!
@brief Create an E57 element for storing a fixed point number.

@param [in] destImageFile The ImageFile where the new node will eventually be stored.
@param [in] rawValue The raw integer value of the element.
@param [in] minimum The smallest rawValue that the element may take.
@param [in] maximum The largest rawValue that the element may take.
@param [in] scale The scaling factor used to compute scaledValue from rawValue.
@param [in] offset The offset factor used to compute scaledValue from rawValue.

@details
A ScaledIntegerNode stores an integer value, a lower and upper bound, and two conversion factors.
The ScaledIntegerNode class corresponds to the ASTM E57 standard ScaledInteger element. See the
class discussion at bottom of ScaledIntegerNode page for more details.

The @a destImageFile indicates which ImageFile the ScaledIntegerNode will eventually be attached to.
A node is attached to an ImageFile by adding it underneath the predefined root of the ImageFile
(gotten from ImageFile::root). It is not an error to fail to attach the ScaledIntegerNode to the @a
destImageFile. It is an error to attempt to attach the ScaledIntegerNode to a different ImageFile.

@warning It is an error to give an @a rawValue outside the @a minimum / @a maximum bounds, even if
the ScaledIntegerNode is destined to be used in a CompressedVectorNode prototype (where the @a
rawValue will be ignored). If the ScaledIntegerNode is to be used in a prototype, it is recommended
to specify a @a rawValue = 0 if 0 is within bounds, or a @a rawValue = @a minimum if 0 is not within
bounds.

@pre The @a destImageFile must be open (i.e. destImageFile.isOpen() must be true).
@pre The @a destImageFile must have been opened in write mode (i.e. destImageFile.isWritable() must
be true).
@pre minimum <= rawValue <= maximum
@pre scale != 0

@throw ::ErrorBadAPIArgument
@throw ::ErrorImageFileNotOpen
@throw ::ErrorFileReadOnly
@throw ::ErrorValueOutOfBounds
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::rawValue, Node, CompressedVectorNode, CompressedVectorNode::prototype
*/
ScaledIntegerNode::ScaledIntegerNode( const ImageFile &destImageFile, int64_t rawValue,
                                      int64_t minimum, int64_t maximum, double scale,
                                      double offset ) :
   impl_(
      new ScaledIntegerNodeImpl( destImageFile.impl(), rawValue, minimum, maximum, scale, offset ) )
{
}

ScaledIntegerNode::ScaledIntegerNode( const ImageFile &destImageFile, int rawValue, int64_t minimum,
                                      int64_t maximum, double scale, double offset ) :
   impl_( new ScaledIntegerNodeImpl( destImageFile.impl(), static_cast<int64_t>( rawValue ),
                                     minimum, maximum, scale, offset ) )
{
}

ScaledIntegerNode::ScaledIntegerNode( const ImageFile &destImageFile, int rawValue, int minimum,
                                      int maximum, double scale, double offset ) :
   impl_( new ScaledIntegerNodeImpl( destImageFile.impl(), static_cast<int64_t>( rawValue ),
                                     static_cast<int64_t>( minimum ),
                                     static_cast<int64_t>( maximum ), scale, offset ) )
{
}

/*!
@brief This second constructor creates an E57 element for storing a fixed point number but does the
scaling for you.

@param [in] destImageFile The ImageFile where the new node will eventually be stored.
@param [in] scaledValue The scaled integer value of the element.
@param [in] scaledMinimum The smallest scaledValue that the element may take.
@param [in] scaledMaximum The largest scaledValue that the element may take.
@param [in] scale The scaling factor used to compute scaledValue from rawValue.
@param [in] offset The offset factor used to compute scaledValue from rawValue.

@details
A ScaledIntegerNode stores an integer value, a lower and upper bound, and two conversion factors.
This ScaledIntegerNode constructor calculates the rawValue, minimum, and maximum by doing the
floor((scaledValue - offset)/scale + .5) on each scaled parameters.

@warning It is an error to give an @a rawValue outside the @a minimum / @a maximum bounds, even if
the ScaledIntegerNode is destined to be used in a CompressedVectorNode prototype (where the @a
rawValue will be ignored). If the ScaledIntegerNode is to be used in a prototype, it is recommended
to specify a @a rawValue = 0 if 0 is within bounds, or a @a rawValue = @a minimum if 0 is not within
bounds.

@pre The @a destImageFile must be open (i.e. destImageFile.isOpen() must be true).
@pre The @a destImageFile must have been opened in write mode (i.e. destImageFile.isWritable() must
be true).
@pre scaledMinimum <= scaledValue <= scaledMaximum
@pre scale != 0

@throw ::ErrorBadAPIArgument
@throw ::ErrorImageFileNotOpen
@throw ::ErrorFileReadOnly
@throw ::ErrorValueOutOfBounds
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledValue, Node, CompressedVectorNode, CompressedVectorNode::prototype
*/
ScaledIntegerNode::ScaledIntegerNode( const ImageFile &destImageFile, double scaledValue,
                                      double scaledMinimum, double scaledMaximum, double scale,
                                      double offset ) :
   impl_( new ScaledIntegerNodeImpl( destImageFile.impl(), scaledValue, scaledMinimum,
                                     scaledMaximum, scale, offset ) )
{
}

/*!
@brief Is this a root node.
@copydetails Node::isRoot()
*/
bool ScaledIntegerNode::isRoot() const
{
   return impl_->isRoot();
}

/*!
@brief Return parent of node, or self if a root node.
@copydetails Node::parent()
*/
Node ScaledIntegerNode::parent() const
{
   return Node( impl_->parent() );
}

/*!
@brief Get absolute pathname of node.
@copydetails Node::pathName()
*/
ustring ScaledIntegerNode::pathName() const
{
   return impl_->pathName();
}

/*!
@brief Get elementName string, that identifies the node in its parent.
@copydetails Node::elementName()
*/
ustring ScaledIntegerNode::elementName() const
{
   return impl_->elementName();
}

/*!
@brief Get the ImageFile that was declared as the destination for the node when it was created.
@copydetails Node::destImageFile()
*/
ImageFile ScaledIntegerNode::destImageFile() const
{
   return ImageFile( impl_->destImageFile() );
}

/*!
@brief Has node been attached into the tree of an ImageFile.
@copydetails Node::isAttached()
*/
bool ScaledIntegerNode::isAttached() const
{
   return impl_->isAttached();
}

/*!
@brief Get raw unscaled integer value of element.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The raw unscaled integer value stored.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledValue, ScaledIntegerNode::minimum, ScaledIntegerNode::maximum
*/
int64_t ScaledIntegerNode::rawValue() const
{
   return impl_->rawValue();
}

/*!
@brief Get scaled value of element.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The scaled value (rawValue*scale + offset) calculated from the rawValue stored.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::rawValue
*/
double ScaledIntegerNode::scaledValue() const
{
   return impl_->scaledValue();
}

/*!
@brief Get the declared minimum that the raw value may take.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The declared minimum that the rawValue may take.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::maximum, ScaledIntegerNode::rawValue
*/
int64_t ScaledIntegerNode::minimum() const
{
   return impl_->minimum();
}

/*!
@brief Get the declared scaled minimum that the scaled value may take.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The declared minimum that the rawValue may take.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledMaximum, ScaledIntegerNode::scaledValue
*/
double ScaledIntegerNode::scaledMinimum() const
{
   return impl_->scaledMinimum();
}

/*!
@brief Get the declared maximum that the raw value may take.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The declared maximum that the rawValue may take.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::minimum, ScaledIntegerNode::rawValue
*/
int64_t ScaledIntegerNode::maximum() const
{
   return impl_->maximum();
}

/*!
@brief Get the declared scaled maximum that the scaled value may take.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The declared maximum that the rawValue may take.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledMinimum, ScaledIntegerNode::scaledValue
*/
double ScaledIntegerNode::scaledMaximum() const // Added by SC
{
   return impl_->scaledMaximum();
}

/*!
@brief Get declared scaling factor.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The scaling factor used to compute scaledValue from rawValue.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledValue
*/
double ScaledIntegerNode::scale() const
{
   return impl_->scale();
}

/*!
@brief Get declared offset.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@post No visible state is modified.

@return The offset used to compute scaledValue from rawValue.

@throw ::ErrorImageFileNotOpen
@throw ::ErrorInternal All objects in undocumented state

@see ScaledIntegerNode::scaledValue
*/
double ScaledIntegerNode::offset() const
{
   return impl_->offset();
}

/*!
@brief Diagnostic function to print internal state of object to output stream in an indented format.
@copydetails Node::dump()
*/
#ifdef E57_ENABLE_DIAGNOSTIC_OUTPUT
void ScaledIntegerNode::dump( int indent, std::ostream &os ) const
{
   impl_->dump( indent, os );
}
#else
void ScaledIntegerNode::dump( int indent, std::ostream &os ) const
{
   UNUSED( indent );
   UNUSED( os );
}
#endif

/*!
@brief Upcast a ScaledIntegerNode handle to a generic Node handle.

@details
An upcast is always safe, and the compiler can automatically insert it for initializations of Node
variables and Node function arguments.

@return A smart Node handle referencing the underlying object.

@throw No E57Exceptions.

@see Explanation in Node, Node::type(), ScaledIntegerNode(const Node&)
*/
ScaledIntegerNode::operator Node() const
{
   // Upcast from shared_ptr<ScaledIntegerNodeImpl> to SharedNodeImplPtr and construct a Node object
   return Node( impl_ );
}

/*!
@brief Downcast a generic Node handle to an ScaledIntegerNode handle.

@param [in] n The generic handle to downcast.

@details
The handle @a n must be for an underlying ScaledIntegerNode, otherwise an exception is thrown. In
designs that need to avoid the exception, use Node::type() to determine the actual type of the @a n
before downcasting. This function must be explicitly called (c++ compiler cannot insert it
automatically).

@throw ::ErrorBadNodeDowncast

@see Node::type(), ScaledIntegerNode::operator, Node()
*/
ScaledIntegerNode::ScaledIntegerNode( const Node &n )
{
   if ( n.type() != TypeScaledInteger )
   {
      throw E57_EXCEPTION2( ErrorBadNodeDowncast, "nodeType=" + toString( n.type() ) );
   }

   // Set our shared_ptr to the downcast shared_ptr
   impl_ = std::static_pointer_cast<ScaledIntegerNodeImpl>( n.impl() );
}

/// @cond documentNonPublic The following isn't part of the API, and isn't documented.
ScaledIntegerNode::ScaledIntegerNode( std::shared_ptr<ScaledIntegerNodeImpl> ni ) : impl_( ni )
{
}
/// @endcond
