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

#ifndef BLOBREADER_H_
#define BLOBREADER_H_

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

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

class AbstractBlobReader
{

    /**
     *  Abstract parent class that adds the capability of reading BlobValues
     *  to a "normal" DataReader
     *  See below for implementation in BlobReader
     *  Also implemented in UniversalReader::UniversalBlobReader
     *
     *  \ingroup data
     */

public:

    virtual ~AbstractBlobReader()
    {}

    // Parent method used in AbstractBlobReader: needs to be re-implemented in child classes
    virtual bool read( DataValue& readTo ) = 0;

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


    BlobValue readBlob( bool& success )
    {
        success = read( blobValue_ );
        return BlobValue( blobValue_.getBytes(), blobValue_.getElements(), getUri().getUnit(), getUri().getBlobType() );
    }

    // Read into the 1-d array. Returns the number of elements written, 0 if an error.
    template<typename T, size_t M>
    int read1DArray( const Unit& unit, T( &x )[M] )
    {
        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 read1DPtr( unit, ( T* )&x, M );
    }

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

    // Read into the 1-d pointer. Returns the number of elements written, 0 if an error.
    template<typename T>
    int read1DPtr( const Unit& unit, T* values, int m )
    {
        if( getUri().getDimensions() != 1 )
        {
            std::cerr << "URI " << Str( getUri() ).cStr() << " is not 1D." << std::endl;
            return 0;
        }
        size_t elements = read( blobValue_ ) ? MIN( m, ( int )blobValue_.getElements() ) : 0;
        if( elements > 0 )
        {
            blobValue_.copyTo( unit, values, m );
        }
        return elements;
    }

    // Read into the 2-d array. Returns the number of elements written, 0 if an error.
    template<typename T, size_t M, size_t N>
    int read2DArray( const Unit& unit, T( &x )[M][N] )
    {
        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;
        }
        size_t elements = read( blobValue_ ) ? MIN( M * N, blobValue_.getElements() ) : 0;
        if( elements > 0 )
        {
            blobValue_.copyTo( unit, x );
        }
        return elements;
    }

    // Read into the 2-d class that inherits from the interface I2D.
    template<typename T>
    int read2DClass( const Unit& unit, I2D<T>& i2d )
    {
        return read2DPtr( unit, i2d.getPtr2d(), i2d.getM(), i2d.getN() );
    }

    // Read into the 2-d pointer. Returns the number of elements written, 0 if an error.
    template<typename T>
    int read2DPtr( const Unit& unit, T** values, int m, int n )
    {
        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;
        }
        size_t elements = read( blobValue_ ) ? MIN( m * n, ( int )blobValue_.getElements() ) : 0;
        if( elements > 0 )
        {
            blobValue_.copyTo( unit, values, elements / n, n );
        }
        return elements;
    }

    // Read into the 3-d array. Returns the number of elements written, 0 if an error.
    template<typename T, size_t M, size_t N, size_t O>
    int read3DArray( const Unit& unit, T( &x )[M][N][O] )
    {
        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;
        }
        size_t elements = read( blobValue_ ) ? MIN( M * N * O, blobValue_.getElements() ) : 0;
        if( elements > 0 )
        {
            blobValue_.copyTo( unit, x );
        }
        return elements;
    }

    // Read into the 3-d class that inherits from the interface I3D.
    template<typename T>
    int read3DClass( const Unit& unit, I3D<T>& i3d )
    {
        return read3DPtr( unit, i3d.getPtr3d(), i3d.getM(), i3d.getN(), i3d.getO() );
    }

    // Read into the 3-d pointer. Returns the number of elements written, 0 if an error.
    template<typename T>
    int read3DPtr( const Unit& unit, T*** values, int m, int n, int o )
    {
        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;
        }
        size_t elements = read( blobValue_ ) ? MIN( m * n * o, ( int )blobValue_.getElements() ) : 0;
        if( elements > 0 )
        {
            blobValue_.copyTo( unit, values, elements / n / o, n, o );
        }
        return elements;
    }

protected:

    BlobValue blobValue_;

    AbstractBlobReader( const ElementURI &uri )
        : blobValue_( uri.getUnit(), uri.getBlobType() )
    {}

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

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


};

class BlobReader: public DataReader, public AbstractBlobReader
{

    /**
     *  A DataReader for reading BlobValues, normally created via
     *  Component::newBlobReader()
     *
     *  \ingroup data
     */

public:

    // Re-implement abstract method from AbstractBlobReader
    virtual bool read( DataValue& readTo )
    {
        return DataReader::read( readTo );
    }

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

protected:

    friend class Component;

    BlobReader( Component* owner,
                DataElement& element,
                const Unit& defaultUnit )
        : DataReader( owner, element, defaultUnit ),
          AbstractBlobReader( getUri() )
    {}

};

#endif /* BLOBREADER_H_ */
