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

#include "SlateBridge.h"
#include "Slate.h"

#include <algorithm> // std::find
#include <vector>


// TODO: add checking for to ensure ElementURI is indeed a DataWriter
// TODO: add support for Blob
// TODO: add support for UniversalBlob
// TODO: add permissions scheme
// TODO: limit the number of writers?

const StrValue SlateBridge::NO_VALUE( "" );

SlateBridge::SlateBridge( Component* owner )
    : owner_( owner )
{
    if( NULL != owner )
        authorizeName( owner->getName() );
}

SlateBridge::~SlateBridge()
{}

bool SlateBridge::slateWrite( const Str& name, const Str& unit, const unsigned char data, const Timestamp& timestamp )
{
    return slateWriteT( name, unit, data, timestamp );
}

bool SlateBridge::slateWrite( const Str& name, const Str& unit, const double data, const Timestamp& timestamp )
{
    return slateWriteT( name, unit, data, timestamp );
}

bool SlateBridge::slateWrite( const Str& name, const Str& unit, const float data, const Timestamp& timestamp )
{
    return slateWriteT( name, unit, data, timestamp );
}

bool SlateBridge::slateWrite( const Str& name, const Str& unit, const int data, const Timestamp& timestamp )
{
    return slateWriteT( name, unit, data, timestamp );
}

DataWriter* SlateBridge::getSlateWriter( const Str& name, const Unit& unit )
{
    DataWriter* writer = NULL;

    // Get the writer from the slate-bridge' fastmap
    writer = writerMap_.get( name );
    if( writer != NULL )
    {
        return writer;
    }

    // No go? See if the component already has matching writer
    unsigned short code = Slate::GetElementURICode( name.cStr() );
    if( code != ElementURI::NO_CODE )
    {
        //logger_.syslog( "Looking for Slate data writer with code: " + Str( code ) +".", Syslog::INFO );
        for( unsigned i = 0; i < owner_->getWriterList().size(); ++i )
        {
            writer = owner_->getWriterList().get( i );
            if( writer != NULL && writer->getUri().getCode() == code )
            {
                //logger_.syslog( "Found writer: " + Str( writer->getUri() ) +".", Syslog::INFO );
                writerMap_.put( name, writer );
                return writer;
            }
        }
    }

    // No existing writer found... Create a new writer! (if authorized)
    Str varName( name );
    Str componentName( owner_->getName() );
    // Try to extract the compnent and var names
    parseVarName( name, componentName, varName );

    if( isAuthorized( name ) || isAuthorized( componentName ) )
    {
        DataURI dataUri( componentName, varName.cStr(), unit, unit.getDefaultDataType() );
        writer = owner_->newDataWriter( dataUri );
        owner_->getLogger().syslog( "Created data writer for " + name + " with code: " + Str( writer->getUri().getCode() ), Syslog::INFO );
        writerMap_.put( name, writer );
        return writer;
    }
    else
    {
        owner_->getLogger().syslog( name + " is unauthorized or unknown.", Syslog::ERROR );
    }

    owner_->getLogger().syslog( "Failed to create a data writer for " + name + ".", Syslog::FAULT );
    return NULL;

    //const ElementURI* uri = ElementURI::FindURI( name );
    //    if( uri != NULL && uri->getElementType() == ElementURI::DATA_ELEMENT && uri->getBlobType() == NOT_BLOB )
}

void SlateBridge::authorizeComponent( const Str& componentName )
{

    if( ComponentRegistry::Find( componentName ) == NULL )
    {
        owner_->getLogger().syslog( "Failed to authorize unknown component " + componentName, Syslog::ERROR );
        return;
    }

    // Add if not on the list
    authorizeName( componentName );
}

void SlateBridge::authorizeName( const Str& name )
{
    // Add if not on the list
    if( std::find( authorizedNames_.begin(), authorizedNames_.end(), name ) == authorizedNames_.end() )
    {
        authorizedNames_.push_back( name );
    }
}

void SlateBridge::deauthorize( const Str& name )
{
    NameVec::iterator it = std::find( authorizedNames_.begin(), authorizedNames_.end(), name );
    if( it != authorizedNames_.end() )
    {
        authorizedNames_.erase( it );
    }
}

bool SlateBridge::isAuthorized( const Str& inputStr )
{
    if( ( inputStr.cStr()[0] == '_' || inputStr.cStr()[0] == '#' ) && inputStr.cStr()[1] == '.' )
    {
        // External source is permitted.
        return true;
    }
    else if( std::find( authorizedNames_.begin(), authorizedNames_.end(), inputStr ) != authorizedNames_.end() )
    {
        // Name is the list of authorized names.
        return true;
    }

    // Name is not permitted.
    return false;
}



DataReader* SlateBridge::getSlateReader( const Str& name, const Unit& unit )
{
    unsigned short code = Slate::GetElementURICode( name.cStr() );

    if( code != ElementURI::NO_CODE )
    {
        for( unsigned i = 0; i < owner_->getReaderList().size(); ++i )
        {
            DataReader* reader = owner_->getReaderList().get( i );
            if( reader != NULL && reader->getUri().getCode() == code )
            {
                return reader;
            }
        }

        // Still here? This means that the name was found in the Slate (valid URI code),
        // but the component has no existing reader for it.

        // Create a new reader:
        ElementURI* elementURI = Slate::GetElementURI( code );
        if( elementURI == NULL )
        {
            owner_->getLogger().syslog( "Failed to get valid element for " + name + " with code: " + Str( code ) + ".", Syslog::ERROR );
        }
        else if( elementURI->isUniversal() )
        {
            // Add a new universal data reader
            UniversalURI* universalURI = dynamic_cast<UniversalURI*>( elementURI );
            if( universalURI != NULL )
            {
                UniversalDataReader* reader = Slate::NewUniversalReader( *universalURI, owner_, &NO_VALUE, false );
                owner_->getLogger().syslog( "Created universal data reader for " + name + " with code: " + Str( code ), Syslog::INFO );

                return ( DataReader* )reader;
            }
        }
        else
        {
            // Add a new data reader
            DataReader* reader = Slate::NewReader( *elementURI, owner_, &NO_VALUE, false );
            owner_->getLogger().syslog( "Created data reader for " + name + " with code: " + Str( code ), Syslog::INFO );

            return reader;
        }
    }

    owner_->getLogger().syslog( "Failed to create a data reader. " + name + " is unknown.", Syslog::FAULT );
    return NULL;
}

unsigned short int SlateBridge::requestSlate( const Str &name, const Str& unitName, bool request )
{
    const Unit* unit = UnitRegistry::FindUnit( unitName );

    if( unit != NULL )
    {
        DataReader* reader = getSlateReader( name, *unit );

        if( reader != NULL )
        {
            reader->requestData( request );

            unsigned short code = reader->getUri().getCode();
            CodeVec::iterator it = findCode( code );

            if( reader->isRequestingData() )
            {
                if( it == requestedCodes_.end() )
                {
                    requestedCodes_.push_back( code );
                    owner_->getLogger().syslog( "Requesting data from " + reader->getUri() + " (" + Str( code ) + "). "
                                                "Requested size is: " + Str( ( int )requestedCodes_.size() ), Syslog::INFO );
                }
            }
            else
            {
                if( it != requestedCodes_.end() )
                {
                    requestedCodes_.erase( it );
                    owner_->getLogger().syslog( "Removed data request for " + reader->getUri() + " (" + Str( code ) + "). "
                                                "Requested size is: " + Str( ( int )requestedCodes_.size() ), Syslog::INFO );
                }
            }
            return reader->getUri().getCode();
        }
    }
    return ElementURI::NO_CODE;
}

void SlateBridge::clearRequested()
{
    for( size_t i = 0; i < requestedCodes_.size(); ++i )
    {
        unsigned short code = requestedCodes_[i];

        if( code != ElementURI::NO_CODE )
        {
            for( unsigned i = 0; i < owner_->getReaderList().size(); ++i )
            {
                DataReader* reader = owner_->getReaderList().get( i );
                if( reader != NULL && reader->getUri().getCode() == code )
                {
                    reader->requestData( false );
                }
            }
        }
    }
}

void SlateBridge::parseVarName( const Str& inputStr, Str& componentName, Str& variableName )
{
    if( inputStr.find( '.' ) != Str::NO_POS )
    {
        int numStr( 0 );
        Str* split = inputStr.split( numStr, "." );
        if( numStr > 1 )
        {
            componentName = split[0];
            variableName = split[1];
        }
        delete[] split;
    }
}

CodeVec::iterator SlateBridge::findCode( unsigned short code )
{
    return std::find( requestedCodes_.begin(), requestedCodes_.end(), code );
}

