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

#include "DataAccess.h"

#include "component/Component.h"
#include "data/DataElement.h"
#include "data/UniversalDataElement.h"
#include "logger/DirectoryEntry.h"
#include "units/UnitRegistry.h"
#include "units/Units.h"

#define FLAG_BIT_RW 0x01
#define FLAG_BIT_INTERP 0x02
#define FLAG_BIT_TIME_PRECISION 0x04

/// DataAccess Constructor from DataAccessor
DataAccess::DataAccess( const DataAccessor& dataAccessor, UniversalDataElement* universal, unsigned short code )
    : ownerCode_( NO_CODE ),
      elementCode_( ElementURI::NO_CODE ),
      universalCode_( ElementURI::NO_CODE ),
      unit_( &Units::NONE ),
      binaryType_( NO_TYPE ),
      typeSize_( 0 ),
      accessorType_( dataAccessor.getAccessorType() ),
      code_( code ),
      tempUnitName_( NULL ),
      noInterpFlag_( false ),
      timePrecision_( dataAccessor.getTimePrecision() )
{
    Component* owner = dataAccessor.getOwner();
    ownerCode_ = owner->getName().getCode();
    DataElement& element = dataAccessor.getElement();
    elementCode_ = element.getUri().getCode();
    if( universal != NULL )
    {
        universalCode_ =  universal->getUri().getCode();
    }
    else if( element.isUniversal() )
    {
        universalCode_ = ElementURI::SELF_CODE;
    }
    DataValue* dataValue = element.getDataValue();
    if( !element.isUniversal() && NULL != dataValue )
    {
        unit_ = &dataValue->getUnit();
        binaryType_ = dataValue->getBinaryType();
        typeSize_ = dataValue->getTypeSize();
    }
}

/// DataAccess Constructor
DataAccess::DataAccess( const unsigned short ownerCode,
                        const unsigned short elementCode,
                        const unsigned short universalCode,
                        const char* unitName,
                        BinaryDataType binaryType,
                        unsigned short typeSize,
                        DataAccessor::AccessorType accessorType,
                        unsigned short code,
                        bool noInterpFlag,
                        Logger::TimePrecisionType timePrecisionType )
    : ownerCode_( ownerCode ),
      elementCode_( elementCode ),
      universalCode_( universalCode ),
      unit_( UnitRegistry::FindUnit( unitName ) ),
      binaryType_( binaryType ),
      typeSize_( typeSize ),
      accessorType_( accessorType ),
      code_( code ),
      tempUnitName_( NULL == unit_ ? new Str( unitName ) : NULL ),
      noInterpFlag_( noInterpFlag ),
      timePrecision_( timePrecisionType )
{}

DataAccess::~DataAccess()
{
    if( NULL != tempUnitName_ )
    {
        delete tempUnitName_;
    }
}

const Unit* DataAccess::getUnit()
{
    if( unit_ == NULL && tempUnitName_ != NULL )
    {
        unit_ = UnitRegistry::FindUnit( *tempUnitName_ );
        delete tempUnitName_;
        tempUnitName_ = NULL;
        if( NULL == unit_ )
        {
            unit_ = &( Units::NONE );
        }
    }
    return unit_;
}

const Str DataAccess::asString( bool includeCodeAndUnit )
{
    size_t bufMax( 1023 );
    char buf[ bufMax + 1 ];

    size_t offset( 0 );
    if( includeCodeAndUnit )
    {
        offset = snprintf( buf, bufMax, "<a code=%04hX ", code_ );
    }

    snprintf( buf + offset, bufMax - offset,
              "owner=%04hX element=%04hX universal=%04hX %s%s%stype=%02hX size=%04hX rw=%02hX",
              ownerCode_,
              elementCode_,
              universalCode_,
              ( includeCodeAndUnit ? "unitName=\"" : "" ),
              ( includeCodeAndUnit ? ( getUnit() == NULL ? "none" : unit_->getName() ) : "" ),
              ( includeCodeAndUnit ? "\" " : "" ),
              ( unsigned short )binaryType_,
              typeSize_,
              ( unsigned short )accessorType_ );
    buf[ 1023 ] = 0;
    return buf;
}

unsigned int DataAccess::write( OutStream& outStream )
{
    unsigned int bytesWritten( 0 );
    unsigned char flags( ( int ) accessorType_
                         + ( noInterpFlag_ ? FLAG_BIT_INTERP : 0 )
                         + ( getTimePrecision() == Logger::TIME_PRECISION_MILLIS ? FLAG_BIT_TIME_PRECISION : 0 ) );

    bytesWritten += outStream.write( " code=" ).bytesWritten();
    bytesWritten += outStream.writeShortHex( code_ ).bytesWritten();

    bytesWritten += outStream.write( " owner=" ).bytesWritten();
    bytesWritten += outStream.writeShortHex( ownerCode_ ).bytesWritten();

    bytesWritten += outStream.write( " element=" ).bytesWritten();
    bytesWritten += outStream.writeShortHex( elementCode_ ).bytesWritten();

    bytesWritten += outStream.write( " universal=" ).bytesWritten();
    bytesWritten += outStream.writeShortHex( universalCode_ ).bytesWritten();

    bytesWritten += outStream.write( " unitName=\"" ).bytesWritten();
    bytesWritten += outStream.write( getUnit() == NULL ? "none" : unit_->getName() ).bytesWritten();
    bytesWritten += outStream.write( "\"" ).bytesWritten();

    bytesWritten += outStream.write( " type=" ).bytesWritten();
    bytesWritten += outStream.writeCharHex( binaryType_ ).bytesWritten();

    bytesWritten += outStream.write( " size=" ).bytesWritten();
    bytesWritten += outStream.writeUShortHex( typeSize_ ).bytesWritten();

    bytesWritten += outStream.write( " fl=" ).bytesWritten();
    bytesWritten += outStream.writeCharHex( flags ).bytesWritten();

    bytesWritten += outStream.write( "\n" ).bytesWritten();

    return bytesWritten;
}

void DataAccess::copyUnitsTypeAndSizeFrom( DataAccess* dataAccess )
{
    unit_ = dataAccess->unit_;
    binaryType_ = dataAccess->binaryType_;
    typeSize_ = dataAccess->typeSize_;
}

DataAccess* DataAccess::Read( InStream& inStream )
{
    short code;
    short ownerCode;
    short elementCode;
    short universalCode;
    char unitName[256];
    signed char binaryType;
    short typeSize;
    unsigned char flags;
    bool ok = true;
    char dummy[16];

    // read " code="
    ok = inStream.read( dummy, 6 ).bytesRead() == 6 && strncmp( dummy, " code=", 6 ) == 0;
    if( ok )
    {
        // read code
        ok = inStream.readShortHex( code ).bytesRead() == 4;
    }

    if( ok )
    {
        // read " owner="
        ok = inStream.read( dummy, 7 ).bytesRead() == 7 && strncmp( dummy, " owner=", 6 ) == 0;
    }
    if( ok )
    {
        // read ownerCode
        ok = inStream.readShortHex( ownerCode ).bytesRead() == 4;
    }

    if( ok )
    {
        // read " element="
        ok = inStream.read( dummy, 9 ).bytesRead() == 9 && strncmp( dummy, " element=", 9 ) == 0;
    }
    if( ok )
    {
        // read elementCode
        ok = inStream.readShortHex( elementCode ).bytesRead() == 4;
    }

    if( ok )
    {
        // read " universal="
        ok = inStream.read( dummy, 11 ).bytesRead() == 11 && strncmp( dummy, " universal=", 11 ) == 0;
    }
    if( ok )
    {
        // read elementCode
        ok = inStream.readShortHex( universalCode ).bytesRead() == 4;
    }

    if( ok )
    {
        // read " unitName=\""
        ok = inStream.read( dummy, 11 ).bytesRead() == 11 && strncmp( dummy, " unitName=\"", 11 ) == 0;
    }
    if( ok )
    {
        // read unitName
        ok = inStream.readUntil( unitName, 255, '\"' ).bytesRead() > 0;
        ok &= inStream.bytesRead() < 255;
        if( ok )  // remove the trailing quote
        {
            unitName[ inStream.bytesRead() - 1 ] = 0;
        }
    }

    if( ok )
    {
        // read " type="
        ok = inStream.read( dummy, 6 ).bytesRead() == 6 && strncmp( dummy, " type=", 6 ) == 0;
    }
    if( ok )
    {
        // read binaryType
        ok = inStream.readCharHex( binaryType ).bytesRead() == 2;
    }

    if( ok )
    {
        // read " size="
        ok = inStream.read( dummy, 6 ).bytesRead() == 6 && strncmp( dummy, " size=", 6 ) == 0;
    }
    if( ok )
    {
        // read typeSize
        ok = inStream.readShortHex( typeSize ).bytesRead() == 4;
    }

    if( ok )
    {
        // read " rw="
        ok = inStream.read( dummy, 4 ).bytesRead() == 4
             && ( strncmp( dummy, " fl=", 4 ) == 0
                  || strncmp( dummy, " rw=", 4 ) == 0 );
    }
    if( ok )
    {
        // read rwCode
        ok = inStream.readUCharHex( flags ).bytesRead() == 2;
    }

    if( ok )
    {
        // read "\n" (OK if missing)
        inStream.read( dummy, 1 );
    }

    if( ok )
    {
        int rwCode = ( FLAG_BIT_RW & flags );
        bool noInterpFlag = ( flags & FLAG_BIT_INTERP ) == FLAG_BIT_INTERP;
        Logger::TimePrecisionType timePrecision = ( flags & FLAG_BIT_TIME_PRECISION ) == FLAG_BIT_TIME_PRECISION ?
                Logger::TIME_PRECISION_MILLIS : Logger::TIME_PRECISION_MICROS;
        DataAccess* dataAccess = new DataAccess( ownerCode, elementCode, universalCode, unitName,
                ( BinaryDataType )binaryType, typeSize, ( DataAccessor::AccessorType )rwCode,
                code, noInterpFlag, timePrecision );
        return dataAccess;
    }

    return NULL;
}

