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

#include "BlobNodeImpl.h"
#include "StringFunctions.h"

using namespace e57;

// Put this function first so we can reference the code in doxygen using @skip
/*!
@brief Check whether BlobNode class invariant is true
@copydetails IntegerNode::checkInvariant()
*/
void BlobNode::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 ( byteCount() < 0 )
   {
      throw E57_EXCEPTION1( ErrorInvarianceViolation );
   }
}

/*!
@class e57::BlobNode

@brief An E57 element encoding an fixed-length sequence of bytes with an opaque format.

@details
A BlobNode is a terminal node (i.e. having no children) that holds an opaque, fixed-length sequence
of bytes. The number of bytes in the BlobNode is declared at creation time. The content of the blob
is stored within the E57 file in an efficient binary format (but not compressed). The BlobNode
cannot grow after it is created. There is no ordering constraints on how content of a BlobNode may
be accessed (i.e. it is random access). BlobNodes in an ImageFile opened for reading are read-only.

There are two categories of BlobNodes, distinguished by their usage: private BlobNodes and public
BlobNodes. In a private BlobNode, the format of its content bytes is not published. This is useful
for storing proprietary data that a writer does not wish to share with all readers. Rather than put
this information in a separate file, the writer can embed the file inside the E57 file so it cannot
be lost.

In a public BlobNode, the format is published or follows some industry standard format (e.g. JPEG).
Rather than reinvent the wheel in applications that are already well-served by an existing format
standard, an E57 file writer can just embed an existing file as an "attachment" in a BlobNode. The
internal format of a public BlobNode is not enforced by the Foundation API. It is recommended that
there be some mechanism for a reader to know ahead of time which format the BlobNode content adheres
to (either specified by a document, or encoded by some scheme in the E57 Element tree).

The BlobNode is the one node type where the set-once policy is not strictly enforced. It is possible
to write the same byte location in a BlobNode several times. However it is not recommended.

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

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

@see Node
*/

/*!
@brief Create an element for storing a sequence of bytes with an opaque format.
@param [in] destImageFile The ImageFile where the new node will eventually be stored.
@param [in] byteCount     The number of bytes reserved in the ImageFile for holding the blob.
@details
The BlobNode class corresponds to the ASTM E57 standard Blob element.

See the class discussion at bottom of BlobNode page for more details.

The E57 Foundation Implementation may pre-allocate disk space in the ImageFile to store the declared
length of the blob. The disk must have enough free space to store @a byteCount bytes of data. The
data of a newly created BlobNode is initialized to zero.

The @a destImageFile indicates which ImageFile the BlobNode 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 BlobNode to the @a destImageFile. It
is an error to attempt to attach the BlobNode to a different ImageFile.

@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 byteCount >= 0

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

@see Node, BlobNode::read, BlobNode::write
*/
BlobNode::BlobNode( const ImageFile &destImageFile, int64_t byteCount ) :
   impl_( new BlobNodeImpl( destImageFile.impl(), byteCount ) )
{
}

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

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

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

/*!
@brief Get elementName string, that identifies the node in its parent.
@copydetails Node::elementName()
*/
ustring BlobNode::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 BlobNode::destImageFile() const
{
   return ImageFile( impl_->destImageFile() );
}

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

/*!
@brief Get size of blob declared when it was created.

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

@return The declared size of the blob when it was created.

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

@see BlobNode::read, BlobNode::write
*/
int64_t BlobNode::byteCount() const
{
   return impl_->byteCount();
}

/*!
@brief Read a buffer of bytes from a blob.

@param [in] buf A memory buffer to store bytes read from the blob.
@param [in] start The index of the first byte in blob to read.
@param [in] count The number of bytes to read.

@details
The memory buffer @a buf must be able to store at least @a count bytes.

The data is stored in a binary section of the ImageFile with checksum protection, so undetected
corruption is very unlikely. It is an error to attempt to read outside the declared size of the
Blob. The format of the data read is opaque (unspecified by the ASTM E57 data format standard).
Since @a buf is a byte buffer, byte ordering is irrelevant (it will come out in the same order that
it went in). There is no constraint on the ordering of reads. Any part of the Blob data can be read
zero or more times.

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@pre buf != NULL
@pre 0 <= @a start < byteCount()
@pre 0 <= count
@pre (@a start + @a count) < byteCount()

@throw ::ErrorBadAPIArgument (n/c)
@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorSeekFailed (n/c)
@throw ::ErrorReadFailed (n/c)
@throw ::ErrorBadChecksum (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see BlobNode::byteCount, BlobNode::write
*/
void BlobNode::read( uint8_t *buf, int64_t start, size_t count )
{
   impl_->read( buf, start, count );
}

/*!
@brief Write a buffer of bytes to a blob.

@param [in] buf A memory buffer of bytes to write to the blob.
@param [in] start The index of the first byte in blob to write to.
@param [in] count The number of bytes to write.

@details
The memory buffer @a buf must store at least @a count bytes.

The data is stored in a binary section of the ImageFile with checksum protection, so undetected
corruption is very unlikely. It is an error to attempt to write outside the declared size of the
Blob. The format of the data written is opaque (unspecified by the ASTM E57 data format standard).
Since @a buf is a byte buffer, byte ordering is irrelevant (it will come out in the same order that
it went in). There is no constraint on the ordering of writes. It is not an error to write a portion
of the BlobNode data more than once, or not at all. Initially all the BlobNode data is zero, so if a
portion is not written, it will remain zero. The BlobNode is one of the two node types that must be
attached to the root of a write mode ImageFile before write operations can be performed (the other
type is CompressedVectorNode).

@pre The destination ImageFile must be open (i.e. destImageFile().isOpen()).
@pre The associated destImageFile must have been opened in write mode (i.e.
destImageFile().isWritable()).
@pre The BlobNode must be attached to an ImageFile (i.e. isAttached()).
@pre buf != NULL
@pre 0 <= @a start < byteCount()
@pre 0 <= count
@pre (@a start + @a count) < byteCount()

@throw ::ErrorBadAPIArgument (n/c)
@throw ::ErrorImageFileNotOpen (n/c)
@throw ::ErrorFileReadOnly (n/c)
@throw ::ErrorNodeUnattached (n/c)
@throw ::ErrorSeekFailed (n/c)
@throw ::ErrorReadFailed (n/c)
@throw ::ErrorWriteFailed (n/c)
@throw ::ErrorBadChecksum (n/c)
@throw ::ErrorInternal All objects in undocumented state

@see BlobNode::byteCount, BlobNode::read
*/
void BlobNode::write( uint8_t *buf, int64_t start, size_t count )
{
   impl_->write( buf, start, count );
}

/*!
@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 BlobNode::dump( int indent, std::ostream &os ) const
{
   impl_->dump( indent, os );
}
#else
void BlobNode::dump( int indent, std::ostream &os ) const
{
   E57_UNUSED( indent );
   E57_UNUSED( os );
}
#endif

/*!
@brief Upcast a BlobNode 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(), BlobNode(const Node&)
*/
BlobNode::operator Node() const
{
   // Upcast from shared_ptr<StringNodeImpl> to SharedNodeImplPtr and construct
   // a Node object
   return Node( impl_ );
}

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

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

@details
The handle @a n must be for an underlying BlobNode, 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 (n/c)

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

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

/// @cond documentNonPublic The following isn't part of the API, and isn't documented.
BlobNode::BlobNode( const ImageFile &destImageFile, int64_t fileOffset, int64_t length ) :
   impl_( new BlobNodeImpl( destImageFile.impl(), fileOffset, length ) )
{
}

BlobNode::BlobNode( std::shared_ptr<BlobNodeImpl> ni ) : impl_( ni )
{
}
/// @endcond
