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

#include "Unit.h"

#include "data/BlobValue.h"
#include "data/DataValue.h"
#include "data/Double.h"
#include "data/Double4.h"
#include "data/Double6.h"
#include "data/Float.h"
#include "data/Float2.h"
#include "data/Float3.h"
#include "data/Int.h"
#include "data/Location.h"
#include "data/Point3D.h"
#include "data/Point6D.h"
#include "data/Short.h"
#include "data/StrValue.h"
#include "data/UChar.h"
#include "data/UShort.h"
#include "units/Unit.h"
#include "units/UnitRegistry.h"
#include "utils/AuvMath.h"

Unit::Unit( const char* name, const char* abbreviation, const BaseUnit& baseUnit, const double toSiMultiplier, const double toSiAddend )
    : name_( name ),
      abbreviation_( abbreviation ),
      baseUnit_( baseUnit ),
      defaultDataType_( baseUnit.getDefaultDataType() ),
      toSiAddend_( toSiAddend ),
      toSiMultiplier_( toSiMultiplier ),
      fromSiAddend_( -toSiAddend / toSiMultiplier ),
      fromSiMultiplier_( 1.0 / toSiMultiplier ),
      needSiAddend_( toSiAddend != 0.0 ),
      needSiMultiplier_( toSiMultiplier != 1.0 )
{
    UnitRegistry::AddUnit( this );
}

Unit::Unit( const char* name, const char* abbreviation, const BinaryDataType defaultDataType )
    : name_( name ),
      abbreviation_( abbreviation ),
      baseUnit_( *this ),
      defaultDataType_( defaultDataType == NO_TYPE ? FLOAT3 : defaultDataType ),
      toSiAddend_( 0.0 ),
      toSiMultiplier_( 1.0 ),
      fromSiAddend_( 0.0 ),
      fromSiMultiplier_( 1.0 ),
      needSiAddend_( false ),
      needSiMultiplier_( false )
{
    UnitRegistry::AddUnit( this );
}

Unit::~Unit()
{}

const double Unit::getScaled( const double& siValue ) const
{
    double scaled( siValue );
    toScaled( scaled );
    return scaled;
}

const float Unit::getScaled( const float& siValue ) const
{
    float scaled( siValue );
    toScaled( scaled );
    return scaled;
}

const int Unit::getScaled( const int siValue ) const
{
    return ( const int ) getScaled( ( const double ) siValue );
}

const double Unit::getSI( const double& scaledValue ) const
{
    double si( scaledValue );
    toSI( si );
    return si;
}

const float Unit::getSI( const float& scaledValue ) const
{
    float si( scaledValue );
    toSI( si );
    return si;
}

const int Unit::getSI( const int scaledValue ) const
{
    return ( const int ) getSI( ( const double ) scaledValue );
}

void Unit::toScaled( double& siValue ) const
{
    if( needSiMultiplier_ )
    {
        siValue *= fromSiMultiplier_;
    }
    if( needSiAddend_ )
    {
        siValue += fromSiAddend_;
    }
}
void Unit::toScaled( float& siValue ) const
{
    if( needSiMultiplier_ )
    {
        siValue *= fromSiMultiplier_;
    }
    if( needSiAddend_ )
    {
        siValue += fromSiAddend_;
    }
}

void Unit::toScaled( int& siValue ) const
{
    siValue = ( int ) getScaled( ( double ) siValue );
}

void Unit::toSI( double& scaledValue ) const
{
    if( needSiMultiplier_ )
    {
        scaledValue *= toSiMultiplier_;
    }
    if( needSiAddend_ )
    {
        scaledValue += toSiAddend_;
    }
}
void Unit::toSI( float& scaledValue ) const
{
    if( needSiMultiplier_ )
    {
        scaledValue *= toSiMultiplier_;
    }
    if( needSiAddend_ )
    {
        scaledValue += toSiAddend_;
    }
}

void Unit::toSI( int& scaledValue ) const
{
    scaledValue = ( int ) getSI( ( double ) scaledValue );
}

bool Unit::operator== ( const Unit& unit ) const
{
    return &( unit.baseUnit_ ) == &( baseUnit_ )
           && unit.toSiAddend_ == toSiAddend_
           && unit.toSiMultiplier_ == toSiMultiplier_;
}

bool Unit::operator!= ( const Unit& unit ) const
{
    return !( unit == *this );
}

DataValue* Unit::dataValue( const BinaryDataType dataType, BlobType blobType ) const
{
    switch( dataType != NO_TYPE ? dataType : defaultDataType_ )
    {
    case UCHAR1 :
        return new UChar( *this, 0 );
    case SHORT2 :
        return new Short( *this, 0 );
    case USHORT2 :
        return new UShort( *this, 0 );
    case DOUBLE8 :
    case NO_TYPE:
    default:
        return new Double( *this, NAN );
    case DOUBLE4 :
        return new Double4( *this, NAN );
    case DOUBLE6 :
        return new Double6( *this, NAN );
    case FLOAT4:
        return new Float( *this, NANF );
    case FLOAT2:
        return new Float2( *this, NANF );
    case FLOAT3:
        return new Float3( *this, NANF );
    case INT4:
        return new Int( *this, 0 );
    /*case LOCATION:
        return ( *this )( NAN, NAN );
    case POINT3D:
        return new Point3D();
    case POINT6D:
        return new Point6D();*/
    case MULTIVALUE:
        return blobType == NOT_BLOB ? new StrValue( NULL, 0, *this ) : new BlobValue( *this, blobType );
    }
}

DataValue* Unit::dataValue( const bool& assignFrom, const BinaryDataType dataType ) const
{
    return ( *this )( ( int )assignFrom, dataType != NO_TYPE ? dataType : UCHAR1 );
}

DataValue* Unit::dataValue( const unsigned char& assignFrom, const BinaryDataType dataType ) const
{
    return ( *this )( ( int )assignFrom, dataType != NO_TYPE ? dataType : UCHAR1 );
}

DataValue* Unit::dataValue( const int& assignFrom, const BinaryDataType dataType ) const
{
    switch( dataType != NO_TYPE ? dataType : INT4 )
    {
    case UCHAR1 :
        return new UChar( *this, ( int ) assignFrom );
    case SHORT2 :
        return new Short( *this, ( int ) assignFrom );
    case USHORT2 :
        return new UShort( *this, ( int ) assignFrom );
    case DOUBLE8 :
        return new Double( *this, ( double ) assignFrom );
    case DOUBLE4 :
        return new Double4( *this, ( double ) assignFrom );
    case DOUBLE6 :
        return new Double6( *this, ( double ) assignFrom );
    case FLOAT4:
        return new Float( *this, ( float ) assignFrom );
    case FLOAT2:
        return new Float2( *this, ( float ) assignFrom );
    case FLOAT3:
        return new Float3( *this, ( float ) assignFrom );
    case INT4:
        return new Int( *this, ( int ) assignFrom );
    default:
        return NULL;
    }
}

DataValue* Unit::dataValue( const float& assignFrom, const BinaryDataType dataType ) const
{
    return ( *this )( ( double )assignFrom, dataType );
}

DataValue* Unit::dataValue( const double& assignFrom, const BinaryDataType dataType ) const
{
    switch( dataType != NO_TYPE ? dataType : defaultDataType_ )
    {
    case UCHAR1 :
        return new UChar( *this, ( int ) assignFrom );
    case SHORT2 :
        return new Short( *this, ( int ) assignFrom );
    case USHORT2 :
        return new UShort( *this, ( int ) assignFrom );
    case DOUBLE8 :
        return new Double( *this, ( double ) assignFrom );
    case DOUBLE4 :
        return new Double4( *this, ( double ) assignFrom );
    case DOUBLE6 :
        return new Double6( *this, ( double ) assignFrom );
    case FLOAT4:
        return new Float( *this, ( float ) assignFrom );
    case FLOAT2:
        return new Float2( *this, ( float ) assignFrom );
    case FLOAT3:
        return new Float3( *this, ( float ) assignFrom );
    case INT4:
        return new Int( *this, ( int ) assignFrom );
    default:
        return NULL;
    }
}

