/** \file
 *
 *  Contains unit tests for the BlobValue, BlobReader, and BlobWriter classes
 *
 *  Copyright (c) 2013 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef _BLOB_TEST_H_
#define _BLOB_TEST_H_

#include "component/TestAsyncComponent.h"
#include "BlobReader.h"
#include "BlobValue.h"
#include "BlobWriter.h"
#include "data/I2D.h"

#include <cxxtest/TestSuite.h>

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

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

    // Test basic BlobValue operations
    void testBlobValue( void )
    {
        BlobURI waterTemps( "Test", "waterTemps", Units::CELSIUS, BLOB_FLOAT64LE, 3 );

        // Test constructor with more than the fixed size
        const double tempsInC[] = { -273.15, 0, 100, 374};
        BlobValue bv( tempsInC, 4,  waterTemps.getUnit(), waterTemps.getBlobType() );
        TS_ASSERT_EQUALS( bv.getElements(), 4ul );
        TS_ASSERT_EQUALS( bv.getByteLength(), 32ul );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 0 ), -273.15, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 1 ), 0, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 2 ), 100, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 3 ), 374, 1.0 );

        // Test accessing more than the fixed size and unit conversion to default
        double tempsInF[4] = {};
        TS_ASSERT_EQUALS( bv.copyTo( Units::FAHRENHEIT, tempsInF, 4 ), 4 );
        TS_ASSERT_DELTA( tempsInF[0], -459.67, 0.01 );
        TS_ASSERT_DELTA( tempsInF[1], 32.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[2], 212.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[3], 705.0, 1.0 );

        // Test accessing less than the written size and unit conversion to default
        double tempsInF2[2] = {};
        TS_ASSERT_EQUALS( bv.copyTo( Units::FAHRENHEIT, tempsInF2, 2 ), 2 );
        TS_ASSERT_DELTA( tempsInF2[0], -459.67, 0.01 );
        TS_ASSERT_DELTA( tempsInF2[1], 32.0, 0.01 );

        // Test accessing the fixed size and unit conversion to base
        double tempsInK[3] = {};
        TS_ASSERT_EQUALS( bv.copyTo( Units::KELVIN, tempsInK, 3 ), 3 );
        TS_ASSERT_DELTA( tempsInK[0], 0.0, 0.01 );
        TS_ASSERT_DELTA( tempsInK[1], 273.15, 0.01 );
        TS_ASSERT_DELTA( tempsInK[2], 373.15, 0.01 );

    }

    /// Test Read & Write
    void testReadWrite( void )
    {
        TestAsyncComponent testComponent( "Blob_TestComponent" );
        BlobURI waterTemps( testComponent.getName(), "waterTemps", Units::FAHRENHEIT, BLOB_FLOAT64LE, 4 );
        BlobWriter* bw = testComponent.newBlobWriter( waterTemps );
        BlobReader* br = testComponent.newBlobReader( waterTemps );

        // Test writing the fixed size
        const double tempsInC[] = { -273.15, 0, 100, 374};
        TS_ASSERT( bw->write1DArray( Units::CELSIUS, tempsInC ) );

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

        //Test reading as a BlobValue
        bool success = false;
        BlobValue bv = br->readBlob( success );
        TS_ASSERT( success );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 0 ), -459.67, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 1 ), 32.0, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 2 ), 212.0, 0.01 );
        TS_ASSERT_DELTA( *( ( double* )bv.getBytes() + 3 ), 705.0, 1.0 );

        // Test reading the fixed size and unit conversion to default
        double tempsInF[4] = {};
        TS_ASSERT_EQUALS( br->read1DArray( Units::FAHRENHEIT, tempsInF ), 4 );
        TS_ASSERT_DELTA( tempsInF[0], -459.67, 0.01 );
        TS_ASSERT_DELTA( tempsInF[1], 32.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[2], 212.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[3], 705.0, 1.0 );

        // Test reading the written size and unit conversion to default
        double tempsInF2[4] = {};
        TS_ASSERT_EQUALS( br->read1DArray( Units::FAHRENHEIT, tempsInF2 ), 4 );
        TS_ASSERT_DELTA( tempsInF2[0], -459.67, 0.01 );
        TS_ASSERT_DELTA( tempsInF2[1], 32.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF2[2], 212.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF2[3], 705.0, 1.0 );

        // Test reading the fixed size and unit conversion to base
        double tempsInK[4] = {};
        TS_ASSERT_EQUALS( br->read1DArray( Units::KELVIN, tempsInK ), 4 );
        TS_ASSERT_DELTA( tempsInK[0], 0.0, 0.01 );
        TS_ASSERT_DELTA( tempsInK[1], 273.15, 0.01 );
        TS_ASSERT_DELTA( tempsInK[2], 373.15, 0.01 );
        TS_ASSERT_DELTA( tempsInK[3], 647.15, 0.01 );
    }

    /// Test Read & Write with cast conversions
    void testReadWriteConversions( void )
    {
        TestAsyncComponent testComponent( "Blob_TestComponent" );
        BlobURI waterTemps( testComponent.getName(), "waterTemps32BE", Units::FAHRENHEIT, BLOB_FLOAT32BE, 4 );
        BlobWriter* bw = testComponent.newBlobWriter( waterTemps );
        BlobReader* br = testComponent.newBlobReader( waterTemps );

        // Test writing the fixed size
        const double tempsInC[] = { -273.15, 0, 100, 374};
        TS_ASSERT( bw->write1DArray( Units::CELSIUS, tempsInC ) );

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

        //Test reading as a BlobValue
        bool success = false;
        BlobValue bv = br->readBlob( success );
        float fVal = nanf( "" );
        bv.getElement( 0, fVal, true );
        TS_ASSERT_DELTA( fVal, -459.67, 0.01 );
        bv.getElement( 1, fVal, true );
        TS_ASSERT_DELTA( fVal, 32, 0.01 );
        bv.getElement( 2, fVal, true );
        TS_ASSERT_DELTA( fVal, 212, 0.01 );
        bv.getElement( 3, fVal, true );
        TS_ASSERT_DELTA( fVal, 705.0, 1.0 );

        // Test reading the fixed size and unit conversion to default
        double tempsInF[4] = {};
        TS_ASSERT_EQUALS( br->read1DArray( Units::FAHRENHEIT, tempsInF ), 4 );
        TS_ASSERT_DELTA( tempsInF[0], -459.67, 0.01 );
        TS_ASSERT_DELTA( tempsInF[1], 32.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[2], 212.0, 0.01 );
        TS_ASSERT_DELTA( tempsInF[3], 705.0, 1.0 );

        // Test reading the fixed size and unit conversion to base
        double tempsInK[4] = {};
        TS_ASSERT_EQUALS( br->read1DArray( Units::KELVIN, tempsInK ), 4 );
        TS_ASSERT_DELTA( tempsInK[0], 0.0, 0.01 );
        TS_ASSERT_DELTA( tempsInK[1], 273.15, 0.01 );
        TS_ASSERT_DELTA( tempsInK[2], 373.15, 0.01 );
        TS_ASSERT_DELTA( tempsInK[3], 647.15, 0.01 );
    }

    // Test basic BlobValue operations
    void testBlobValue2D( void )
    {
        const int m = 2, n = 3;
        BlobURI waterTemps( "Test", "temp2D", Units::FAHRENHEIT, BLOB_FLOAT32LE, m, n );

        // Test basic operation with non-pointer arrays
        const float dataF[m][n] = { { -40, 0, 32 }, { -459.67, 212, 705 } };
        const float dataC[m][n] = { { -40, -17.78, 0 }, { -273.15, 100, 374 } };
        float dataOut[m][n];
        BlobValue bv( waterTemps.getUnit(), waterTemps.getBlobType() );
        bv.setFrom( Units::FAHRENHEIT, dataF );
        bv.copyTo( Units::FAHRENHEIT, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( dataF[i][j], dataOut[i][j], 1.f );
            }
        }
        bv.copyTo( Units::CELSIUS, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( dataC[i][j], dataOut[i][j], 1.f );
            }
        }
        bv.setFrom( Units::CELSIUS, dataC );
        bv.copyTo( Units::CELSIUS, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( dataC[i][j], dataOut[i][j], 1.f );
            }
        }
        bv.copyTo( Units::FAHRENHEIT, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( dataF[i][j], dataOut[i][j], 1.f );
            }
        }

        // Test basic operation with pointer arrays
        float** pDataF = I2D<float>::New2D( m, n );
        float** pDataC = I2D<float>::New2D( m, n );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                pDataF[i][j] = dataF[i][j];
                pDataC[i][j] = dataC[i][j];
            }
        }
        float** pDataOut = I2D<float>::New2D( m, n );
        bv.setFrom( Units::FAHRENHEIT, ( const float** ) pDataF, m, n );
        bv.copyTo( Units::FAHRENHEIT, pDataOut, m, n );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( pDataF[i][j], pDataOut[i][j], 1.f );
            }
        }
        bv.copyTo( Units::CELSIUS, pDataOut, m, n );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( pDataC[i][j], pDataOut[i][j], 1.f );
            }
        }
        bv.setFrom( Units::CELSIUS, ( const float** ) pDataC, m, n );
        bv.copyTo( Units::CELSIUS, pDataOut, m, n );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( pDataC[i][j], pDataOut[i][j], 1.f );
            }
        }
        bv.copyTo( Units::FAHRENHEIT, pDataOut, m, n );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                TS_ASSERT_DELTA( pDataF[i][j], pDataOut[i][j], 1.f );
            }
        }
        I2D<float>::Delete2D( pDataF );
        I2D<float>::Delete2D( pDataC );
        I2D<float>::Delete2D( pDataOut );

    }

    // Test basic BlobValue operations
    void testBlobValue3D( void )
    {
        const int m = 2, n = 3, o = 4;
        BlobURI waterTemps( "Test", "temp3D", Units::FAHRENHEIT, BLOB_FLOAT32LE, m, n );

        // Test basic operation with non-pointer arrays
        const float dataF[m][n][o] = { { { -40, 50, 140, 230}, {0, 90, 180, 270}, {32, 122, 212, 302} },
            { { -459.67, -369.67, -279.67, -189.67}, {212, 302, 392, 482}, {705, 795, 885, 975} }
        };
        const float dataC[m][n][o] = { { { -40, 10, 60, 110}, { -17.78, 32.22, 82.22, 132.22}, {0, 50, 100, 150} },
            { { -273.15, -223.15, -173.15, -123.15}, {100, 150, 200, 250}, {374, 424, 474, 524} }
        };
        float dataOut[m][n][o];
        BlobValue bv( waterTemps.getUnit(), waterTemps.getBlobType() );
        bv.setFrom( Units::FAHRENHEIT, dataF );
        bv.copyTo( Units::FAHRENHEIT, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( dataF[i][j][k], dataOut[i][j][k], 1.f );
                }
            }
        }
        bv.copyTo( Units::CELSIUS, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( dataC[i][j][k], dataOut[i][j][k], 1.f );
                }
            }
        }
        bv.setFrom( Units::CELSIUS, dataC );
        bv.copyTo( Units::CELSIUS, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( dataC[i][j][k], dataOut[i][j][k], 1.f );
                }
            }
        }
        bv.copyTo( Units::FAHRENHEIT, dataOut );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( dataF[i][j][k], dataOut[i][j][k], 1.f );
                }
            }
        }

        // Test basic operation with pointer arrays
        float*** pDataF = I3D<float>::New3D( m, n, o );
        float*** pDataC = I3D<float>::New3D( m, n, o );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    pDataF[i][j][k] = dataF[i][j][k];
                    pDataC[i][j][k] = dataC[i][j][k];
                }
            }
        }
        float*** pDataOut = I3D<float>::New3D( m, n, o );
        bv.setFrom( Units::FAHRENHEIT, ( const float*** ) pDataF, m, n, o );
        bv.copyTo( Units::FAHRENHEIT, pDataOut, m, n, o );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( pDataF[i][j][k], pDataOut[i][j][k], 1.f );
                }
            }
        }
        bv.copyTo( Units::CELSIUS, pDataOut, m, n, o );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( pDataC[i][j][k], pDataOut[i][j][k], 1.f );
                }
            }
        }
        bv.setFrom( Units::CELSIUS, ( const float*** ) pDataC, m, n, o );
        bv.copyTo( Units::CELSIUS, pDataOut, m, n, o );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( pDataC[i][j][k], pDataOut[i][j][k], 1.f );
                }
            }
        }
        bv.copyTo( Units::FAHRENHEIT, pDataOut, m, n, o );
        for( int i = 0; i < m; ++i )
        {
            for( int j = 0; j < n; ++j )
            {
                for( int k = 0; k < o; ++k )
                {
                    TS_ASSERT_DELTA( pDataF[i][j][k], pDataOut[i][j][k], 1.f );
                }
            }
        }
        I3D<float>::Delete3D( pDataF );
        I3D<float>::Delete3D( pDataC );
        I3D<float>::Delete3D( pDataOut );

    }
};

#endif // _BLOB_TEST_H
