/** \file
 *
 *  Contains unit tests for the Universal classes
 *
 *  Copyright (c) 2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef _UNIVERSAL_TEST_H_
#define _UNIVERSAL_TEST_H_

#include "component/TestAsyncComponent.h"
#include "data/Point3D.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "data/I3D.h"

#include <cxxtest/TestSuite.h>

class PubUniversalBlobURI: public UniversalBlobURI
{
public:

    /// Exposed Constructor for use by static initializers
    PubUniversalBlobURI( const char* fullURI, const Unit& unit,
                         BlobType blobType, unsigned short dimension1 = 0,
                         unsigned short dimension2 = 0, unsigned short dimension3 = 0 )
        : UniversalBlobURI( fullURI, unit, blobType, dimension1, dimension2, dimension3 )
    {}

};


class TinyCube: public I3D<float>
{
public:

    TinyCube( float f000 = 0, float f001 = 0, float f010 = 0, float f011 = 0,
              float f100 = 0, float f101 = 0, float f110 = 0, float f111 = 0 )
        : value_( I3D<float>::New3D( 2, 2, 2 ) )
    {
        value_[0][0][0] = f000;
        value_[0][0][1] = f001;
        value_[0][1][0] = f010;
        value_[0][1][1] = f011;
        value_[1][0][0] = f100;
        value_[1][0][1] = f101;
        value_[1][1][0] = f110;
        value_[1][1][1] = f111;
    }

    virtual ~TinyCube()
    {
        I3D<float>::Delete3D( value_ );
    };

    virtual float*** getPtr3d()
    {
        return value_;
    }

    virtual float** getPtr2d()
    {
        return value_[0];
    }

    virtual float* getPtr1d()
    {
        return value_[0][0];
    }

    virtual int getM() const
    {
        return 2;
    }

    virtual int getN() const
    {
        return 2;
    }

    virtual int getO() const
    {
        return 2;
    }

    float** operator[]( int index )
    {
        return value_[index];
    };

protected:

    float*** value_;

};

/**
 *  Unit tests for the BlobValue, BlobReader, and BlobWriter classes

 *  \ingroup data
 */
class Universal_Test : public CxxTest::TestSuite
{
public:

    /// Test 1D Read & Write
    void test1DUniversalBlobReadWrite( void )
    {
        TestAsyncComponent testComponent( "Blob_TestComponent" );
        PubUniversalBlobURI velocitycityURI( "velocitycity", Units::METER_PER_SECOND, BLOB_FLOAT64LE, 3 );

        UniversalBlobWriter* bw = testComponent.newUniversalBlobWriter( velocitycityURI );
        UniversalBlobReader* br = testComponent.newUniversalBlobReader( velocitycityURI );

        double u = .75, v = .65, w = sqrt( 1 - u * u - v * v ); // Overall speed in 1 m/s
        Point3D velocityInMPS( u, v, w );
        bw->setAccuracy( Units::METER_PER_SECOND, 0.05 );
        TS_ASSERT( bw->write1DClass( Units::METER_PER_SECOND, velocityInMPS ) );

        // Test readability
        TS_ASSERT( br->isActive() );

        //Test reading as a BlobValue
        bool success = false;
        BlobValue bv = br->readBlob( success );
        TS_ASSERT_EQUALS( bv.getByteLength(), 3 * sizeof( double ) )
        TS_ASSERT( success );
        TS_ASSERT_DELTA( ( ( double* )bv.getBytes() )[0], velocityInMPS.getU(), 0.01 );
        TS_ASSERT_DELTA( ( ( double* )bv.getBytes() )[1], velocityInMPS.getV(), 0.01 );
        TS_ASSERT_DELTA( ( ( double* )bv.getBytes() )[2], velocityInMPS.getW(), 0.01 );

        // Test reading the fixed size and unit conversion to base
        Point3D velocityInMPH = velocityInMPS * 2.23694;
        Point3D velocityOut;
        TS_ASSERT_EQUALS( br->read1DClass( Units::MILE_PER_HOUR, velocityOut ), 3 );
        TS_ASSERT_DELTA( velocityOut.getU(), velocityInMPH.getU(), 0.01 );
        TS_ASSERT_DELTA( velocityOut.getV(), velocityInMPH.getV(), 0.01 );
        TS_ASSERT_DELTA( velocityOut.getW(), velocityInMPH.getW(), 0.01 );
    }

    /// Test 3D Read & Write
    void test3DUniversalBlobReadWrite( void )
    {
        TestAsyncComponent testComponent( "Blob_TestComponent" );
        PubUniversalBlobURI radials3D( "radials3D", Units::METER_PER_SECOND, BLOB_FLOAT32LE, 2, 2, 2 );

        UniversalBlobWriter* bw = testComponent.newUniversalBlobWriter( radials3D );
        UniversalBlobReader* br = testComponent.newUniversalBlobReader( radials3D );

        TinyCube radialsInMPS( 0.000, 0.001, 0.010, 0.011, 0.100, 0.101, 0.110, 0.111 );
        bw->setAccuracy( Units::METER_PER_SECOND, 0.0001 );
        TS_ASSERT( bw->write3DClass( Units::METER_PER_SECOND, radialsInMPS ) );

        // Test readability
        TS_ASSERT( br->isActive() );

        //Test reading as a BlobValue
        bool success = false;
        BlobValue bv = br->readBlob( success );
        TS_ASSERT_EQUALS( bv.getByteLength(), 2 * 2 * 2 * sizeof( float ) )
        TS_ASSERT( success );
        for( int i = 0; i < 2; ++i )
        {
            for( int j = 0; j < 2; ++j )
            {
                for( int k = 0; k < 2; ++k )
                {
                    TS_ASSERT_DELTA( ( ( float* )bv.getBytes() )[i * 4 + j * 2 + k],
                                     radialsInMPS[i][j][k], 0.0001 );
                }
            }
        }

        // Test reading the fixed size and unit conversion to base
        TinyCube radialsOut;
        TS_ASSERT_EQUALS( br->read3DClass( Units::MILE_PER_HOUR, radialsOut ), 2 * 2 * 2 );
        for( int i = 0; i < 2; ++i )
        {
            for( int j = 0; j < 2; ++j )
            {
                for( int k = 0; k < 2; ++k )
                {
                    TS_ASSERT_DELTA( radialsOut[i][j][k],
                                     radialsInMPS[i][j][k] * 2.23694, 0.0001 );
                }
            }
        }
    }

};

#endif // _BLOB_TEST_H
