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

/** \defgroup data Data and Slate
 *
 *  All communications between Components (see \ref component)
 *  are via readers and writers that interact via the slate...
 *  which is itself basically a registry of URIs corresponding
 *  to Component names and variable names.
 */

#ifndef SLATE_H_
#define SLATE_H_

#include "component/Component.h"
#include "data/DataAccess.h"
#include "data/DataAccessor.h"
#include "data/DataValue.h"
#include "data/ElementURI.h"
#include "data/SimpleDataElement.h"
#include "data/UniversalURI.h"
#include "logger/Logger.h"
#include "utils/Mutex.h"
#include "utils/Str.h"

#ifdef ERROR  // A workaround for wingdi.h
#undef ERROR
#endif

class DataReader;
class DataValue;
class DataWriter;
class InStream;
class LogWriter;
class SimpleDataElement;
class UniversalDataReader;
class UniversalDataWriter;

template<typename S, typename T> class FastMap;

/// Maps for keeping track of actual data elements
typedef FastMap<const ElementURI, DataElement*> DataElementMap;
typedef FastMap<const ElementURI, UniversalDataElement*> UniversalMap;

/// Maps for keeping track of names only
typedef FastMap<const Str, DataAccess*> DataAccessMap;
typedef FastMap<const Str, ElementURI*> ElementURIMap;
typedef FastMap<const Str, CodedStr*> NameMap;

template<typename T> class FlexArray;

/// Arrays for attaching codes to names
typedef FlexArray<DataAccess*> DataAccessArray;
typedef FlexArray<DataAccess*> DataAccessList;
typedef FlexArray<ElementURI*> ElementURIArray;
typedef FlexArray<ElementURI*> ElementURIList;
typedef FlexArray<CodedStr*> NameArray;
typedef FlexArray<CodedStr*> NameList;

/** Code unit that represents the slate.  Includes
 *  methods for registering output elements (variables) and
 *  methods for requesting input elements (variables) that
 *  also register the requester as a consumer of the requested
 *  input element.
 *
 *  \ingroup data
 */
class Slate
{
public:

    /// static Constructor
    static void Construct();

    /// Returns the DataAccess with the code, or NULL if none
    static DataAccess* GetDataAccess( const unsigned short& code );

    /// Returns a DataAccess with the specified attributes.
    /// Creates a new one if the supplied attributes are unique
    static DataAccess* GetDataAccess( const DataAccessor& dataAccessor, UniversalDataElement* universal = NULL );

    /// Returns the number of DataAccess element currently registered
    static unsigned short GetDataAccessCount()
    {
        return NextDataAccessCode_;
    }

    /// Adds the supplied DataAccess to the registries
    static DataAccess* RegisterDataAccess( DataAccess* dataAccess );

    /// Returns the DataElement of the uriCode, or NULL
    static DataElement* GetElement( const unsigned short& uriCode );

    /// Returns the DataElement matching the supplied ElementURI
    static DataElement* GetElement( const ElementURI& elementURI );

    /// Returns the ElementURI with the code, or NULL if none
    static ElementURI* GetElementURI( const unsigned short& code );

    /// Returns the registered ElementURI matching the supplied ElementURI
    static ElementURI* GetElementURI( const ElementURI& elementURI );

    /// Returns the number of registered ElementURIs
    static unsigned int GetElementURICount();

    /// Adds a unique code to the elementURI.
    /// If slate.dir already exists, the code is taken from there.
    static void CodifyElementURI( ElementURI& elementURI );

    /// Adds the supplied ElementURI to the registries
    static ElementURI* RegisterElementURI( ElementURI* elementURI );

    /// Removes the supplied ElementURI to the registries
    static void DeRegisterElementURI( ElementURI* elementURI );

    /// Returns a ElementURI that matches the supplied str.
    /// Returns NULL if none match
    static ElementURI* FindElementURI( const Str& str );

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

    /// Returns the CodedStr with the code, or NULL if none
    static CodedStr* GetName( const unsigned short& code );

    /// Returns the CodedStr like the supplied string.
    /// Returns NULL if not found
    static CodedStr* FindCodedName( const Str& name );

    /// Returns the CodedStr like the supplied string.
    /// Creates a new one if the supplied one is unique
    static CodedStr& GetCodedName( const Str& name );

    /// Maps the supplied DataElement to the suplied Uri.
    static void MapDataElement( ElementURI* elementURI, DataElement* dataElement );

    /// Adds the supplied CodedStr to the registries
    static CodedStr* RegisterCodedName( CodedStr* name );

    /// Creates a reader for this component's input (and a dataElement)
    /// Doesn't take a ComponentName, assumes it's being created in the
    ///  owner's namespace.
    static DataReader* NewInputReader( const Str& uriPart,
                                       Component* owner, const DataValue* defaultValue, bool deleteDefaultValue = true );
    static DataReader* NewInputReader( const ElementURI &elementURI,
                                       Component* owner, const DataValue* defaultValue, bool deleteDefaultValue = true );

    // Creates a writer for this component's output (and a dataElement)
    static DataWriter* NewOutputWriter( const Str& uriPart,
                                        Component* owner,
                                        DataValue* initialValue );
    static DataWriter* NewOutputWriter( const ElementURI &elementURI,
                                        Component* owner,
                                        DataValue* initialValue );

    // Creates a reader for some other component's declared output
    static DataReader* NewReader( const Str &componentName, const Str& uriPart,
                                  Component* owner, const DataValue* defaultValue, bool deleteDefaultValue = true ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newDataReader with DataURI arg instead.") ));
    static DataReader* NewReader( const ElementURI &elementURI,
                                  Component* owner, const DataValue* defaultValue, bool deleteDefaultValue = true ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newDataReader with DataURI arg instead.") ));

    // Creates a reader for any universal outputs
    // Also creates a UniversalDataElement if a suitable one does not exist
    static UniversalDataReader* NewUniversalReader( const UniversalURI &universalURI,
            Component* owner, const DataValue* defaultValue, bool deleteDefaultValue = true ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newUniversalReader with UniversalURI arg instead.") ));

    // Creates a writer for this component's universal output (and a dataElement)
    // Also creates a UniversalDataElement if a suitable one does not exist
    static UniversalDataWriter* NewUniversalWriter( const UniversalURI &universalURI,
            Component* owner, DataValue* initialValue,
            const float accuracy = DataElement::NO_ACCURACY ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newUniversalWriter with UniversalURI arg instead.") ));

    // Creates a writer for some other component's declared input
    static DataWriter* NewWriter( const Str &componentName, const Str& uriPart,
                                  Component* owner ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newDataWriter with DataURI arg instead.") ));
    static DataWriter* NewWriter( const ElementURI &elementURI,
                                  Component* owner ); // TODO: __attribute__( ( deprecated ) ); // ("Use Component::newDataWriter with DataURI arg instead.") ));

    static DataElement* NewDataElement( const ElementURI &elementURI,
                                        Component* owner,
                                        const Unit& unit, BinaryDataType binaryType,
                                        const float accuracy = DataElement::NO_ACCURACY );

    static UniversalDataElement *NewUniversalElement( const UniversalURI &universalURI,
            Component* owner, const Unit& unit, BinaryDataType binaryType );


    /// If the value is only going to be read once, it's probably
    /// not worth providing a DataReader.  These
    /// functions allow for fairly efficient single reads.
    /// Return true if the value is assigned.  False otherwise.
    /// Note: These reads can not be logged.
    template<typename T>
    inline static bool ReadOnce( const Str& componentName, const Str& uriPart,
                                 const Unit& unit, T& readTo, Logger& logger,
                                 Syslog::Severity severity = Syslog::CRITICAL )
    {
        return ReadOnce( ElementURI( componentName, uriPart ), unit, readTo,
                         logger, severity );
    }
    template<typename T> static bool ReadOnce( const ElementURI& elementURI,
            const Unit& unit, T& readTo, Logger& logger,
            Syslog::Severity severity = Syslog::CRITICAL );

    inline static bool ReadOnce( const Str& componentName, const Str& uriPart,
                                 DataValue& readTo, Logger& logger, Syslog::Severity severity =
                                     Syslog::CRITICAL )
    {
        return ReadOnce( ElementURI( componentName, uriPart ), readTo, logger,
                         severity );
    }

    static bool ReadOnce( const ElementURI& elementURI, DataValue& readTo,
                          Logger& logger, Syslog::Severity severity = Syslog::CRITICAL );
    template<typename T> static bool ReadOnce( const unsigned short & uriCode,
            const Unit& unit, T& readTo, Logger& logger,
            Syslog::Severity severity = Syslog::CRITICAL );
    /// If the value is only going to be written once, it's probably
    /// not worth providing a DataWriter.  These
    /// functions allow for fairly efficient single writer.
    /// They return true if the value is writtem, false otherwise.
    /// Note: These writes are logged.
    template<typename T> static bool WriteOnce( const Str& uriPart,
            Component* owner, const Unit& unit, const T& writeThis );
    static bool WriteOnce( const Str& uriPart, Component* owner,
                           const DataValue& writeThis );
    template<typename T> static bool WriteOnce( const unsigned short & uriCode,
            Component* actor, const Unit& unit, const T& writeThis );
    static bool WriteOnce( const unsigned short & uriCode, Component* actor,
                           const DataValue& writeThis );
    // Called during initialization, to build up slate table entries based on
    // previous outputs -- allowing continious logging across multiple
    // runs and re-initialization of dataValues.
    static void ReadDirectory( const Str& dirFilename );
    // Called only by unserialize, to allow multiple directory scans.
    static void ClearDirectory();

    static bool IsDirectoryRead()
    {
        return DirectoryRead_;
    }

    // Enables logging of directory entries.
    // Not called by unseriailize, for example.
    static void EnableDirectoryWrites();
    // Called by Unit Tests.
    static void SimulateDirectoryWrites();
    // Called by DirectoryWriter to log all the directory entries
    static void WriteDirectory( LogWriter* rewriteTo = NULL, bool forceLog =
                                    false );
    static const unsigned short NO_LOG_FORMAT = 0x0000;

private:
    /// Private Constructor for static class
    Slate()
    {
    }

    ;
    static DataElement* NewDataElement( const ElementURI& elementURI,
                                        Component* owner, DataValue* initialValue, float accuracy =
                                            DataElement::NO_ACCURACY );
    static UniversalDataElement* NewUniversalElement(
        const UniversalURI& universalURI, Component* owner,
        const DataValue* testValue );
    /// Read and process a header frame
    ///
    /// \return True if the header frame was processed sucessfully,
    /// False if there were any problems.
    static bool ReadHeader( InStream& inStream );
    /// Helper functions that write directory information
    static void WriteDataAccessEntry( DataAccess* dataAccess,
                                      LogWriter* rewriteTo = NULL, bool forceWrite = false );
    static void WriteElementURIEntry( ElementURI* elementURI,
                                      LogWriter* rewriteTo = NULL, bool forceWrite = false );
    static void WriteNameEntry( CodedStr* codeName, LogWriter* rewriteTo = NULL,
                                bool forceWrite = false );
    static bool DirectoryWritesEnabled_;
    static bool DirectoryWritesSimulated_;
    static unsigned short NextDataAccessCode_;
    static unsigned short NextElementURICode_;
    static unsigned short NextNameCode_;
    static bool DirectoryRead_;
    static Logger* Logger_;
    static DataElementMap* DataElementMap_;
    static UniversalMap* UniversalMap_;
    static DataAccessMap* DataAccessMap_;
    static ElementURIMap* ElementURIMap_;
    static NameMap* NameMap_;
    static DataAccessArray* DataAccessArray_;
    static DataAccessArray* DataAccessList_;
    static ElementURIArray* ElementURIArray_;
    static ElementURIList* ElementURIList_;
    static int ElementURICount_;
    static NameArray* NameArray_;
    static NameList* NameList_;
    static Mutex DataAccessMutex_; // Makes DataAccess read/writes calls thread-sage
    static Mutex ElementMutex_; // Makes ElementURI read/writes thread sage
    static Mutex NameMutex_;
    /// Makes sure that static objects are deleted on shutdown
    class StaticDestructor
    {
    public:
        virtual ~StaticDestructor();
    };
    /// Static instance of StaticDestructor
    static StaticDestructor StaticDestructor_;

    static void ConfigureNewDataElement( Component* owner, DataElement*& dataElement );
    static float GetConfigureValue( const Str& cfgName );

    /// Allows Supervisor to call Uninitialize
    friend class Supervisor;
    friend class Unserialize;

    static void Uninitialize();

};

#endif /*SLATE_H_*/
