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

#include <limits.h>
#include <cassert>

#include "Slate.h"

#include "data/ConfigDataElement.h"
#include "data/DataReader.h"
#include "data/DataWriter.h"
#include "data/Double.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/SimpleDataElement.h"
#include "data/UChar.h"
#include "data/UniversalDataElement.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "logger/BinaryLogReader.h"
#include "logger/DirectoryEntry.h"
#include "logger/FileLogReader.h"
#include "logger/LogWriter.h"
#include "module/Module.h"
#include "io/FileInStream.h"
#include "io/FileOutStream.h"
#include "utils/FastMap.h"
#include "units/UnitRegistry.h"
#include "units/Units.h"

bool Slate::DirectoryWritesEnabled_;
bool Slate::DirectoryWritesSimulated_;
unsigned short Slate::NextDataAccessCode_;
unsigned short Slate::NextElementURICode_;
unsigned short Slate::NextNameCode_;
bool Slate::DirectoryRead_( false );

Logger* Slate::Logger_;

DataElementMap* Slate::DataElementMap_;
UniversalMap* Slate::UniversalMap_;

DataAccessMap* Slate::DataAccessMap_( NULL );
ElementURIMap* Slate::ElementURIMap_( NULL );
NameMap* Slate::NameMap_( NULL );

DataAccessArray* Slate::DataAccessArray_;
DataAccessList* Slate::DataAccessList_;
ElementURIArray* Slate::ElementURIArray_;
ElementURIList* Slate::ElementURIList_;
NameArray* Slate::NameArray_;
NameList* Slate::NameList_;

Mutex Slate::DataAccessMutex_;
Mutex Slate::ElementMutex_;
Mutex Slate::NameMutex_;

Slate::StaticDestructor Slate::StaticDestructor_;

/// static Constructor
/// called via NewName
void Slate::Construct()
{
    // No mutex since this is called from mutex-protected code

    DirectoryWritesEnabled_ = false;
    DirectoryWritesSimulated_ = false;
    NextDataAccessCode_ = 0;
    NextElementURICode_ = 0;
    NextNameCode_ = 0;

    CodedStr* slateName = new CodedStr( "Slate", NextNameCode_++ );
    Logger_ = new Logger( slateName );

    DataElementMap_ = new DataElementMap();
    UniversalMap_ = new UniversalMap();

    DataAccessMap_ = new DataAccessMap();
    ElementURIMap_ = new ElementURIMap();
    NameMap_ = new NameMap();

    DataAccessArray_ = new DataAccessArray( false );
    DataAccessList_ = new DataAccessList( true );
    ElementURIArray_ = new ElementURIArray( false );
    ElementURIList_ = new ElementURIList( false );
    NameArray_ = new NameArray( false );
    NameList_ = new NameList( true );

    NameMap_->put( *slateName, slateName );
    NameList_->push( slateName );
}

/// static Destructor
Slate::StaticDestructor::~StaticDestructor()
{
    MutexLocker elementLocker( ElementMutex_ );
    MutexLocker dataAccessLocker( DataAccessMutex_ );
    MutexLocker nameLocker( NameMutex_ );

    // Clear out the dataElementMap_s DataElementMap
    while( DataElementMap_->size() > 0 )
    {
        delete DataElementMap_->popIndexed( 0 );
    }

    /// These deletions do not affect their stored contents
    delete DataElementMap_;
    delete UniversalMap_;

    delete DataAccessMap_;
    delete NameMap_;

    delete DataAccessArray_;
    delete ElementURIArray_;
    delete NameArray_;

    delete ElementURIList_;

    /// These deletions automatically delete their stored contents
    delete DataAccessList_;
    delete NameList_;
    delete ElementURIMap_;

    delete Logger_;

}

/// Returns the DataAccess with the code, or NULL if none
DataAccess* Slate::GetDataAccess( const unsigned short& code )
{
    MutexLocker dataAccessLocker( DataAccessMutex_ );

    return DataAccessArray_->get( code );
}

/// Returns a DataAccess with the specified attributes.
/// Creates a new one if the supplied attributes are unique
DataAccess* Slate::GetDataAccess( const DataAccessor& dataAccessor, UniversalDataElement* universal )
{
    MutexLocker dataAccesslocker( DataAccessMutex_ );

    if( NULL == DataAccessMap_ )
    {
        Construct();
    }

    DataAccess dataAccess( dataAccessor, universal );
    Str dataAccessStr( dataAccess.asString( false ) );
    DataAccess* mappedDataAccess = DataAccessMap_->get( dataAccessStr );
    if( NULL == mappedDataAccess )
    {
        DataAccess* newDataAccess = new DataAccess( dataAccessor, universal );
        DataAccessMap_->put( dataAccessStr, newDataAccess );
        DataAccessList_->push( newDataAccess );
        if( DirectoryWritesEnabled_ || DirectoryWritesSimulated_ )
        {
            newDataAccess->setCode( NextDataAccessCode_++ );
            WriteDataAccessEntry( newDataAccess, NULL, false );
        }
        mappedDataAccess = newDataAccess;
    }

    return mappedDataAccess;
}

/// Adds the supplied DataAccess to the registries
DataAccess* Slate::RegisterDataAccess( DataAccess* dataAccess )
{
    MutexLocker dataAccesslocker( DataAccessMutex_ );

    unsigned short code = dataAccess->getCode();
    if( code != DataAccess::NO_CODE && NULL == DataAccessArray_->get( code ) )
    {
        NextDataAccessCode_ = AuvMath::Max( NextDataAccessCode_, ( unsigned short )( code + 1 ) );
        Str dataAccessStr( dataAccess->asString( false ) );
        DataAccess* mappedDataAccess = DataAccessMap_->get( dataAccessStr );
        if( NULL != mappedDataAccess )
        {
            mappedDataAccess->setCode( code );
            DataAccessArray_->set( code, mappedDataAccess );
            delete dataAccess;
            return mappedDataAccess;
        }
        else
        {
            DataAccessArray_->set( code, dataAccess );
            DataAccessMap_->put( dataAccessStr, dataAccess );
            DataAccessList_->push( dataAccess );
            return dataAccess;
        }
    }
    delete dataAccess;
    return NULL;
}

/// Returns the DataElement of the uriCode, or NULL
DataElement* Slate::GetElement( const unsigned short& uriCode )
{
    MutexLocker elementLocker( ElementMutex_ );

    if( uriCode == ElementURI::NO_CODE )
    {
        return NULL;
    }
    ElementURI* elementURI( GetElementURI( uriCode ) );
    if( NULL == elementURI )
    {
        return NULL;
    }
    DataElement* dataElement( DataElementMap_->get( *elementURI ) );
    return dataElement;
}

/// Returns the DataElement matching the supplied ElementURI
DataElement* Slate::GetElement( const ElementURI& elementURI )
{
    MutexLocker elementLocker( ElementMutex_ );

    return DataElementMap_->get( elementURI );
}

/// Returns the ElementURI with the code, or NULL if none
ElementURI* Slate::GetElementURI( const unsigned short& code )
{
    // No mutex since this is called by GetElement( uriCode )

    return ElementURIArray_->get( code );
}

/// Returns the registered ElementURI matching the supplied ElementURI
ElementURI* Slate::GetElementURI( const ElementURI& elementURI )
{
    MutexLocker elementLocker( ElementMutex_ );

    return ElementURIMap_->get( elementURI );
}

/// Returns the number of registered ElementURIs
unsigned int Slate::GetElementURICount()
{
    MutexLocker dataAccessLocker( DataAccessMutex_ );

    return NULL == DataAccessList_ ? 0 : DataAccessList_->size();
}

/// Adds a code to the supplied ElementURI
void Slate::CodifyElementURI( ElementURI& elementURI )
{
    MutexLocker elementLocker( ElementMutex_ );

    if( NULL == ElementURIMap_ )
    {
        Construct();
    }

    ElementURI* existingElementURI = ElementURIMap_->get( elementURI );
    if( NULL == existingElementURI || existingElementURI->getCode() == ElementURI::NO_CODE )
    {
        ElementURIMap_->put( elementURI, &elementURI );
        ElementURIList_->push( &elementURI );
        if( DirectoryWritesEnabled_ || DirectoryWritesSimulated_ )
        {
            elementURI.setCode( NextElementURICode_++ );
            WriteElementURIEntry( &elementURI, NULL, false );
        }
    }
    else
    {
        elementURI.setCode( existingElementURI->getCode() );
    }
}

/// Adds the supplied ElementURI to the registries
ElementURI* Slate::RegisterElementURI( ElementURI* elementURI )
{
    // no Mutex since this is called by MapDataElement
    // Also, only external call is from DirectoryEntry:Read, which is single threaded

    unsigned short code = elementURI->getCode();
    if( code != ElementURI::NO_CODE && NULL == ElementURIArray_->get( code ) )
    {
        NextElementURICode_ = AuvMath::Max( NextElementURICode_, ( unsigned short )( code + 1 ) );
        ElementURI* mappedElement = ElementURIMap_->get( *elementURI );
        if( NULL != mappedElement )
        {
            mappedElement->setCode( code );
            ElementURIArray_->set( code, mappedElement );
            delete elementURI;
            return mappedElement;
        }
        else
        {
            ElementURIArray_->set( code, elementURI );
            ElementURIMap_->put( *elementURI, elementURI );
            ElementURIList_->push( elementURI );
            return elementURI;
        }
    }
    delete elementURI;
    return NULL;
}

void Slate::DeRegisterElementURI( ElementURI* elementURI )
{
    MutexLocker elementLocker( ElementMutex_ );

    unsigned short code = elementURI->getCode();
    if( code != ElementURI::NO_CODE && NULL != ElementURIArray_->get( code ) )
    {
        ElementURIArray_->set( code, NULL );
        ElementURIMap_->pop( *elementURI );
        //ElementURIList_->pop( elementURI );
        //delete elementURI;
        DataElement* element = DataElementMap_->get( *elementURI );
        if( NULL != element )
        {
            DataElementMap_->pop( *elementURI );
            DataElementMap_->put( ElementURI( "deregistered", Str( code ) ), element );
        }
    }
}

/// Returns a ElementURI that matches the supplied str.
/// Returns NULL if none match
ElementURI* Slate::FindElementURI( const Str& str )
{
    MutexLocker elementLocker( ElementMutex_ );

    return ElementURIMap_->get( str );
}

/// Returns a ElementURI code for the ElementURI that matches the supplied
/// chars. Returns ElementURI::NO_CODE if the supplied one is unique
unsigned short Slate::GetElementURICode( const char* chars )
{
    MutexLocker elementLocker( ElementMutex_ );

    ElementURI* foundElementURI = ElementURIMap_->get( Str( chars ) );
    if( NULL == foundElementURI )
    {
        return ElementURI::NO_CODE;
    }
    return foundElementURI->getCode();
}

/// Returns the CodedStr with the code, or NULL if none
CodedStr* Slate::GetName( const unsigned short& code )
{
    MutexLocker namelocker( NameMutex_ );

    return NameArray_->get( code );
}

/// Returns the CodedStr like the supplied string.
/// Returns NULL if not found
CodedStr* Slate::FindCodedName( const Str& name )
{
    MutexLocker namelocker( NameMutex_ );

    if( NULL == NameMap_ )
    {
        Construct();
    }

    return NameMap_->get( name );
}

/// Returns the CodedStr like the supplied string.
/// Creates a new one if the supplied one is unique
CodedStr& Slate::GetCodedName( const Str& name )
{
    MutexLocker namelocker( NameMutex_ );

    if( NULL == NameMap_ )
    {
        Construct();
    }

    CodedStr* newName = NameMap_->get( name );
    if( NULL == newName )
    {
        newName = new CodedStr( name );
        NameMap_->put( name, newName );
        NameList_->push( newName );
        if( DirectoryWritesEnabled_ || DirectoryWritesSimulated_ )
        {
            newName->setCode( NextNameCode_++ );
            WriteNameEntry( newName, NULL );
        }
    }

    return *newName;
}

/// Adds the supplied CodedStr to the registries
CodedStr* Slate::RegisterCodedName( CodedStr* name )
{
    MutexLocker namelocker( NameMutex_ );

    unsigned short code = name->getCode();
    if( code != CodedStr::NO_CODE && NULL == NameArray_->get( code ) )
    {
        NextNameCode_ = AuvMath::Max( NextNameCode_, ( unsigned short )( code + 1 ) );
        CodedStr* mappedName = NameMap_->get( *name );
        if( NULL != mappedName )
        {
            mappedName->setCode( code );
            NameArray_->set( code, mappedName );
            delete name;
            return mappedName;
        }
        else
        {
            NameArray_->set( code, name );
            NameMap_->put( *name, name );
            NameList_->push( name );
            return name;
        }
    }
    delete name;
    return NULL;
}

/// Maps the supplied DataElement to the suplied Uri.
void Slate::MapDataElement( ElementURI* elementURI, DataElement* dataElement )
{
    MutexLocker elementLocker( ElementMutex_ );

    const ElementURI* mappedURI = ElementURIMap_->get( *elementURI );
    if( NULL == mappedURI )
    {
        mappedURI = RegisterElementURI( elementURI );
    }
    if( NULL == DataElementMap_->get( *mappedURI ) )
    {
        DataElementMap_->put( *mappedURI, dataElement );
    }
}

// Creates a reader for this component's input (and a dataElement)
DataReader* Slate::NewInputReader( const Str& uriPart,
                                   Component* owner, const DataValue* defaultValue, bool deleteDefaultValue )
{
    if( NULL != dynamic_cast<const ElementURI*>( &uriPart ) )
    {
        owner->getLogger().syslog( "Attempt to treat ElementURI as ElementURI part: " + uriPart, Syslog::CRITICAL );
    }

    return NewInputReader( ElementURI::Create( owner->getName(), uriPart ), owner, defaultValue, deleteDefaultValue );
}

DataReader* Slate::NewInputReader( const ElementURI& elementURI,
                                   Component* owner, const DataValue* defaultValue, bool deleteDefaultValue )
{
    // Actually identical to newReader at present, though
    // NewInputReader assumes the DataElement has to be created.
    DataElement* newElement = GetElement( elementURI );
    if( NULL == newElement )
    {
        newElement = NewDataElement( elementURI, owner, defaultValue->copy() );
    }
    else if( NULL == newElement->getDataValue() )
    {

        newElement->setDataValue( defaultValue->copy() );
    }
    return new DataReader( owner, *newElement, defaultValue, deleteDefaultValue );
}

// Creates a writer for this component's output (and a dataElement)
DataWriter* Slate::NewOutputWriter( const Str& uriPart,
                                    Component* owner,
                                    DataValue* initialValue )
{
    if( NULL != dynamic_cast<const ElementURI*>( &uriPart ) )
    {
        owner->getLogger().syslog( "Attempt to treat ElementURI as ElementURI part: " + uriPart, Syslog::CRITICAL );
    }
    ElementURI elementURI( owner->getName(), uriPart );
    return NewOutputWriter( elementURI, owner, initialValue );
}

// Creates a writer for this component's output (and a dataElement)
DataWriter* Slate::NewOutputWriter( const ElementURI& elementURI,
                                    Component* owner,
                                    DataValue* initialValue )
{
    DataElement* newElement = NewDataElement( elementURI, owner, initialValue );

    DataWriter* newWriter = new DataWriter( owner, *newElement, NULL, Logger::TIME_PRECISION_MILLIS );

    return newWriter;
}

// Creates a reader for some other component's declared output
DataReader* Slate::NewReader( const Str& componentName, const Str& uriPart,
                              Component* owner, const DataValue* defaultValue, bool deleteDefaultValue )
{
    if( NULL != dynamic_cast<const ElementURI*>( &uriPart ) )
    {
        owner->getLogger().syslog( "Attempt to treat ElementURI as ElementURI part: " + uriPart, Syslog::CRITICAL );
    }
    return NewReader( ElementURI::Create( componentName, uriPart ), owner, defaultValue, deleteDefaultValue );
}

DataReader* Slate::NewReader( const ElementURI& elementURI,
                              Component* owner, const DataValue* defaultValue, bool deleteDefaultValue )
{
    // This relies on newDataElement looking for an existing
    // element.  If it already exists, it is returned.

    // need to actually check for existence of the data element?
    // why?  so we can copy defaultValue
    DataElement* newElement = GetElement( elementURI );
    if( NULL == newElement )
    {
        if( NULL == defaultValue )
        {
            return NULL;
        }
        newElement = NewDataElement( elementURI, owner, defaultValue->copy() );
    }
    return new DataReader( owner, *newElement, defaultValue, deleteDefaultValue );

}

// Creates a reader for any universal outputs
// Also creates a UniversalDataElement if a suitable one does not exist
UniversalDataReader* Slate::NewUniversalReader( const UniversalURI& universalURI,
        Component* owner, const DataValue* defaultValue, bool deleteDefaultValue )
{
    // Need check for existence of universal so we can copy the default value
    UniversalDataElement* newElement = NewUniversalElement( universalURI, owner, NULL );

    // universal reader is same as any other reader
    //return NewReader( universalURI, owner, defaultValue, deleteDefaultValue );
    return new UniversalDataReader( owner, *newElement, defaultValue, deleteDefaultValue );

}


//---------------------------------------------------------
// Creates a writer for this component's universal output (and a dataElement)
// Also creates a UniversalDataElement if a suitable one does not exist
UniversalDataWriter* Slate::NewUniversalWriter( const UniversalURI& universalURI,
        Component* owner,
        DataValue* initialValue,
        const float accuracy )
{
    // newUniversalElement should check for an existing element
    //printf("In NewUniversalWriter with uriPart=%s and universalURI=%s\n",uriPart.c_str(), universalURI.c_str());
    UniversalDataElement* universal = NewUniversalElement( universalURI, owner, initialValue );

    //DataWriter* newWriter = NewOutputWriter(( Str )universalURI, owner, initialValue, accuracy, universal );

    ElementURI elementURI( owner->getName(), universalURI );

    DataElement* newElement = NewDataElement( elementURI, owner, initialValue, accuracy );

    UniversalDataWriter* newWriter = new UniversalDataWriter( owner, *newElement, universal, Logger::TIME_PRECISION_MILLIS );

    if( NULL != universal )
    {
        DataElement& element = *newElement;

        // Not that comfortable with this kind of two-way linking...
        element.registerUniversal( universal );
        universal->addElement( element );
        universal->notifyChangeDone( newWriter );
    }

    newWriter->setAccuracy( initialValue->getUnit(), accuracy );

    return newWriter;
}

//---------------------------------------------------------
// Creates a writer for some other component's declared input
DataWriter* Slate::NewWriter( const Str& componentName, const Str& uriPart,
                              Component* owner )
{
    if( NULL != dynamic_cast<const ElementURI*>( &uriPart ) )
    {
        owner->getLogger().syslog( "Attempt to treat ElementURI as ElementURI part: " + uriPart, Syslog::CRITICAL );
    }

    return NewWriter( ElementURI::Create( componentName, uriPart ), owner );
}

DataWriter* Slate::NewWriter( const ElementURI& elementURI,
                              Component* owner )
{
    // Similar to newReader.  If it doesn't exist, create it
    // this policy may be revised at some point.
    // As in newReader, relies on newDataElement returning the
    // existing element.
    DataElement* newElement = NewDataElement( elementURI, owner, NULL );
    return new DataWriter( owner, *newElement, NULL, Logger::TIME_PRECISION_MILLIS );
}

template<typename T>
bool Slate::ReadOnce( const ElementURI& elementURI, const Unit& unit, T& readTo, Logger& logger, Syslog::Severity severity )
{
    DataElement* dataElement = GetElement( elementURI );
    if( NULL != dataElement )
    {
        ConfigDataElement* cfgElement = dynamic_cast<ConfigDataElement*>( dataElement );
        if( NULL != cfgElement )
        {
            const ConfigURI* cfgUri = dynamic_cast<const ConfigURI*>( &elementURI );
            if( NULL != cfgUri )
            {
                cfgElement->setRequiresRestart( cfgUri->requiresRestart() );
            }
        }

        DataValue* dataValue = dataElement->getDataValue();
        if( NULL != dataValue )
        {
            dataValue->copyTo( unit, readTo );
            return true;
        }
        else
        {
            logger.syslog( "Could not read element " + elementURI, severity );
        }
    }
    else
    {
        logger.syslog( "Could not find element " + elementURI, severity );
    }
    return false;
}

/// \cond DOXYGEN_CANT_HANDLE_THIS
template bool Slate::ReadOnce( const ElementURI& elementURI, const Unit&, unsigned char&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const ElementURI& elementURI, const Unit&, bool&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const ElementURI& elementURI, const Unit&, int&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const ElementURI& elementURI, const Unit&, float&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const ElementURI& elementURI, const Unit&, double&, Logger&, Syslog::Severity );
/// \endcond // DOXYGEN_CANT_HANDLE_THIS


bool Slate::ReadOnce( const ElementURI& elementURI, DataValue& readTo, Logger& logger, Syslog::Severity severity )
{
    DataElement* dataElement = GetElement( elementURI );
    if( NULL != dataElement && !dataElement->isOrphaned() )
    {
        ConfigDataElement* cfgElement = dynamic_cast<ConfigDataElement*>( dataElement );
        if( NULL != cfgElement )
        {
            const ConfigURI* cfgUri = dynamic_cast<const ConfigURI*>( &elementURI );
            if( NULL != cfgUri )
            {
                cfgElement->setRequiresRestart( cfgUri->requiresRestart() );
            }
        }

        DataValue* dataValue = dataElement->getDataValue();
        if( NULL != dataValue )
        {
            dataValue->copyTo( readTo );
            return true;
        }
        else
        {
            logger.syslog( "Could not read element " + elementURI, severity );
        }
    }
    else
    {
        logger.syslog( "Could not find element " + elementURI, severity );
    }
    return false;
}


template<typename T>
bool Slate::ReadOnce( const unsigned short& uriCode, const Unit& unit, T& readTo, Logger& logger, Syslog::Severity severity )
{
    if( uriCode == ElementURI::NO_CODE )
    {
        return false;
    }
    ElementURI* elementURI( GetElementURI( uriCode ) );

    DataElement* dataElement( GetElement( *elementURI ) );
    if( NULL != dataElement && !dataElement->isOrphaned() )
    {

        ConfigDataElement* cfgElement = dynamic_cast<ConfigDataElement*>( dataElement );
        if( NULL != cfgElement )
        {
            const ConfigURI* cfgUri = dynamic_cast<const ConfigURI*>( elementURI );
            if( NULL != cfgUri )
            {
                cfgElement->setRequiresRestart( cfgUri->requiresRestart() );
            }
        }

        DataValue* dataValue = dataElement->getDataValue();
        if( NULL != dataValue )
        {
            dataValue->copyTo( unit, readTo );
            return true;
        }
        else
        {
            logger.syslog( "Could not read element " + *elementURI, severity );
        }
    }
    else
    {
        logger.syslog( "Could not find element " + *elementURI, severity );
    }
    return false;
}
/// \cond DOXYGEN_CANT_HANDLE_THIS
template bool Slate::ReadOnce( const unsigned short& uriCode, const Unit&, unsigned char&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const unsigned short& uriCode, const Unit&, bool&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const unsigned short& uriCode, const Unit&, int&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const unsigned short& uriCode, const Unit&, float&, Logger&, Syslog::Severity );
template bool Slate::ReadOnce( const unsigned short& uriCode, const Unit&, double&, Logger&, Syslog::Severity );
/// \endcond // DOXYGEN_CANT_HANDLE_THIS


template <typename T>
bool Slate::WriteOnce( const Str& uriPart,
                       Component* owner, const Unit& unit, const T& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriPart, owner, *writeValue );
        delete writeValue;
    }
    return success;
}

/// \cond DOXYGEN_CANT_HANDLE_THIS
template bool Slate::WriteOnce( const Str&, Component*, const Unit&, const unsigned char& writeThis );
template bool Slate::WriteOnce( const Str&, Component*, const Unit&, const int& writeThis );
/// \endcond // DOXYGEN_CANT_HANDLE_THIS

template <>
bool Slate::WriteOnce<float>( const Str& uriPart,
                              Component* owner, const Unit& unit, const float& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis, FLOAT4 ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriPart, owner, *writeValue );
        delete writeValue;
    }
    return success;
}

template <>
bool Slate::WriteOnce<double>( const Str& uriPart,
                               Component* owner, const Unit& unit, const double& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis, DOUBLE8 ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriPart, owner, *writeValue );
        delete writeValue;
    }
    return success;
}

bool Slate::WriteOnce( const Str& uriPart,
                       Component* owner, const DataValue& writeThis )
{
    ElementURI elementURI( owner->getName(), uriPart );
    DataElement* newElement = GetElement( elementURI );
    DataWriter* newWriter( NULL );
    if( NULL == newElement )
    {
        newElement = NewDataElement( elementURI, owner, writeThis.copy() );
    }
    else
    {
        newWriter = newElement->findWriter( elementURI );
    }
    if( NULL == newWriter )
    {
        newWriter = new DataWriter( owner, *newElement, NULL, Logger::TIME_PRECISION_MILLIS );
    }
    newWriter->write( writeThis );
    return true;
}


template <typename T>
bool Slate::WriteOnce( const unsigned short& uriCode,
                       Component* actor, const Unit& unit, const T& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriCode, actor, *writeValue );
        delete writeValue;
    }
    return success;
}

/// \cond DOXYGEN_CANT_HANDLE_THIS
template bool Slate::WriteOnce( const unsigned short&, Component*, const Unit&, const unsigned char& writeThis );
template bool Slate::WriteOnce( const unsigned short&, Component*, const Unit&, const int& writeThis );
/// \endcond // DOXYGEN_CANT_HANDLE_THIS

template <>
bool Slate::WriteOnce<float>( const unsigned short& uriCode,
                              Component* actor, const Unit& unit, const float& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis, FLOAT4 ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriCode, actor, *writeValue );
        delete writeValue;
    }
    return success;
}

template <>
bool Slate::WriteOnce<double>( const unsigned short& uriCode,
                               Component* actor, const Unit& unit, const double& writeThis )
{
    bool success( false );
    DataValue* writeValue( unit( writeThis, DOUBLE8 ) );
    if( NULL != writeValue )
    {
        success = WriteOnce( uriCode, actor, *writeValue );
        delete writeValue;
    }
    return success;
}

bool Slate::WriteOnce( const unsigned short& uriCode,
                       Component* actor, const DataValue& writeThis )
{
    if( uriCode == ElementURI::NO_CODE )
    {
        return false;
    }
    ElementURI* elementURI( GetElementURI( uriCode ) );

    DataElement* element( GetElement( *elementURI ) );
    if( NULL == element )
    {
        return false;
    }
    DataWriter* newWriter( element->findWriter( *elementURI ) );
    if( NULL == newWriter )
    {
        newWriter = new DataWriter( actor, *element, NULL, Logger::TIME_PRECISION_MILLIS );
    }
    newWriter->write( writeThis );
    return true;
}

void Slate::EnableDirectoryWrites()
{
    if( NULL == dynamic_cast<DataAccessMap*>( DataAccessMap_ ) )
    {
        Construct();
    }

    if( !DirectoryWritesEnabled_ )
    {
        DirectoryWritesEnabled_ = true;
        // Make sure "Slate" gets written, even though it has a code
        WriteNameEntry( ( CodedStr* ) & ( Logger_->getFacility() ), NULL );
    }
}

void Slate::SimulateDirectoryWrites()
{
    if( NULL == dynamic_cast<DataAccessMap*>( DataAccessMap_ ) )
    {
        Construct();
    }

    DirectoryWritesSimulated_ = true;

}

DataElement* Slate::NewDataElement( const ElementURI &elementURI,
                                    Component* owner,
                                    const Unit& unit, BinaryDataType binaryType,
                                    const float accuracy )
{
    DataElement* dataElement( GetElement( elementURI ) );
    if( NULL == dataElement )
    {
        dataElement = new SimpleDataElement( elementURI, unit.dataValue( binaryType, elementURI.getBlobType() ), accuracy );
        {
            // Brackets reduce scope of elementLocker.
            MutexLocker elementLocker( ElementMutex_ );
            DataElementMap_->put( elementURI, dataElement );
        }
        ConfigureNewDataElement( owner, dataElement );
    }
    return dataElement;
}

UniversalDataElement* Slate::NewUniversalElement( const UniversalURI& universalURI,
        Component* owner, const Unit& unit, BinaryDataType binaryType )
{
    // Check for existing here
    UniversalDataElement* universal = UniversalMap_->get( universalURI );
    if( NULL != universal )
    {
        return universal;
    }

    DataValue* tempDataValue = unit.dataValue( binaryType );
    UniversalDataElement* newUniversal = new UniversalDataElement( universalURI, tempDataValue );
    delete tempDataValue;

    UniversalMap_->put( universalURI, newUniversal );
    DataElementMap_ -> put( universalURI, newUniversal );
    return newUniversal;
}

void Slate::ConfigureNewDataElement( Component* owner, DataElement*& dataElement )
{
    if( NULL != owner && NULL != dataElement )
    {
        dataElement->setFrequency( GetConfigureValue( owner->getName() + ".frequency" ) );
        dataElement->setLatency( GetConfigureValue( owner->getName() + ".latency" ) );
        dataElement->setPower( GetConfigureValue( owner->getName() + ".power" ) );
    }
}

float Slate::GetConfigureValue( const Str& cfgName )
{
    float cfgVal( nanf( "" ) );

    unsigned short code = Slate::GetElementURICode( cfgName.cStr() );
    if( code != ElementURI::NO_CODE )
    {
        DataElement* de = Slate::GetElement( code );
        if( NULL != de )
        {
            const Unit* unit = de->getBaseUnit();
            DataValue* dv = de->getDataValue();
            if( NULL != dv && NULL != unit )
            {
                dv->copyTo( *unit, cfgVal );
            }
        }
    }

    return cfgVal;
}


//---------------------------------------------------------
//= Protected ==

DataElement* Slate::NewDataElement( const ElementURI& elementURI,
                                    Component* owner,
                                    DataValue* initialValue,
                                    float accuracy )
{
    DataElement* dataElement( GetElement( elementURI ) );
    if( NULL != dataElement )
    {
        if( NULL == dataElement->getDataValue() && !dataElement->isUniversal() && NULL != initialValue )
        {
            WriteOnce( elementURI.getCode(), owner, *initialValue );
            DataWriter* newWriter( dataElement->findWriter( elementURI ) );
            if( NULL == newWriter )
            {
                newWriter = new DataWriter( owner, *dataElement, NULL, Logger::TIME_PRECISION_MILLIS );
            }
            newWriter->write( *initialValue );
        }
        else
        {
            if( NULL != initialValue )
            {
                // Already exists
                delete initialValue;
            }
        }
    }

    if( NULL == dataElement )
    {
        dataElement = new SimpleDataElement( elementURI, initialValue, accuracy );
        {
            // Brackets reduce scope of elementLocker.
            MutexLocker elementLocker( ElementMutex_ );
            DataElementMap_->put( elementURI, dataElement );
        }
        ConfigureNewDataElement( owner, dataElement );
    }

    return dataElement;
}

UniversalDataElement* Slate::NewUniversalElement( const UniversalURI& universalURI,
        Component* owner, const DataValue* testValue )
{
    // Check for existing here
    UniversalDataElement* universal = UniversalMap_->get( universalURI );
    if( NULL != universal )
    {
        if( universal->getBaseUnit() != NULL && testValue != NULL )
        {
            if( *universal->getBaseUnit() !=  testValue->getBaseUnit() )
            {
                owner->getLogger().syslog( "Attempt to change base unit of universal '"
                                           + universalURI + "' from " + universal->getBaseUnit()->getName()
                                           + " to " + testValue->getBaseUnit().getName(), Syslog::ERROR );
            }
            if( universal->getTypeSize() !=  testValue->getTypeSize() )
            {
                owner->getLogger().syslog( "Attempt to change type size of universal '"
                                           + universalURI + "' from " + Str( universal->getTypeSize(), 10 )
                                           + " to " + Str( testValue->getTypeSize(), 10 ), Syslog::ERROR );
            }
        }
        else if( universal->getBaseUnit() == NULL && testValue != NULL )
        {
            universal->setBaseUnit( &testValue->getUnit().getBaseUnit() );
            universal->setTypeSize( testValue->getTypeSize() );
        }
        return universal;
    }

    UniversalDataElement* newUniversal = new UniversalDataElement( universalURI, testValue );

    UniversalMap_->put( universalURI, newUniversal );
    DataElementMap_ -> put( universalURI, newUniversal );
    return newUniversal;
}

void Slate::ReadDirectory( const Str& dirFilename )
{
    FileLogReader reader( dirFilename, new BinaryLogReader( *Logger_ ) );
    int missedCount = 0;
    while( missedCount < 20 )
    {
        missedCount = ( 0 == reader.read() ? missedCount + 1 : 0 );
        DirectoryRead_ = true;
    }
}

void Slate::ClearDirectory()
{
    MutexLocker elementLocker( ElementMutex_ );
    MutexLocker dataAccessLocker( DataAccessMutex_ );
    MutexLocker nameLocker( NameMutex_ );

    while( DataElementMap_->size() > 0 )
    {
        delete DataElementMap_->popIndexed( 0 );
    }

    UniversalMap_->clear();
    NextDataAccessCode_ = 0;
    NextElementURICode_ = 0;
    NextNameCode_ = 0;

    DataAccessMap_->clear();
    ElementURIMap_->clear();
    NameMap_->clear();

    DataAccessArray_->clear();
    DataAccessList_->clear();
    ElementURIArray_->clear();
    ElementURIList_->clear();
    NameArray_->clear();
    NameList_->clear();

    CodedStr* slateName = new CodedStr( "Slate", NextNameCode_++ );
    if( NULL != Logger_ )
    {
        delete Logger_;
    }
    Logger_ = new Logger( slateName );

    NameMap_->put( *slateName, slateName );
    NameList_->push( slateName );

    DirectoryRead_ = false;

}

void Slate::Uninitialize()
{
    MutexLocker elementLocker( ElementMutex_ );
    MutexLocker dataAccessLocker( DataAccessMutex_ );
    MutexLocker nameLocker( NameMutex_ );

    // Clear name codes
    for( unsigned int i = 1; i < NameList_->size(); ++i )
    {
        CodedStr* name = NameList_->get( i );
        if( NULL != name )
        {
            name->setCode( CodedStr::NO_CODE );
        }
    }
    NextNameCode_ = 0;

    // Clear element codes
    for( unsigned int i = 0; i < ElementURIList_->size(); ++i )
    {
        ElementURI* elementURI = ElementURIList_->get( i );
        if( NULL != elementURI )
        {
            elementURI->setCode( ElementURI::NO_CODE );
        }
    }
    NextElementURICode_ = 0;

    // Clear data accesse codes
    for( unsigned int i = 0; i < DataAccessList_->size(); ++i )
    {
        DataAccess* dataAccess = DataAccessList_->get( i );
        if( NULL != dataAccess )
        {
            dataAccess->setCode( DataAccess::NO_CODE );
        }
    }
    NextDataAccessCode_ = 0;
}

void Slate::WriteDirectory( LogWriter* rewriteTo, bool forceLog )
{
    // Write names
    for( unsigned int i = 0; i < NameList_->size(); ++i )
    {
        CodedStr* name = NameList_->get( i );
        if( NULL != name && name->getCode() != CodedStr::NO_CODE
                && name->getCode() >= NextNameCode_ )
        {
            NextNameCode_ = name->getCode() + 1;
        }
    }
    for( unsigned int i = 0; i < NameList_->size(); ++i )
    {
        CodedStr* name = NameList_->get( i );
        bool writeNow( rewriteTo != NULL );
        if( NULL != name && name->getCode() == CodedStr::NO_CODE )
        {
            name->setCode( NextNameCode_++ );
            writeNow = true;
        }
        if( NULL != name && writeNow )
        {
            WriteNameEntry( name, rewriteTo, forceLog );
        }
    }

    // Write elements
    for( unsigned int i = 0; i < ElementURIList_->size(); ++i )
    {
        ElementURI* elementURI = ElementURIList_->get( i );
        if( NULL != elementURI && elementURI->getCode() != ElementURI::NO_CODE
                && elementURI->getCode() >= NextElementURICode_ )
        {
            NextElementURICode_ = elementURI->getCode() + 1;
        }
    }
    for( unsigned int i = 0; i < ElementURIList_->size(); ++i )
    {
        ElementURI* elementURI = ElementURIList_->get( i );
        bool writeNow( NULL != rewriteTo );
        if( NULL != elementURI && elementURI->getCode() == ElementURI::NO_CODE )
        {
            elementURI->setCode( NextElementURICode_++ );
            writeNow = true;
        }
        if( NULL != elementURI && writeNow )
        {
            WriteElementURIEntry( elementURI, rewriteTo, forceLog );
        }
    }

    // Write data accesses
    for( unsigned int i = 0; i < DataAccessList_->size(); ++i )
    {
        DataAccess* dataAccess = DataAccessList_->get( i );
        if( NULL != dataAccess && dataAccess->getCode() != DataAccess::NO_CODE
                && dataAccess->getCode() >= NextDataAccessCode_ )
        {
            NextDataAccessCode_ = dataAccess->getCode() + 1;
        }
    }
    for( unsigned int i = 0; i < DataAccessList_->size(); ++i )
    {
        DataAccess* dataAccess = DataAccessList_->get( i );
        bool writeNow( NULL != rewriteTo );
        if( NULL != dataAccess && dataAccess->getCode() == DataAccess::NO_CODE )
        {
            dataAccess->setCode( NextDataAccessCode_++ );
            writeNow = true;
        }
        if( NULL != dataAccess && writeNow )
        {
            WriteDataAccessEntry( dataAccess, rewriteTo, forceLog );
        }
    }
}

void Slate::WriteDataAccessEntry( DataAccess* dataAccess, LogWriter* rewriteTo, bool forceLog )
{
    if( DataAccessArray_->get( dataAccess->getCode() ) == NULL || forceLog )
    {
        DataAccessArray_->set( dataAccess->getCode(), dataAccess );
        if( !DirectoryWritesSimulated_ )
        {
            Logger_->log( new DirectoryEntry( dataAccess ) );
        }
    }
    else if( NULL != rewriteTo )
    {
        DirectoryEntry* entry = new DirectoryEntry( dataAccess );
        rewriteTo->write( entry );
        delete entry;
    }
}

void Slate::WriteElementURIEntry( ElementURI* elementURI, LogWriter* rewriteTo, bool forceLog )
{
    if( ElementURIArray_->get( elementURI->getCode() ) == NULL || forceLog )
    {
        ElementURIArray_->set( elementURI->getCode(), elementURI );
        if( !DirectoryWritesSimulated_ )
        {
            Logger_->log( new DirectoryEntry( elementURI ) );
        }
    }
    else if( NULL != rewriteTo )
    {
        DirectoryEntry* entry = new DirectoryEntry( elementURI );
        rewriteTo->write( entry );
        delete entry;
    }
}

void Slate::WriteNameEntry( CodedStr* name, LogWriter* rewriteTo, bool forceLog )
{
    if( NameArray_->get( name->getCode() ) == NULL || forceLog )
    {
        NameArray_->set( name->getCode(), name );
        if( !DirectoryWritesSimulated_ )
        {
            Logger_->log( new DirectoryEntry( name ) );
        }
    }
    else if( NULL != rewriteTo )
    {
        DirectoryEntry* entry = new DirectoryEntry( name );
        rewriteTo->write( entry );
        delete entry;
    }
}
