/*
 * 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.
 */

#include "BlobNodeImpl.h"
#include "CheckedFile.h"
#include "ImageFileImpl.h"
#include "SectionHeaders.h"
#include "StringFunctions.h"

namespace e57
{
   BlobNodeImpl::BlobNodeImpl( ImageFileImplWeakPtr destImageFile, int64_t byteCount ) :
      NodeImpl( destImageFile )
   {
      // don't checkImageFileOpen, NodeImpl() will do it

      ImageFileImplSharedPtr imf( destImageFile );

      // This what caller thinks blob length is
      blobLogicalLength_ = byteCount;

      // Round segment length up to multiple of 4 bytes
      binarySectionLogicalLength_ = sizeof( BlobSectionHeader ) + blobLogicalLength_;
      unsigned remainder = binarySectionLogicalLength_ % 4;
      if ( remainder > 0 )
      {
         binarySectionLogicalLength_ += 4 - remainder;
      }

      // Reserve space for blob in file, extend with zeros since writes will
      // happen at later time by caller
      binarySectionLogicalStart_ = imf->allocateSpace( binarySectionLogicalLength_, true );

      // Prepare BlobSectionHeader
      BlobSectionHeader header;
      header.sectionLogicalLength = binarySectionLogicalLength_;
#ifdef E57_VERBOSE
      header.dump(); //???
#endif

      // Write header at beginning of section
      imf->file_->seek( binarySectionLogicalStart_ );
      imf->file_->write( reinterpret_cast<char *>( &header ), sizeof( header ) );
   }

   BlobNodeImpl::BlobNodeImpl( ImageFileImplWeakPtr destImageFile, int64_t fileOffset,
                               int64_t length ) : NodeImpl( destImageFile )
   {
      // Init blob object that already exists in E57 file currently reading.

      // don't checkImageFileOpen, NodeImpl() will do it

      ImageFileImplSharedPtr imf( destImageFile );

      // Init state from values read from XML
      blobLogicalLength_ = length;
      binarySectionLogicalStart_ = imf->file_->physicalToLogical( fileOffset );
      binarySectionLogicalLength_ = sizeof( BlobSectionHeader ) + blobLogicalLength_;
   }

   bool BlobNodeImpl::isTypeEquivalent( NodeImplSharedPtr ni )
   {
      // don't checkImageFileOpen, NodeImpl() will do it

      // Same node type?
      if ( ni->type() != TypeBlob )
      {
         return ( false );
      }

      // Downcast to shared_ptr<BlobNodeImpl>
      std::shared_ptr<BlobNodeImpl> bi( std::static_pointer_cast<BlobNodeImpl>( ni ) );

      // blob lengths must match
      if ( blobLogicalLength_ != bi->blobLogicalLength_ )
      {
         return ( false );
      }

      // ignore blob contents, doesn't have to match

      // Types match
      return ( true );
   }

   bool BlobNodeImpl::isDefined( const ustring &pathName )
   {
      // don't checkImageFileOpen, NodeImpl() will do it

      // We have no sub-structure, so if path not empty return false
      return pathName.empty();
   }

   int64_t BlobNodeImpl::byteCount()
   {
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      return ( blobLogicalLength_ );
   }

   void BlobNodeImpl::read( uint8_t *buf, int64_t start, size_t count )
   {
      //??? check start not negative

      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      if ( static_cast<uint64_t>( start ) + count > blobLogicalLength_ )
      {
         throw E57_EXCEPTION2( ErrorBadAPIArgument,
                               "this->pathName=" + this->pathName() +
                                  " start=" + toString( start ) + " count=" + toString( count ) +
                                  " length=" + toString( blobLogicalLength_ ) );
      }

      ImageFileImplSharedPtr imf( destImageFile_ );
      imf->file_->seek( binarySectionLogicalStart_ + sizeof( BlobSectionHeader ) + start );
      imf->file_->read( reinterpret_cast<char *>( buf ),
                        static_cast<size_t>( count ) ); //??? arg1 void* ?
   }

   void BlobNodeImpl::write( uint8_t *buf, int64_t start, size_t count )
   {
      //??? check start not negative
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );

      ImageFileImplSharedPtr destImageFile( destImageFile_ );

      if ( !destImageFile->isWriter() )
      {
         throw E57_EXCEPTION2( ErrorFileReadOnly, "fileName=" + destImageFile->fileName() );
      }
      if ( !isAttached() )
      {
         throw E57_EXCEPTION2( ErrorNodeUnattached, "fileName=" + destImageFile->fileName() );
      }

      if ( static_cast<uint64_t>( start ) + count > blobLogicalLength_ )
      {
         throw E57_EXCEPTION2( ErrorBadAPIArgument,
                               "this->pathName=" + this->pathName() +
                                  " start=" + toString( start ) + " count=" + toString( count ) +
                                  " length=" + toString( blobLogicalLength_ ) );
      }

      ImageFileImplSharedPtr imf( destImageFile_ );
      imf->file_->seek( binarySectionLogicalStart_ + sizeof( BlobSectionHeader ) + start );
      imf->file_->write( reinterpret_cast<char *>( buf ),
                         static_cast<size_t>( count ) ); //??? arg1 void* ?
   }

   void BlobNodeImpl::checkLeavesInSet( const StringSet &pathNames, NodeImplSharedPtr origin )
   {
      // don't checkImageFileOpen

      // We are a leaf node, so verify that we are listed in set. ???true for
      // blobs? what exception get if try blob in compressedvector?
      if ( pathNames.find( relativePathName( origin ) ) == pathNames.end() )
      {
         throw E57_EXCEPTION2( ErrorNoBufferForElement, "this->pathName=" + this->pathName() );
      }
   }

   void BlobNodeImpl::writeXml( ImageFileImplSharedPtr /*imf*/, CheckedFile &cf, int indent,
                                const char *forcedFieldName )
   {
      // don't checkImageFileOpen

      ustring fieldName;
      if ( forcedFieldName != nullptr )
      {
         fieldName = forcedFieldName;
      }
      else
      {
         fieldName = elementName_;
      }

      //??? need to implement
      //??? Type --> type
      //??? need to have length?, check same as in section header?
      uint64_t physicalOffset = cf.logicalToPhysical( binarySectionLogicalStart_ );
      cf << space( indent ) << "<" << fieldName << " type=\"Blob\" fileOffset=\"" << physicalOffset
         << "\" length=\"" << blobLogicalLength_ << "\"/>\n";
   }

#ifdef E57_ENABLE_DIAGNOSTIC_OUTPUT
   void BlobNodeImpl::dump( int indent, std::ostream &os ) const
   {
      // don't checkImageFileOpen
      os << space( indent ) << "type:        Blob" << " (" << type() << ")" << std::endl;
      NodeImpl::dump( indent, os );
      os << space( indent ) << "blobLogicalLength_:           " << blobLogicalLength_ << std::endl;
      os << space( indent ) << "binarySectionLogicalStart:    " << binarySectionLogicalStart_
         << std::endl;
      os << space( indent ) << "binarySectionLogicalLength:   " << binarySectionLogicalLength_
         << std::endl;
      //    size_t i;
      //    for (i = 0; i < blobLogicalLength_ && i < 10; i++) {
      //        uint8_t b;
      //        read(&b, i, 1);
      //        os << space(indent) << "data[" << i << "]: "<< static_cast<int>(b)
      //        << std::endl;
      //    }
      //    if (i < blobLogicalLength_)
      //        os << space(indent) << "more data unprinted..." << std::endl;
   }
#endif

}
