/******************************************************************************
* Copyright (c) 2011, Michael P. Gerlek (mpg@flaxen.com)
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in
*       the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
*       names of its contributors may be used to endorse or promote
*       products derived from this software without specific prior
*       written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
****************************************************************************/

#ifndef INCLUDED_POINTBUFFER_HPP
#define INCLUDED_POINTBUFFER_HPP


#include <boost/cstdint.hpp>
#include <boost/scoped_array.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

#include <pdal/pdal_internal.hpp>
#include <pdal/Bounds.hpp>
#include <pdal/Schema.hpp>
#include <pdal/Metadata.hpp>

#include <vector>

namespace pdal
{
    typedef std::map<Dimension const*, Dimension const*> DimensionMap;

/// A PointBuffer is the object that is passed through pdal::Stage instances
/// to form a pipeline. A PointBuffer is composed of a pdal::Schema that determines
/// the layout of the data contained within, along with a dictionary of pdal::Metadata
/// entries. The capacity of a PointBuffer is determined by the number of points
/// it contains, and the number of points possible in a PointBuffer is limited to
/// std::numeric_limits<boost::uint32_t>::max().  Underneath the covers, a PointBuffer
/// is simply the composed array of bytes described in the pdal::Schema. You can
/// operate on the raw bytes if you need to, but PointBuffer provides a number of
/// convienence methods to make things easier.
class PDAL_DLL PointBuffer
{
public:

    /** @name Constructors
    */
    /*! Base constructor for pdal::PointBuffer.
        \param schema pdal::Schema instance to use to describe the layout. It is copied.
        \param capacity size of the pdal::PointBuffer in number of points.
        \verbatim embed:rst
        .. note::

            All fields are initialized to inactive regardless of what the
            passed-in pdal::Schema says.
        \endverbatim
    */
    PointBuffer(const Schema& schema, boost::uint32_t capacity=65536);

    /// Copy constructor. The data array is simply memcpy'd.
    PointBuffer(const PointBuffer&);

    /// Assignment constructor.
    PointBuffer& operator=(const PointBuffer&);

    /// Destructor.
    ~PointBuffer() {};
    
    void reset(Schema const& new_schema);

    /** @name Attribute access
    */
    /*! returns the pdal::Bounds instance associated with this pdal::PointBuffer.
        \verbatim embed:rst
        .. note::

            It is not a requirement that stages keep the pdal::Bounds instance
            up-to-date when operating on the PointBuffer.
        \endverbatim
    */
    const Bounds<double>& getSpatialBounds() const;

    /// sets the pdal::Bounds instance for this pdal::PointBuffer
    /// @param bounds bounds instance to set.
    void setSpatialBounds(const Bounds<double>& bounds);

    /// returns the number of points that are set for the PointBuffer. It is
    /// an arbitrary number that must be <= getCapacity() is the number of
    /// active points for the PointBuffer
    inline boost::uint32_t getNumPoints() const
    {
        return m_numPoints;
    }

    /// sets the number of active points for the PointBuffer.
    /// @param v number of points to set.
    inline void setNumPoints(boost::uint32_t v)
    {
        assert(v <= m_capacity);
        m_numPoints = v;
    }

    /*! returns the maximum number of points the PointBuffer can hold.
        \verbatim embed:rst
        .. note::

            This value is given at construction time, and it in conjunction
            with the given pdal::Schema determine the size of the raw byte
            buffer that contains the point data. It cannot be changed.
        \endverbatim
    */
    inline boost::uint32_t getCapacity() const
    {
        return m_capacity;
    }

    /// A const reference to the internally copied pdal::Schema instance that
    /// was given at construction time.
    const Schema& getSchema() const
    {
        return m_schema;
    }

    /// returns the size of the currently filled raw byte array
    /// Equivalent to getNumPoints() * getSchema() * getByteSize().
    inline boost::uint64_t getBufferByteLength() const
    {
        return m_byteSize * m_numPoints;
    }

    /// returns the size of the theoretically filled raw byte array.
    /// Equivalent to getCapacity() * getSchema() * getByteSize().
    inline boost::uint64_t getBufferByteCapacity() const
    {
        return m_byteSize * m_capacity;
    }

    /** @name Point data access
    */
    /*! fetch the value T for a given pdal::Dimension dim at pointIndex i.
        \param dim pdal::Dimension instance describing the dimension to select
        \param pointIndex the point index of the PointBuffer to select.
        \verbatim embed:rst
        .. warning::

            If the data type of T is not the same as described in pdal::Dimension,
            the data value will be casted into the appropriate type. In some
            situations this may not be what you want. In situations where the T
            is smaller than the datatype given by `dim`, the return value T
            will simply be saturated.
        \endverbatim
    */
    template<class T> T const& getField(Dimension const& dim, boost::uint32_t pointIndex) const;

    /*! set the value T for a given pdal::Dimension dim at pointIndex i.
        \param dim pdal::Dimension instance describing the dimension to select
        \param pointIndex the point index of the PointBuffer to select.
        \param value the T value to set
        \verbatim embed:rst
        .. warning::

            If the data type of T is not the same as described in pdal::Dimension,
            the data value will be casted into the appropriate type. In some
            situations this may not be what you want. In situations where the T
            is smaller than the datatype given by `dim`, the return value T
            will simply be saturated.
        \endverbatim
    */
    template<class T> void setField(Dimension const& dim, boost::uint32_t pointIndex, T value);

    /*! bulk copy all the fields from the given point into this object
        \param destPointIndex the destination point index to copy the data from
                srcPointBuffer at srcPointIndex.
        \param srcPointIndex the point index of the PointBuffer to copy from.
        \param srcPointBuffer the source PointBuffer to copy from
        \verbatim embed:rst
        .. warning::

            This is only legal if the source and destination schemas are
            exactly the same. It is up the caller to ensure this is the case.
            Additionally, if the schemas are the same :cpp:func:`pdal::Schema::getByteSize()` but of
            different compositions, congratulations :)
        \endverbatim
    */
    inline void copyPointFast(boost::uint32_t destPointIndex,
                              boost::uint32_t srcPointIndex,
                              const PointBuffer& srcPointBuffer)
    {
        const boost::uint8_t* src = srcPointBuffer.getData(srcPointIndex);
        boost::uint8_t* dest = getData(destPointIndex);
        
        assert(srcPointBuffer.getSchema().getByteSize() == m_byteSize);
        
        memcpy(dest, src, m_byteSize);

        assert(m_numPoints <= m_capacity);

        return;
    }

    /*! bulk copy all the fields from the given point into this object
        \param destPointIndex the destination point index to copy the data from
                srcPointBuffer at srcPointIndex.
        \param srcPointIndex the point index of the PointBuffer to copy from.
        \param srcPointBuffer the source PointBuffer to copy from
        \param numPoints the number of points to copy
        \verbatim embed:rst
        .. warning::

            This is only legal if the source and destination schemas are
            exactly the same. It is up the caller to ensure this is the case.
            Additionally, if the schemas are the same :cpp:func:`pdal::Schema::getByteSize()` but of
            different compositions, congratulations :)
        \endverbatim
    */
    inline void copyPointsFast(boost::uint32_t destPointIndex,
                               boost::uint32_t srcPointIndex,
                               const PointBuffer& srcPointBuffer,
                               boost::uint32_t numPoints)
    {
        const boost::uint8_t* src = srcPointBuffer.getData(srcPointIndex);
        boost::uint8_t* dest = getData(destPointIndex);

        assert(m_numPoints <= m_capacity);
        memcpy(dest, src, m_byteSize * numPoints);
        return;
    }

    /** @name Raw Data Access
    */
    /// access to the raw byte data at specified pointIndex
    /// @param pointIndex position to start accessing
    inline boost::uint8_t* getData(boost::uint32_t pointIndex) const
    {
        return const_cast<boost::uint8_t*>(&(m_data.front())) + m_byteSize * pointIndex;
    }

    /// copies the raw data into your own byte array and sets the size
    /// @param data pointer to your byte array
    /// @param size size of the array in bytes
    void getData(boost::uint8_t** data, boost::uint64_t* size) const;

    /// set the data for a single point at given pointIndex from a
    /// raw byte array
    /// @param data raw byte array
    /// @param pointIndex point position to set data.
    void setData(boost::uint8_t* data, boost::uint32_t pointIndex);

    /// overwrite raw data at a given pointIndex for a given number of byteCount
    /// @param data raw byte array
    /// @param pointIndex position to start writing
    /// @param byteCount number of bytes to overwrite at given position
    void setDataStride(boost::uint8_t* data, boost::uint32_t pointIndex, boost::uint32_t byteCount);

    /// returns the raw array size of the current buffer. 
    inline boost::uint64_t getArraySize() const
    {
        return static_cast<boost::uint64_t>(m_data.size());
    }
    
    static void scaleData(PointBuffer const& source_buffer,
                          PointBuffer& destination_buffer,
                          Dimension const& source_dimension,
                          Dimension const& destination_dimension,
                          boost::uint32_t source_index,
                          boost::uint32_t destination_index);


    /** @name Metadata
    */

    /// return  Metadatas const& for the PointBuffer
    inline Metadata const& getMetadata() const
    {
        return m_metadata;
    }

    /// @return Metadata& for the PointBuffer
    inline Metadata& getMetadataRef()
    {
        return m_metadata;
    }
    
    void resize(boost::uint32_t const& capacity, bool bExact=false);
    
    /** @name Serialization
    */
    /*! returns a boost::property_tree containing the point records, which is
        useful for dumping and such.
        \verbatim embed:rst
        ::

            0:
                X: 1.00
                Y: 2.00
                Z: 3.00
            1:
                X: 1.00
                Y: 2.00
                Z: 3.00

        .. note::

            If you want to print out details about the fields, e.g. the byte
            offset or the datatype, use the :cpp:func:`pdal::Schema::getPtree()` dumper.
        \endverbatim
    */
    boost::property_tree::ptree toPTree() const;

    pdal::Bounds<double> calculateBounds(bool bis3d=true) const;

    /// Copies dimensions from the given PointBuff
    static void copyLikeDimensions( PointBuffer const& source, 
                                    PointBuffer& destination, 
                                    DimensionMap const& dimensions,
                                    boost::uint32_t source_starting_position, 
                                    boost::uint32_t destination_starting_position,
                                    boost::uint32_t howMany);
    
    static DimensionMap* mapDimensions(PointBuffer const& source, PointBuffer const& destination);
    
    /** @name private attributes
    */
private:
    Schema m_schema;
    std::vector<boost::uint8_t> m_data;
    boost::uint32_t m_numPoints;
    boost::uint32_t m_capacity;
    Bounds<double> m_bounds;

    // We cache m_schema.getByteSize() here because it would end up
    // being dereferenced for every point read otherwise.
    schema::size_type m_byteSize;

    Metadata m_metadata;

    template<class T> static void scale(Dimension const& source_dimension,
                                 Dimension const& destination_dimension,
                                 T& value);
};

template <class T>
inline void PointBuffer::setField(pdal::Dimension const& dim, boost::uint32_t pointIndex, T value)
{
    if (dim.getPosition() == -1)
    {
        // this is a little harsh, but we'll keep it for now as we shake things out
        throw buffer_error("This dimension has no identified position in a schema. Use the setRawField method to access an arbitrary byte position.");
    }
    
    boost::uint64_t point_start_byte_position = static_cast<boost::uint64_t>(pointIndex) * static_cast<boost::uint64_t>(m_byteSize); 
    boost::uint64_t offset = point_start_byte_position + dim.getByteOffset();

#ifdef DEBUG
    boost::uint64_t array_size = static_cast<boost::uint64_t>(m_capacity) * static_cast<boost::uint64_t>(m_byteSize); 
    assert(offset + sizeof(T) <= array_size);
#endif

    boost::uint8_t* p = (boost::uint8_t*)&(m_data.front()) + offset;

    if (sizeof(T) == dim.getByteSize())
    {
        // Winner, winner, chicken dinner. We're not going to try to
        // do anything magical. It's up to you to get the interpretation right.
        *(T*)(void*)p = value;
        return;
    }

    T output(0);
    output = boost::lexical_cast<T>(value);
    *(T*)(void*)p = output;


}

template <class T>
inline  T const& PointBuffer::getField(pdal::Dimension const& dim, boost::uint32_t pointIndex) const
{
    if (dim.getPosition() == -1)
    {
        // this is a little harsh, but we'll keep it for now as we shake things out
        throw buffer_error("This dimension has no identified position in a schema.");
    }

    boost::uint64_t point_start_byte_position = static_cast<boost::uint64_t>(pointIndex) * static_cast<boost::uint64_t>(m_byteSize); 
    boost::uint64_t offset = point_start_byte_position + dim.getByteOffset();

#ifdef DEBUG
    // This test ends up being somewhat expensive when run for every field 
    // for every point. 
    boost::uint64_t array_size = static_cast<boost::uint64_t>(m_capacity) * static_cast<boost::uint64_t>(m_byteSize); 
    
    if (offset + sizeof(T) > array_size)
    {
        std::ostringstream oss;
        oss << "Offset for given dimension is off the end of the buffer!";
        throw buffer_error(oss.str());
    }

    assert(offset + sizeof(T) <= array_size );
#endif

    boost::uint8_t const* p = (boost::uint8_t const*)&(m_data.front()) + offset;
    T const& output = *(T const*)(void const*)p;
    return output;
}


#ifdef PDAL_COMPILER_MSVC
// the writeScaledData function causes lots of conversion warnings which
// are real but which we will ignore: blame the filter's user for casting badly
#  pragma warning(push)
#  pragma warning(disable: 4244)  // conversion from T1 to T2, possible loss of data
#endif

inline void PointBuffer::scaleData(PointBuffer const& source,
                            PointBuffer& destination,
                            Dimension const& source_dimension,
                            Dimension const& destination_dimension,
                            boost::uint32_t source_index,
                            boost::uint32_t destination_index)
{
    if (source_dimension.getInterpretation() == dimension::Float)
    {
        if (source_dimension.getByteSize() == 4)
        {
            float v = source.getField<float>(source_dimension, source_index);
            scale(source_dimension,
                  destination_dimension,
                  v);
            if (destination_dimension.getInterpretation() == dimension::SignedInteger)
            {
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension::Float to dimension::SignedInteger of size >8");
                return;
            }
        }
        else if (source_dimension.getByteSize() == 8)
        {
            double v = source.getField<double>(source_dimension, source_index);
            scale(source_dimension,
                  destination_dimension,
                  v);
            if (destination_dimension.getInterpretation() == dimension::SignedInteger)
            {
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension::Float to dimension::SignedInteger of size >8");
                return;
            }
        }
        else
        {
            std::ostringstream oss;
            oss << "Unable to interpret Float of size '" << source_dimension.getByteSize() <<"'";
            throw pdal_error(oss.str());
        }
    }

    if (source_dimension.getInterpretation() == dimension::SignedInteger)
    {

        if (destination_dimension.getInterpretation() == dimension::SignedInteger)
        {
            if (source_dimension.getByteSize() == 1)
            {
                boost::int8_t v = source.getField<boost::int8_t>(source_dimension, source_index);
                scale(source_dimension,
                      destination_dimension,
                      v);
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 1 to SignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 2)
            {
                boost::int16_t v = source.getField<boost::int16_t>(source_dimension, source_index);
                scale(source_dimension,
                      destination_dimension,
                      v);
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 2 to SignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 4)
            {
                boost::int32_t v = source.getField<boost::int32_t>(source_dimension, source_index);
                scale(source_dimension,
                      destination_dimension,
                      v);
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 4 to SignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 8)
            {
                boost::int64_t v = source.getField<boost::int64_t>(source_dimension, source_index);
                scale(source_dimension,
                      destination_dimension,
                      v);
                if (destination_dimension.getByteSize() == 1)
                    destination.setField<boost::int8_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 2)
                    destination.setField<boost::int16_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 4)
                    destination.setField<boost::int32_t>(destination_dimension, destination_index, v);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<boost::int64_t>(destination_dimension, destination_index, v);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 8 to SignedInteger of size > 8");
                return;
            }
            else
            {
                throw pdal_error("Unable to convert dimension::SignedInteger >8 to dimension::SignedInteger of size >8");
            }

        }
        else if (destination_dimension.getInterpretation() == dimension::Float)
        {

            if (source_dimension.getByteSize() == 1)
            {
                boost::int8_t v = source.getField<boost::int8_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 1 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 2)
            {
                boost::int16_t v = source.getField<boost::int16_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 2 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 4)
            {
                boost::int32_t v = source.getField<boost::int32_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 4 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 8)
            {
                boost::int64_t v = source.getField<boost::int64_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from SignedInteger 8 to Float of size > 8");
                return;
            }
            else
            {
                throw pdal_error("Unable to convert dimension::SignedInteger >8 to dimension::Float of size >8");
            }

        }
    }

    if (source_dimension.getInterpretation() == dimension::UnsignedInteger)
    {

        if (destination_dimension.getInterpretation() == dimension::UnsignedInteger)
        {
            if (source_dimension.getByteSize() == 1)
            {
                boost::uint8_t v = source.getField<boost::uint8_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);

                if (destination_dimension.getByteSize() == 1)
                {
                    boost::uint8_t i = destination_dimension.removeScaling<boost::uint8_t>(d);
                    destination.setField<boost::uint8_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 2)
                {
                    boost::uint16_t i = destination_dimension.removeScaling<boost::uint16_t>(d);
                    destination.setField<boost::uint16_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 4)
                {
                    boost::uint32_t i = destination_dimension.removeScaling<boost::uint32_t>(d);
                    destination.setField<boost::uint32_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 8)
                {
                    boost::uint64_t i = destination_dimension.removeScaling<boost::uint64_t>(d);
                    destination.setField<boost::uint64_t>(destination_dimension, destination_index, i);
                }
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 8 to UnsignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 2)
            {
                boost::uint16_t v = source.getField<boost::uint16_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);

                if (destination_dimension.getByteSize() == 1)
                {
                    boost::uint8_t i = destination_dimension.removeScaling<boost::uint8_t>(d);
                    destination.setField<boost::uint8_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 2)
                {
                    boost::uint16_t i = destination_dimension.removeScaling<boost::uint16_t>(d);
                    destination.setField<boost::uint16_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 4)
                {
                    boost::uint32_t i = destination_dimension.removeScaling<boost::uint32_t>(d);
                    destination.setField<boost::uint32_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 8)
                {
                    boost::uint64_t i = destination_dimension.removeScaling<boost::uint64_t>(d);
                    destination.setField<boost::uint64_t>(destination_dimension, destination_index, i);
                }
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 2 to UnsignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 4)
            {
                boost::uint32_t v = source.getField<boost::uint32_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);

                if (destination_dimension.getByteSize() == 1)
                {
                    boost::uint8_t i = destination_dimension.removeScaling<boost::uint8_t>(d);
                    destination.setField<boost::uint8_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 2)
                {
                    boost::uint16_t i = destination_dimension.removeScaling<boost::uint16_t>(d);
                    destination.setField<boost::uint16_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 4)
                {
                    boost::uint32_t i = destination_dimension.removeScaling<boost::uint32_t>(d);
                    destination.setField<boost::uint32_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 8)
                {
                    boost::uint64_t i = destination_dimension.removeScaling<boost::uint64_t>(d);
                    destination.setField<boost::uint64_t>(destination_dimension, destination_index, i);
                }
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 4 to UnsignedInteger of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 8)
            {
                boost::uint64_t v = source.getField<boost::uint64_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);

                if (destination_dimension.getByteSize() == 1)
                {
                    boost::uint8_t i = destination_dimension.removeScaling<boost::uint8_t>(d);
                    destination.setField<boost::uint8_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 2)
                {
                    boost::uint16_t i = destination_dimension.removeScaling<boost::uint16_t>(d);
                    destination.setField<boost::uint16_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 4)
                {
                    boost::uint32_t i = destination_dimension.removeScaling<boost::uint32_t>(d);
                    destination.setField<boost::uint32_t>(destination_dimension, destination_index, i);
                }
                else if (destination_dimension.getByteSize() == 8)
                {
                    boost::uint64_t i = destination_dimension.removeScaling<boost::uint64_t>(d);
                    destination.setField<boost::uint64_t>(destination_dimension, destination_index, i);
                }
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 8 to UnsignedInteger of size > 8");
                return;
            }
            else
            {
                throw pdal_error("Unable to convert dimension::UnsignedInteger >8 to dimension::UnsignedInteger of size >8");
            }

        }
        else if (destination_dimension.getInterpretation() == dimension::Float)
        {

            if (source_dimension.getByteSize() == 1)
            {
                boost::uint8_t v = source.getField<boost::uint8_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 1 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 2)
            {
                boost::uint16_t v = source.getField<boost::uint16_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 2 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 4)
            {
                boost::uint32_t v = source.getField<boost::uint32_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 4 to Float of size > 8");
                return;
            }
            else if (source_dimension.getByteSize() == 8)
            {
                boost::uint64_t v = source.getField<boost::uint64_t>(source_dimension, source_index);
                double d = source_dimension.applyScaling<double>(v);
                d = destination_dimension.removeScaling<double>(d);
                if (destination_dimension.getByteSize() == 4)
                    destination.setField<float>(destination_dimension, destination_index, (float)d);
                else if (destination_dimension.getByteSize() == 8)
                    destination.setField<double>(destination_dimension, destination_index, d);
                else
                    throw pdal_error("Unable to convert dimension from UnsignedInteger 8 to Float of size > 8");
                return;
            }
            else
            {
                throw pdal_error("Unable to convert dimension::UnsignedInteger >8 to dimension::Float of size >8");
            }

        }
    }

    throw pdal_error("Dimension data type unable to be scaled because it is Undefined");
}

#ifdef PDAL_COMPILER_MSVC
#  pragma warning(pop)
#endif


#ifdef PDAL_COMPILER_MSVC
// when template T is know, std::numeric_limits<T>::is_exact is a constant...
#  pragma warning(push)
#  pragma warning(disable: 4127)  // conditional expression is constant
#endif
// #pragma GCC diagnostic ignored "-Wfloat-equal"
// #pragma GCC diagnostic push

template <class T>
inline void PointBuffer::scale(Dimension const& source_dimension,
                           Dimension const& destination_dimension,
                           T& value)
{

    double v = static_cast<double>(value);
    double out = (v*source_dimension.getNumericScale() + source_dimension.getNumericOffset() - destination_dimension.getNumericOffset())/destination_dimension.getNumericScale();

    T output = static_cast<T>(out);

    if (std::numeric_limits<T>::is_exact) //
    {
        if (Utils::compare_distance<T>(output, (std::numeric_limits<T>::max)()))
        {
            std::ostringstream oss;
            oss << "PointBuffer::scale: scale and/or offset combination causes "
                "re-scaled value to be greater than std::numeric_limits::max for dimension '" << destination_dimension.getName() << "'. " <<
                "value is: " << output << " and max() is: " << (std::numeric_limits<T>::max)();
        }
        else if (Utils::compare_distance<T>(output, (std::numeric_limits<T>::min)()))
        {
            std::ostringstream oss;
            oss << "PointBuffer::scale: scale and/or offset combination causes "
                "re-scaled value to be less than std::numeric_limits::min for dimension '" << destination_dimension.getName() << "'. " <<
                "value is: " << output << " and min() is: " << (std::numeric_limits<T>::min)();
            throw std::out_of_range(oss.str());

        }
    }
    value = output;

    return;
}

#ifdef PDAL_COMPILER_MSVC
#  pragma warning(pop)
#endif
// #pragma GCC diagnostic pop

PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const PointBuffer&);


} // namespace pdal

#endif
