/*
 * Node.cpp - implementation of public functions of the Node 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 Node.cpp

#include "NodeImpl.h"

using namespace e57;

// Put this function first so we can reference the code in doxygen using @skip
/*!
@brief Check whether Node class invariant is true

@param [in] doRecurse If true, also check invariants of all children or sub-objects recursively.
@param [in] doDowncast If true, also check any invariants of the actual derived type in addition to
the generic node invariants.

@details
This function checks at least the assertions in the documented class invariant description (see
class reference page for this object). Other internal invariants that are implementation-dependent
may also be checked. If any invariant clause is violated, an E57Exception with errorCode of
ErrorInvarianceViolation is thrown.

Specifying doRecurse=true only makes sense if doDowncast=true is also specified (the generic Node
has no way to access any children). Checking the invariant recursively may be expensive if the tree
is large, so should be used judiciously, in debug versions of the application.

@post No visible state is modified.

@throw ::ErrorInvarianceViolation or any other E57 ErrorCode

@see Class Invariant section in Node, IntegerNode::checkInvariant,
ScaledIntegerNode::checkInvariant, FloatNode::checkInvariant, BlobNode::checkInvariant,
StructureNode::checkInvariant, VectorNode::checkInvariant, CompressedVectorNode::checkInvariant
*/
void Node::checkInvariant( bool doRecurse, bool doDowncast )
{
   ImageFile imf = destImageFile();

   // If destImageFile not open, can't test invariant (almost every call would throw)
   if ( !imf.isOpen() )
   {
      return;
   }

   // Parent attachment state is same as this attachment state
   if ( isAttached() != parent().isAttached() )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }

   // Parent destination ImageFile is same as this
   if ( imf != parent().destImageFile() )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }

   // If this is the ImageFile root node
   if ( *this == imf.root() )
   {
      // Must be attached
      if ( !isAttached() )
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }

      // Must be is a root node
      if ( !isRoot() )
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }
   }

   // If this is a root node
   if ( isRoot() )
   {
      // Absolute pathName is "/"
      if ( pathName() != "/" )
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }

      // parent() returns this node
      if ( *this != parent() )
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }
   }
   else
   {
      // Non-root can't be own parent
      if ( *this == parent() )
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }

      // pathName is concatenation of parent pathName and this elementName
      if ( parent().isRoot() )
      {
         if ( pathName() != "/" + elementName() )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }
      }
      else
      {
         if ( pathName() != parent().pathName() + "/" + elementName() )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }
      }

      // Non-root nodes must be children of either a VectorNode or StructureNode
      if ( parent().type() == TypeVector )
      {
         VectorNode v = static_cast<VectorNode>( parent() );

         // Must be defined in parent VectorNode with this elementName
         if ( !v.isDefined( elementName() ) )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }

         // Getting child of parent with this elementName must return this
         if ( v.get( elementName() ) != *this )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }
      }
      else if ( parent().type() == TypeStructure )
      {
         StructureNode s = static_cast<StructureNode>( parent() );

         // Must be defined in parent VectorNode with this elementName
         if ( !s.isDefined( elementName() ) )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }

         // Getting child of parent with this elementName must return this
         if ( s.get( elementName() ) != *this )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }
      }
      else
      {
         throw E57_EXCEPTION1( ErrorInvarianceViolation );
      }
   }

   // If this is attached
   if ( isAttached() )
   {
      // Get root of this
      Node n = *this;
      while ( !n.isRoot() )
      {
         n = n.parent();
      }

      // If in tree of ImageFile (could be in a prototype instead)
      if ( n == imf.root() )
      {
         // pathName must be defined
         if ( !imf.root().isDefined( pathName() ) )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }

         // Getting by absolute pathName must be this
         if ( imf.root().get( pathName() ) != *this )
         {
            throw E57_EXCEPTION1( ErrorInvarianceViolation );
         }
      }
   }

   // If requested, check invariants of derived types:
   if ( doDowncast )
   {
      switch ( type() )
      {
         case TypeStructure:
         {
            StructureNode s( *this );
            s.checkInvariant( doRecurse, false );
         }
         break;
         case TypeVector:
         {
            VectorNode v( *this );
            v.checkInvariant( doRecurse, false );
         }
         break;
         case TypeCompressedVector:
         {
            CompressedVectorNode cv( *this );
            cv.checkInvariant( doRecurse, false );
         }
         break;
         case TypeInteger:
         {
            IntegerNode i( *this );
            i.checkInvariant( doRecurse, false );
         }
         break;
         case TypeScaledInteger:
         {
            ScaledIntegerNode si( *this );
            si.checkInvariant( doRecurse, false );
         }
         break;
         case TypeFloat:
         {
            FloatNode f( *this );
            f.checkInvariant( doRecurse, false );
         }
         break;
         case TypeString:
         {
            StringNode s( *this );
            s.checkInvariant( doRecurse, false );
         }
         break;
         case TypeBlob:
         {
            BlobNode b( *this );
            b.checkInvariant( doRecurse, false );
         }
         break;
         default:
            break;
      }
   }
}

/*!
@class e57::Node

@brief Generic handle to any of the 8 types of E57 element objects.

@details
A Node is a generic handle to an underlying object that is any of the eight type of E57 element
objects. Each of the eight node types support the all the functions of the Node class. A Node is a
vertex in a tree (acyclic graph), which is a hierarchical organization of nodes. At the top of the
hierarchy is a single root Node. If a Node is a container type (StructureNode, VectorNode,
CompressedVectorNode) it may have child nodes. The following are non-container type nodes (also
known as terminal nodes): IntegerNode, ScaledIntegerNode, FloatNode, StringNode, BlobNode. Terminal
nodes store various types of values and cannot have children. Each Node has an elementName, which is
a string that uniquely identifies it within the children of its parent. Children of a StructureNode
have elementNames that are explicitly given by the API user. Children of a VectorNode or
CompressedVectorNode have element names that are string reorientations of the Node's positional
index, starting at "0". A path name is a sequence elementNames (divided by "/") that must be
traversed to get from a Node to one of its descendents.

Data is organized in an E57 format file (an ImageFile) hierarchically. Each ImageFile has a
predefined root node that other nodes can be attached to as children (either directly or
indirectly). A Node can exist temporarily without being attached to an ImageFile, however the state
will not be saved in the associated file, and the state will be lost if the program exits.

A handle to a generic Node may be safely be converted to and from a handle to the Node's true
underlying type. Since an attempt to convert a generic Node to a incorrect handle type will fail
with an exception, the true type should be interrogated beforehand.

Due to the set-once design of the Foundation API, terminal nodes are immutable (i.e. their values
and attributes can't change after creation). Once a parent-child relationship has been established,
it cannot be changed.

Only generic operations are available for a Node, to access more specific operations (e.g.
StructureNode::childCount) the generic handle must be converted to the node type of the underlying
object. This conversion is done in a type-safe way using "downcasting" (see discussion below).

@section node_Downcasting Downcasting
The conversion from a general handle type to a specific handle type is called "downcasting". Each of
the 8 specific node types have a downcast function (see IntegerNode::IntegerNode(const Node&) for
example). If a downcast is requested to an incorrect type (e.g. taking a Node handle that is
actually a FloatNode and trying to downcast it to a IntegerNode), an E57Exception is thrown with an
ErrorCode of ::ErrorBadNodeDowncast. Depending on the program design, throwing a bad downcast
exception might be acceptable, if an element must be a specific type and no recovery is possible. If
a standard requires an element be one several types, then Node::type() should be used to interrogate
the type in an @c if or @c switch statement. Downcasting is "dangerous" (can fail with an exception)
so the API requires the programmer to explicitly call the downcast functions rather than have the
c++ compiler insert them automatically.

@section node_Upcasting Upcasting
The conversion of a specific node handle (e.g. IntegerNode) to a general Node handle is called
"upcasting". Each of the 8 specific node types have an upcast function (see IntegerNode::operator
Node() for example). Upcasting is "safe" (can't cause an exception) so the API allows the c++
compiler to insert them automatically. Upcasting is useful if you have a specific node handle and
want to call a function that takes a generic Node handle argument. In this case, the function can be
called with the specific handle and the compiler will automatically insert the upcast conversion.
This implicit conversion allows one function, with an argument of type Node, to handle operations
that apply to all 8 types of nodes (e.g. StructureNode::set()).

@section node_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 Node.cpp
@skip void Node::checkInvariant
@until ^}

@see StructureNode, VectorNode, CompressedVectorNode, IntegerNode, ScaledIntegerNode, FloatNode,
StringNode, BlobNode
*/

/*!
@brief Return the NodeType of a generic Node.

@details
This function allows the actual node type to be interrogated before upcasting the handle to the
actual node type (see Upcasting and Downcasting section in Node).

@post No visible state is modified.

@return The NodeType of a generic Node, which may be one of the following NodeType enumeration
values:
::TypeStructure, ::TypeVector, ::TypeCompressedVector, ::TypeInteger, ::TypeScaledInteger,
::TypeFloat, ::TypeString, ::TypeBlob.

@see NodeType, upcast/downcast discussion in Node
*/
NodeType Node::type() const
{
   return impl_->type();
}

/*!
@brief Is this a root node.

@details
A root node has itself as a parent (it is not a child of any node).
Newly constructed nodes (before they are inserted into an ImageFile tree) start out as root nodes.
It is possible to temporarily create small trees that are unattached to any ImageFile. In these
temporary trees, the top-most node will be a root node. After the tree is attached to the ImageFile
tree, the only root node will be the pre-created one of the ImageTree (the one returned by
ImageFile::root). The concept of @em attachment is slightly larger than that of the parent-child
relationship (see Node::isAttached and CompressedVectorNode::CompressedVectorNode for more details).

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

@return true if this node is a root node.

@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see Node::parent, Node::isAttached, CompressedVectorNode::CompressedVectorNode
*/
bool Node::isRoot() const
{
   return impl_->isRoot();
}

/*!
@brief Return parent of node, or self if a root node.

@details
Nodes are organized into trees (acyclic graphs) with a distinguished node (the "top-most" node)
called the root node. A parent-child relationship is established between nodes to form a tree. Nodes
can have zero or one parent. Nodes with zero parents are called root nodes.

In the API, if a node has zero parents it is represented by having itself as a parent. Due to the
set-once design of the API, a parent-child relationship cannot be modified once established. A child
node can be any of the 8 node types, but a parent node can only be one of the 3 container node types
(::TypeStructure, ::TypeVector, and ::TypeCompressedVector). Each parent-child link has a string
name (the elementName) associated with it (See Node::elementName for more details). More than one
tree can be formed at any given time. Typically small trees are temporarily constructed before
attachment to an ImageFile so that they will be written to the disk.

@warning User algorithms that use this function to walk the tree must take care to handle the case
where a node is its own parent (it is a root node). Use Node::isRoot to avoid infinite loops or
infinite recursion.

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

@return A smart Node handle referencing the parent node or this node if is a root node.

@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see Node::isRoot, Node::isAttached, CompressedVectorNode::CompressedVectorNode, Node::elementName
*/
Node Node::parent() const
{
   return Node( impl_->parent() );
}

/*!
@brief Get absolute pathname of node.

@details
Nodes are organized into trees (acyclic graphs) by a parent-child relationship between nodes. Each
parent-child relationship has an associated elementName string that is unique for a given parent.
Any node in a given tree can be identified by a sequence of elementNames of how to get to the node
from the root of the tree. An absolute pathname string that is formed by arranging this sequence of
elementNames separated by the "/" character with a leading "/" prepended.

Some example absolute pathNames: "/data3D/0/points/153/cartesianX", "/data3D/0/points",
"/cameraImages/1/pose/rotation/w", and "/". These examples have probably been attached to an
ImageFile. Here is an example absolute pathName of a node in a pose tree that has not yet been
attached to an ImageFile: "/pose/rotation/w".

A technical aside: the elementName of a root node does not appear in absolute pathnames, since the
"path" is between the staring node (the root) and the ending node. By convention, in this API, a
root node has the empty string ("") as its elementName.

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

@return The absolute path name of the node.

@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see Node::elementName, Node::parent, Node::isRoot
*/
ustring Node::pathName() const
{
   return impl_->pathName();
}

/*!
@brief Get element name of node.

@details
The elementName is a string associated with each parent-child link between nodes. For a given
parent, the elementName uniquely identifies each of its children. Thus, any node in a tree can be
identified by a sequence of elementNames that form a path from the tree's root node (see
Node::pathName for more details).

Three types of nodes (the container node types) can be parents: StructureNode, VectorNode, and
CompressedVectorNode. The children of a StructureNode are explicitly given unique elementNames when
they are attached to the parent (using StructureNode::set). The children of VectorNode and
CompressedVectorNode are implicitly given elementNames based on their position in the list (starting
at "0"). In a CompressedVectorNode, the elementName can become quite large: "1000000000" or more.
However in a CompressedVectorNode, the elementName string is not stored in the file and is deduced
by the position of the child.

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

@return The element name of the node, or "" if a root node.

@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see Node::pathName, Node::parent, Node::isRoot
*/
ustring Node::elementName() const
{
   return impl_->elementName();
}

/*!
@brief Get the ImageFile that was declared as the destination for the node when it was created.

@details
The first argument of the constructors of each of the 8 types of nodes is an ImageFile that
indicates which ImageFile the node will eventually be attached to. This function returns that
constructor argument. It is an error to attempt to attach the node to a different ImageFile. However
it is not an error to not attach the node to any ImageFile (it's just wasteful). Use
Node::isAttached to check if the node actually did get attached.

@post No visible object state is modified.

@return The ImageFile that was declared as the destination for the node when it was created.

@see Node::isAttached, StructureNode::StructureNode(), VectorNode::VectorNode(),
CompressedVectorNode::CompressedVectorNode(), IntegerNode::IntegerNode(),
ScaledIntegerNode::ScaledIntegerNode(), FloatNode::FloatNode(), StringNode::StringNode(),
BlobNode::BlobNode()
*/
ImageFile Node::destImageFile() const
{
   return ImageFile( impl_->destImageFile() );
}

/*!
@brief Has node been attached into the tree of an ImageFile.

@details
Nodes are attached into an ImageFile tree by inserting them as children (directly or indirectly) of
the ImageFile's root node. Nodes can also be attached to an ImageFile if they are used in the @c
codecs or @c prototype trees of an CompressedVectorNode that is attached. Attached nodes will be
saved to disk when the ImageFile is closed, and restored when the ImageFile is read back in from
disk. Unattached nodes will not be saved to disk. It is not recommended to create nodes that are not
eventually attached to the ImageFile.

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

@return @c true if node is child of (or in codecs or prototype of a child CompressedVectorNode of)
the root node of an ImageFile.

@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see Node::destImageFile, ImageFile::root
*/
bool Node::isAttached() const
{
   return impl_->isAttached();
}

/*!
@brief Diagnostic function to print internal state of object to output stream in an indented format.

@param [in] indent Number of spaces to indent all the printed lines of this object.
@param [in] os Output stream to print on.

@details
All objects in the E57 Foundation API (with exception of E57Exception) support a dump() function.
These functions print out to the console a detailed listing of the internal state of objects. The
content of these printouts is not documented, and is really of interest only to implementation
developers/maintainers or the really adventurous users. In implementations of the API other than the
Reference Implementation, the dump() functions may produce no output (although the functions should
still be defined). The output format may change from version to version.

@post No visible object state is modified.

@throw No E57Exceptions
*/
#ifdef E57_ENABLE_DIAGNOSTIC_OUTPUT
void Node::dump( int indent, std::ostream &os ) const
{
   impl_->dump( indent, os );
}
#else
void Node::dump( int indent, std::ostream &os ) const
{
   E57_UNUSED( indent );
   E57_UNUSED( os );
}
#endif

/*!
@brief Test if two node handles refer to the same underlying node

@param [in] n2 The node to compare this node with

@post No visible object state is modified.

@return @c true if node handles refer to the same underlying node.

@throw No E57Exceptions
*/
bool Node::operator==( const Node &n2 ) const
{
   return ( impl_ == n2.impl_ );
}

/*!
@brief Test if two node handles refer to different underlying nodes

@param [in] n2 The node to compare this node with

@post No visible object state is modified.

@return @c true if node handles refer to different underlying nodes.

@throw No E57Exceptions
*/
bool Node::operator!=( const Node &n2 ) const
{
   return ( impl_ != n2.impl_ );
}

/// @cond documentNonPublic The following isn't part of the API, and isn't documented.
Node::Node( NodeImplSharedPtr ni ) : impl_( ni )
{
}
/// @endcond
