/*
 * 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 "FloatNodeImpl.h"
#include "CheckedFile.h"
#include "StringFunctions.h"

namespace e57
{
   FloatNodeImpl::FloatNodeImpl( ImageFileImplWeakPtr destImageFile, double value,
                                 FloatPrecision precision, double minimum, double maximum ) :
      NodeImpl( destImageFile ), value_( value ), precision_( precision ), minimum_( minimum ),
      maximum_( maximum )
   {
      // Since this ctor also used to construct single precision, and defaults for minimum/maximum
      // are for double precision, adjust bounds smaller if single.
      if ( precision_ == PrecisionSingle )
      {
         if ( minimum_ < FLOAT_MIN )
         {
            minimum_ = FLOAT_MIN;
         }
         if ( maximum_ > FLOAT_MAX )
         {
            maximum_ = FLOAT_MAX;
         }
      }
   }

   // Throw an exception if the value is not within bounds.
   void FloatNodeImpl::validateValue() const
   {
      if ( value_ < minimum_ || value_ > maximum_ )
      {
         throw E57_EXCEPTION2( ErrorValueOutOfBounds, "this->pathName=" + this->pathName() +
                                                         " value=" + toString( value_ ) +
                                                         " minimum=" + toString( minimum_ ) +
                                                         " maximum=" + toString( maximum_ ) );
      }
   }

   bool FloatNodeImpl::isTypeEquivalent( NodeImplSharedPtr ni )
   {
      // don't checkImageFileOpen

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

      // Downcast to shared_ptr<FloatNodeImpl>
      std::shared_ptr<FloatNodeImpl> fi( std::static_pointer_cast<FloatNodeImpl>( ni ) );

      // precision must match
      if ( precision_ != fi->precision_ )
      {
         return ( false );
      }

      // minimum must match
      if ( minimum_ != fi->minimum_ )
      {
         return ( false );
      }

      // maximum must match
      if ( maximum_ != fi->maximum_ )
      {
         return ( false );
      }

      // ignore value_, doesn't have to match

      // Types match
      return ( true );
   }

   bool FloatNodeImpl::isDefined( const ustring &pathName )
   {
      // don't checkImageFileOpen

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

   double FloatNodeImpl::value() const
   {
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      return value_;
   }

   FloatPrecision FloatNodeImpl::precision() const
   {
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      return precision_;
   }

   double FloatNodeImpl::minimum() const
   {
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      return minimum_;
   }

   double FloatNodeImpl::maximum() const
   {
      checkImageFileOpen( __FILE__, __LINE__, static_cast<const char *>( __FUNCTION__ ) );
      return maximum_;
   }

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

      // We are a leaf node, so verify that we are listed in set (either relative or absolute form)
      if ( pathNames.find( relativePathName( origin ) ) == pathNames.end() &&
           pathNames.find( pathName() ) == pathNames.end() )
      {
         throw E57_EXCEPTION2( ErrorNoBufferForElement, "this->pathName=" + this->pathName() );
      }
   }

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

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

      cf << space( indent ) << "<" << fieldName << " type=\"Float\"";
      if ( precision_ == PrecisionSingle )
      {
         cf << " precision=\"single\"";

         // Don't need to write if are default values
         if ( minimum_ > FLOAT_MIN )
         {
            cf << " minimum=\"" << static_cast<float>( minimum_ ) << "\"";
         }
         if ( maximum_ < FLOAT_MAX )
         {
            cf << " maximum=\"" << static_cast<float>( maximum_ ) << "\"";
         }

         // Write value as child text, unless it is the default value
         if ( value_ != 0.0 )
         {
            cf << ">" << static_cast<float>( value_ ) << "</" << fieldName << ">\n";
         }
         else
         {
            cf << "/>\n";
         }
      }
      else
      {
         // Don't need to write precision="double", because that's the default

         // Don't need to write if are default values
         if ( minimum_ > DOUBLE_MIN )
         {
            cf << " minimum=\"" << minimum_ << "\"";
         }
         if ( maximum_ < DOUBLE_MAX )
         {
            cf << " maximum=\"" << maximum_ << "\"";
         }

         // Write value as child text, unless it is the default value
         if ( value_ != 0.0 )
         {
            cf << ">" << value_ << "</" << fieldName << ">\n";
         }
         else
         {
            cf << "/>\n";
         }
      }
   }

#ifdef E57_ENABLE_DIAGNOSTIC_OUTPUT
   void FloatNodeImpl::dump( int indent, std::ostream &os ) const
   {
      // don't checkImageFileOpen
      os << space( indent ) << "type:        Float" << " (" << type() << ")" << std::endl;
      NodeImpl::dump( indent, os );
      os << space( indent ) << "precision:   ";
      if ( precision() == PrecisionSingle )
      {
         os << "single" << std::endl;
      }
      else
      {
         os << "double" << std::endl;
      }

      // Save old stream config
      const std::streamsize oldPrecision = os.precision();
      const std::ios_base::fmtflags oldFlags = os.flags();

      os << space( indent ) << std::scientific << std::setprecision( 17 )
         << "value:       " << value_ << std::endl;
      os << space( indent ) << "minimum:     " << minimum_ << std::endl;
      os << space( indent ) << "maximum:     " << maximum_ << std::endl;

      // Restore old stream config
      os.precision( oldPrecision );
      os.flags( oldFlags );
   }
#endif
}
