/** \file
 *
 *  Contains the BlobValue class definition.
 *
 *  Copyright (c) 2007,2008,2009,2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#ifndef BLOBVALUE_H_
#define BLOBVALUE_H_

#include "data/BlobType.h"
#include "data/StrValue.h"
#include <stdio.h>

class DataWriter;
class ElementURI;
class RingBufferVoid;

/**
 *  Wraps a StrValue as an arbitrary collection of binary values, with units.
 *  Note that the values are not internally converted and stored in base SI units.
 *
 * \ingroup data
 */
class BlobValue : public StrValue
{
public:

    static size_t ElementSize( const BlobType& blobType )
    {
        return ELEMENT_SIZE[ blobType >= NOT_BLOB && blobType < BLOB_TYPE_COUNT ? blobType : NOT_BLOB ];
    }

    static const size_t ELEMENT_SIZE[BLOB_TYPE_COUNT];

    /// Empty constructior
    BlobValue( const Unit& unit, const BlobType blobType );

    /// ElementURI constructior
    BlobValue( const ElementURI& elementURI );

    /// Copy Constructor
    BlobValue( const BlobValue& blobValue );

    /// raw constructor
    BlobValue( const void* bytes, size_t elements, const Unit& unit, const BlobType blobType );

    /// double array pointer constructor
    BlobValue( const double* array, size_t elements, const Unit& unit, const BlobType blobType = BLOB_FLOAT64LE );

    /// float array pointer constructor
    BlobValue( const float* array, size_t elements, const Unit& unit, const BlobType blobType = BLOB_FLOAT32LE );

    /// int array pointer constructor
    BlobValue( const int* array, size_t elements, const Unit& unit, const BlobType blobType = BLOB_INT32LE );

    /// Destructor
    virtual ~BlobValue()
    {}
    ;

    /// Return a pointer to a new copy
    virtual BlobValue* copy() const;

    // set from  raw data
    bool setFromRaw( const void* bytes, size_t elements );

    // set from a 1-d array (of double, float or int)
    template<typename T>
    bool setFrom( const Unit& unit, const T* array, size_t elements );

    // set from a 2-d array (of double, float or int)
    template<typename T, size_t m, size_t n>
    bool setFrom( const Unit& unit, const T( &array )[m][n] )
    {
        size_t length = m * n * ElementSize( blobType_ );
        m_ = m;
        n_ = n;
        o_ = 0;
        value_.set( NULL, length );
        if( conversionNeeded<T>( unit ) )
        {
            for( size_t i = 0; i < m; ++i )
            {
                for( size_t j = 0; j < n; ++j )
                {
                    set( i * n + j, unit, array[i][j] );
                }
            }
        }
        else
        {
            length = n * ElementSize( blobType_ );
            for( size_t i = 0; i < m; ++i )
            {
                size_t offset = i * length;
                memcpy( ( char* )value_.cStr() + offset, ( const char* )( array[i] ), length );
            }
        }
        return true;
    }

    // set from a 2-d array pointer (of double, float or int)
    template<typename T>
    bool setFrom( const Unit& unit, const T** array, int m, int n );

    // set from a 3-d array (of double, float or int)
    template<typename T, size_t m, size_t n, size_t o>
    bool setFrom( const Unit& unit, const T( &array )[m][n][o] )
    {
        size_t length = m * n * o * ElementSize( blobType_ );
        m_ = m;
        n_ = n;
        o_ = o;
        value_.set( NULL, length );
        if( conversionNeeded<T>( unit ) )
        {
            for( size_t i = 0; i < m; ++i )
            {
                for( size_t j = 0; j < n; ++j )
                {
                    for( size_t k = 0; k < o; ++k )
                    {
                        set( i * n * o + j * o + k, unit, array[i][j][k] );
                    }
                }
            }
        }
        else
        {
            length = o * ElementSize( blobType_ );
            for( size_t i = 0; i < m; ++i )
            {
                for( size_t j = 0; j < n; ++j )
                {
                    size_t offset = ( i * n + j ) * length;
                    memcpy( ( char* )value_.cStr() + offset, ( const char* )( array[i][j] ), length );
                }
            }
        }
        return true;
    }

    // set from a 3-d array pointer (of double, float or int)
    template<typename T>
    bool setFrom( const Unit& unit, const T*** array, int m, int n, int o );

    // set from a 1-d, 2-d, or 3-d flattened array pointer (of double, float or int)
    // if 1-d or 2-d, o == 0
    // if 1-d, n == 0
    template<typename T>
    bool setFrom( const Unit& unit, const T* array, int m, int n, int o );

    /// set from bool value
    virtual bool setFrom( const Unit& unit, bool value )
    {
        return setFrom( unit, &value, 1, 0, 0 );
    }

    /// set from unsigned char value
    virtual bool setFrom( const Unit& unit, unsigned char value )
    {
        return setFrom( unit, &value, 1, 0, 0 );
    }

    /// set from double value
    virtual bool setFrom( const Unit& unit, double value )
    {
        return setFrom( unit, &value, 1, 0, 0 );
    }

    /// set from float value
    virtual bool setFrom( const Unit& unit, float value )
    {
        return setFrom( unit, &value, 1, 0, 0 );
    }

    /// set int valu
    virtual bool setFrom( const Unit& unit, int value )
    {
        return setFrom( unit, &value, 1, 0, 0 );
    }

    /// set DataValue value
    virtual bool setFrom( const DataValue& value );

    /// Set an individual value
    bool set( size_t index, const Unit& unit, double value );

    // copy to a 1-d array (of double, float or int), returns # of elements copied
    template<typename T>
    int copyTo( const Unit& unit, T* array, size_t elements ) const;

    // copy to a 1-d class that inherits from the interface I1D.
    template<typename T>
    int copyTo( const Unit& unit, I1D<T>& i1d )
    {
        return copyTo( unit, i1d.getPtr1d(), i1d.getM() );
    }

    // copy to a 2-d array (of doubles, float, or int)
    template<typename T, size_t m, size_t n>
    int copyTo( const Unit& unit, T( &array )[m][n] ) const
    {
        size_t elements = m * n;
        if( elements > getElements() )
        {
            elements = getElements() / n * n;
        }
        if( conversionNeeded<T>( unit ) )
        {
            double value;
            for( unsigned int i = 0; i < elements / n; ++i )
            {
                for( unsigned int j = 0; j < elements / m; ++j )
                {
                    get( i * n + j, unit, value );
                    array[i][j] = value;
                }
            }
        }
        else
        {
            size_t length = n * sizeof( T );
            for( unsigned int i = 0; i < elements / n; ++i )
            {
                size_t offset = i * length;
                memcpy( ( char* )( array[i] ), value_.cStr() + offset, length );
            }
        }
        return elements;
    }

    // copy to a 2-d array pointer (of double, float or int), returns # of elements copied
    template<typename T>
    int copyTo( const Unit& unit, T** array, int m, int n ) const;

    // copy to a 3-d array (of doubles, float, or int)
    template<typename T, size_t m, size_t n, size_t o>
    int copyTo( const Unit& unit, T( &array )[m][n][o] ) const
    {
        size_t elements = m * n * o;
        if( elements > getElements() )
        {
            elements = getElements() / n / o * n * o;
        }
        if( conversionNeeded<T>( unit ) )
        {
            double value;
            for( unsigned int i = 0; i < elements / n / o ; ++i )
            {
                for( unsigned int j = 0; j < n; ++j )
                {
                    for( unsigned int k = 0; k < o; ++k )
                    {
                        get( i * n * o + j * o + k, unit, value );
                        array[i][j][k] = value;
                    }
                }
            }
        }
        else
        {
            size_t length = o * sizeof( T );
            for( unsigned int i = 0; i < elements / n / o; ++i )
            {
                for( unsigned int j = 0; j < n; ++j )
                {
                    size_t offset = ( i * n + j ) * length;
                    memcpy( ( char* )( array[i][j] ), value_.cStr() + offset, length );
                }
            }
        }
        return elements;
    }

    // copy to a 3-d array pointer (of double, float or int), returns # of elements copied
    template<typename T>
    int copyTo( const Unit& unit, T*** array, int m, int n, int o ) const;

    /// Copy value to bool
    virtual bool copyTo( const Unit& unit, bool& assignTo ) const
    {
        return copyTo( unit, &assignTo, 1 );
    }

    /// Copy value to unsigned char
    virtual bool copyTo( const Unit& unit, unsigned char& assignTo ) const
    {
        return copyTo( unit, &assignTo, 1 );
    }

    /// Copy value to double
    virtual bool copyTo( const Unit& unit, double& assignTo ) const
    {
        return copyTo( unit, &assignTo, 1 );
    }

    /// Copy value to float
    virtual bool copyTo( const Unit& unit, float& assignTo ) const
    {
        return copyTo( unit, &assignTo, 1 );
    }

    /// Copy value to unsigned int
    virtual bool copyTo( const Unit& unit, int& assignTo ) const
    {
        return copyTo( unit, &assignTo, 1 );
    }

    /// Get an individual value
    bool get( size_t index, const Unit& unit, double& value ) const;

    size_t getElements() const
    {
        return ElementSize( blobType_ ) == 0 ? 0 : value_.length() / ElementSize( blobType_ );
    }

    size_t getByteLength() const
    {
        return value_.length();
    }

    void* getBytes() const
    {
        return ( void* )value_.cStr();
    }

    // as a Str
    virtual Str toString() const
    {
        return DataValue::toString();
    }

    // as a Str
    virtual Str toString( const Unit& unit ) const;

    BlobType getBlobType() const
    {
        return blobType_;
    }

    int getM() const
    {
        return m_;
    }

    int getN() const
    {
        return n_;
    }

    int getO() const
    {
        return o_;
    }

    int getRank() const
    {
        return o_ > 0 ? 3 : ( n_ > 0 ? 2 : ( m_ > 0 ? 1 : 0 ) );
    }

    /// Load the data from the a binary representation in the buffer
    /// Typically just a memcopy of the variable, though endianness
    /// may come into it.
    virtual void fromBinary( const void *buffer, unsigned short typeSize );

protected:

    friend class Blob_Test;

    // returns true if conversion needed
    template<typename T>
    bool conversionNeeded( const Unit& unit ) const;

    /// Set an individual value, without bounds checking or scaling
    template<typename T>
    void setElement( size_t index, T value, bool flip );

    /// Get an individual value, without bounds checking or scaling
    template<typename T>
    void getElement( size_t index, T& value, bool flip ) const;

    BlobType blobType_;

    int m_, n_, o_;

};

#endif /*BLOBVALUE_H_*/
