/** \file
 *
 *  Contains the BlobWriter class definition.
 *
 *  Copyright (c) 2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#ifndef BLOBWRITER_H_
#define BLOBWRITER_H_

#include "BlobValue.h"
#include "MessagingDataWriter.h"
#include "I1D.h"
#include "I2D.h"
#include "I3D.h"

#include <iostream> // for cerr, since there is no logger_ in this class, but writers should not fail silently (or close to silently)

class AbstractBlobWriter
{

    /**
     *  A DataWriter for writing BlobValues, normally created via Component::newBlobWriter()
     *  See below for implementation in BlobWriter
     *  Also implemented in UniversalWriter::UniversalBlobWriter
     *
     *  \ingroup data
     */

public:

    virtual ~AbstractBlobWriter() {}

    // Parent method used in AbstractBlobWriter: needs to be re-implemented in child classes
    virtual bool write( const DataValue& value, const Timestamp& timestamp ) = 0;

    // Parent method used in AbstractBlobWriter: needs to be re-implemented in child classes
    virtual const ElementURI& getUri( void ) const = 0;

    /**
     * Simply writes the binary blob
     *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
     */
    virtual bool writeBlob( const void* bytes, size_t elements, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        blobValue_.setFromRaw( bytes, elements );
        return write( blobValue_, timestamp );
    }

    /**
    * Writes the array as a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T, size_t M>
    bool write1DArray( const Unit& unit, T( &x )[M], const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 1 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 1D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != M )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << ", but received " << M << std::endl;
            return 0;
        }
        return write1DPtr( unit, ( T* )&x, M, timestamp );
    }

    /**
    * Writes data from the class that implements as I1D a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T>
    bool write1DClass( const Unit& unit, I1D<T>& i1d, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        return write1DPtr( unit, ( const T* ) i1d.getPtr1d(), i1d.getM(), timestamp );
    }

    /**
     * Writes data from the 1-d pointer as a binary blob
     *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
     */
    template<typename T>
    bool write1DPtr( const Unit& unit, const T* values, int m, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 1 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 1D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != m )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << ", but received " << m << std::endl;
            return 0;
        }
        blobValue_.setFrom( unit, values, m );
        return write( blobValue_, timestamp );
    }


    /**
    * Writes the array as a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T, size_t M, size_t N>
    bool write2DArray( const Unit& unit, T( &x )[M][N], const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 2 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 2D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != M || getUri().getDimension2() != N )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << "-by-" << getUri().getDimension2() << ", but received " << M << "-by-" << N << std::endl;
            return 0;
        }
        blobValue_.setFrom( unit, x );
        return write( blobValue_, timestamp );
    }

    /**
    * Writes data from the class that implements as I2D a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T>
    bool write2DClass( const Unit& unit, I2D<T>& i2d, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        return write2DPtr( unit, ( const T** ) i2d.getPtr2d(), i2d.getM(), i2d.getN(), timestamp );
    }

    /**
     * Writes data from the 2-d pointer as a binary blob
     *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
     */
    template<typename T>
    bool write2DPtr( const Unit& unit, const T** values, int m, int n, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 2 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 2D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != m || getUri().getDimension2() != n )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << "-by-" << getUri().getDimension2() << ", but received " << m << "-by-" << n << std::endl;
            return 0;
        }
        blobValue_.setFrom( unit, values, m, n );
        return write( blobValue_, timestamp );
    }


    /**
    * Writes the array as a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T, size_t M, size_t N, size_t O>
    bool write3DArray( const Unit& unit, T( &x )[M][N][O], const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 3 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 3D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != M || getUri().getDimension2() != N || getUri().getDimension3() != O )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << "-by-" << getUri().getDimension2() << "-by-" << getUri().getDimension3() << ", but received " << M << "-by-" << N << "-by-" << O << std::endl;
            return 0;
        }
        blobValue_.setFrom( unit, x );
        return write( blobValue_, timestamp );
    }

    /**
    * Writes data from the class that implements as I3D a binary blob
    *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
    */
    template<typename T>
    bool write3DClass( const Unit& unit, I3D<T>& i3d, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        return write3DPtr( unit, ( const T*** ) i3d.getPtr3d(), i3d.getM(), i3d.getN(), i3d.getO(), timestamp );
    }

    /**
     * Writes data from the 3-d pointer as a binary blob
     *   ElementURI used to declare this DataWriter should have a BLOB TYPE;
     */
    template<typename T>
    bool write3DPtr( const Unit& unit, const T*** values, int m, int n, int o, const Timestamp& timestamp = Timestamp::NOT_SET_TIME )
    {
        if( getUri().getDimensions() != 3 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 3D." << std::endl;
            return 0;
        }
        else if( getUri().getDimension1() != m || getUri().getDimension2() != n || getUri().getDimension3() != o )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " expects " << getUri().getDimension1() << "-by-" << getUri().getDimension2() << "-by-" << getUri().getDimension3() << ", but received " << m << "-by-" << n << "-by-" << o << std::endl;
            return 0;
        }
        blobValue_.setFrom( unit, values, m, n, o );
        return write( blobValue_, timestamp );
    }


protected:

    AbstractBlobWriter( const ElementURI& elementUri )
        : blobValue_( elementUri.getUnit(), elementUri.getBlobType() ) {}

    BlobValue blobValue_;


private:
    // Disallow default constructor for abstract base class
    AbstractBlobWriter();

    // Disallow copy constructor
    AbstractBlobWriter( const AbstractBlobWriter& copyFrom );

};

class BlobWriter: public MessagingDataWriter, public AbstractBlobWriter
{

public:

    virtual ~BlobWriter() {}

    // Re-implement abstract method from AbstractBlobWriter
    virtual bool write( const DataValue& value, const Timestamp& timestamp )
    {
        return MessagingDataWriter::write( value, timestamp );
    }

    // Re-implement abstract method from AbstractBlobWriter
    virtual const ElementURI& getUri( void ) const
    {
        return DataWriter::getUri();
    }

protected:

    friend class Component;

    BlobWriter( Component* owner,
                DataElement& element,
                UniversalDataElement* universal,
                Logger::TimePrecisionType timePrecisionType )
        : MessagingDataWriter( owner, element, universal, timePrecisionType ),
          AbstractBlobWriter( DataWriter::getUri() )
    {}

};


#endif /* BLOBWRITER_H_ */
