/////////////////////////////////////////////////////////////////////
//                                                                 //
// file: aBrainStem-core.h                                         //
//                                                                 //
/////////////////////////////////////////////////////////////////////
//                                                                 //
// description: BrainStem API's and support.                       //
//                                                                 //
//                                                                 //
/////////////////////////////////////////////////////////////////////
//                                                                 //
// Copyright (c) 2018 Acroname Inc. - All Rights Reserved          //
//                                                                 //
// This file is part of the BrainStem release. See the license.txt //
// file included with this package or go to                        //
// https://acroname.com/software/brainstem-development-kit         //
// for full license details.                                       //
/////////////////////////////////////////////////////////////////////

#ifndef __BrainStem_core_H__
#define __BrainStem_core_H__

#include <stdint.h>
#include <string>
#include <list>
#include <vector>
#include <memory>
#include <map>
#include <functional>

#ifdef _WIN32

#include <winsock2.h>
#include <ws2tcpip.h>

#ifndef in_addr_t
#define in_addr_t uint32_t
#endif

#else // Linux

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif

#include "BrainStem-C.h"
#include "aProtocoldefs.h"


#ifdef __cplusplus
using std::list;
#endif // __cplusplus


namespace Acroname {
    namespace BrainStem {
        
        /// aEtherConfig Class
        /// This class provides basic configurations to the underlying Client-Server model.
        class aEtherConfig
        {
        public:
        /// aEtherConfig Constructor. This configuration will result in the smoothest user experience.
        /// NOTE: If localOnly == false AND networkInterface is default (0 or LOCALHOST_IP_ADDRESS)
        /// it will be populated with the auto-selected interface upon successful connection.
            aEtherConfig() :
                enabled(true),
                fallback(true),
                localOnly(true),
                assignedPort(0),
                networkInterface(LOCALHOST_IP_ADDRESS) {}
            
            bool enabled; /**< True: Client-Server module is used; False: Direct module control is used.*/
            bool fallback; /**< True: If connections fails it will automatically search for network connections; */
            bool localOnly; /**< True: Restricts access to localhost; False: Expose device to external network */
            uint16_t assignedPort; /**< Server assigned port after successful connection*/
            uint32_t networkInterface; /**< Network interface to use for connections.*/
        };
        
        

        /// LinkClass:
        /// The Link class provides an interface to a BrainStem link.
        /// The link is used to create interfaces to modules on a BrainStem network.
        /// The link represents a connection to the BrainStem network from a host computer.
        /// The link is always associated with a transport (e.g.: USB, Ethernet, etc.) and a
        /// link module, but there are several ways to make this association.\n
        ///    -# The link can be fully specified with a transport and module serial number
        ///    -# The link can be created by searching a transport and connecting to the first
        ///    module found.
        ///
        /// Calling connect on a link will start a connection to the module based on
        /// The link specification. Calling disconnect will disconnect the link from
        /// the the current connection.
        class aLIBEXPORT Link
        {
        public:
        
            /// Gets the links current aEther configuration
            /// \param config Pointer to the configuration to be filled
            /// \return aErrNone on success; aErrParam if config is NULL
            aErr getConfig(aEtherConfig* config);
        
            /// Sets the links aEther configuration.
            /// Configuration must be applied BEFORE connecting
            /// \param config Configuration to be applied
            /// \return aErrNone on success. aErrPermission if the module is currently connected.
            aErr setConfig(const aEtherConfig config);
        

            /// Discover is called with a specified transport to search for
            /// link modules on that transport. The callback is called with
            /// a fully filled in specifier for any link module found. The sDiscover returns
            /// aErrNone if the discovery process is successful, regardless of if any links
            /// are found. An error is only returned if the link discovery
            /// process fails. Discovery can take some time.
            /// The callback will occur in the same thread context as this routine call.
            /// \param type Transport to search for available BrainStem link modules
            /// on. See the \ref linkType "transport" enum for supported transports.
            /// \param cbLinkFound Process that is called when a module is discovered.
            /// \param vpCBRef This is passed to cbLinkFound when a module is discovered.
            /// \return ::aErrNotFound if no devices were found.
            /// \return ::aErrNone on success.
            static aErr sDiscover(const linkType type,
                                  aDiscoveryModuleFoundProc cbLinkFound,
                                  void* vpCBRef,
                                  const uint32_t networkInterface = LOCALHOST_IP_ADDRESS)
            {
                aErr err = aErrNone;
                uint8_t count = 0;
                count = aDiscovery_EnumerateModules(type,
                                                    cbLinkFound,
                                                    vpCBRef,
                                                    networkInterface);
                if (count == 0) {
                    err = aErrNotFound;
                }
                return err;

            };

            /// sFindAll is a callback function which matches any found stem.
            /// SFindAll is used by sDiscover(const linkType, list<linkSpec>*) to
            /// fill the list provided with any found modules on the specified link
            /// type.
            /// \param spec The linkspec pointer for the device currently being
            ///             evaluated.
            /// \param bSuccess a returned value indicating whether the search has
            ///                 succeeded.
            /// \param vpCBRef Reference pointer to the std::list that was passed in.
            /// \return true To continue processing, or false to stop processing.
            static bContinueSearch sFindAll(const linkSpec* spec, bool* bSuccess, void* vpCBRef) {

                list<linkSpec>* pSpecs = (std::list<linkSpec>*)vpCBRef;
                pSpecs->push_back(*spec);
                *bSuccess = true;
                return true;
            }

            /// Discover is called with a specified transport to search for
            /// link modules on that transport. The devices list is filled with device
            /// specifiers. sDiscover returns aErrNone if the discovery process is
            /// successful, regardless of whether any links
            /// are found. An error is only returned if the link discovery
            /// process fails. Discovery can take some time.
            /// \param type Transport to search for available BrainStem link modules
            /// on. See the \ref linkType "transport" enum for supported transports.
            /// \param devices an empty list of specifiers that will be filled in.
            /// \return ::aErrNotFound if no devices were found.
            /// \return ::aErrNone on success.
            static aErr sDiscover(const linkType type,
                                  list<linkSpec>* devices,
                                  const uint32_t networkInterface = LOCALHOST_IP_ADDRESS)
            {
                return sDiscover(type, sFindAll, devices, networkInterface);
            };

            /// Link Constructor. Takes a fully specified linkSpec pointer and creates
            /// a link instance with this specifier information.
            /// \param linkSpecifier The connection details for a specific module.
            /// \param name A name for the link to be created.
            /// This name can be used to reference the link during later interactions.
            Link(const linkSpec linkSpecifier, const char* name = "Link");

            /// Link constructor without a specifier will most likely use the
            /// discoverAndConnect call to create a connection to a link module.
            /// \param name A name for the link to be created.
            Link(const char* name = "Link");

            /// Destructor.
            ~Link(void);

            /// A discovery-based connect.
            /// This member function will connect to the first available BrainStem
            /// found on the given transport. If the
            /// serial number is passed, it will only connect to the module with that serial number.
            /// Passing 0 as the serial number will create a link to the first link module found
            /// on the specified transport.
            /// If a link module is found on the specified transport, a connection will be made.
            /// \param type Transport on which to search for available BrainStem link
            /// modules. See the \ref linkType "transport" enum for supported transports.
            /// \param serialNumber Specify a serial number to connect to a specific
            /// link module. Use 0 to connect to the first link module found.
            /// \param model Acroname model number for the device.
            /// \return ::aErrBusy - if the module is already in use.
            /// \return ::aErrParam - if the transport type is undefined.
            /// \return ::aErrNotFound - if the module cannot be found or if no modules found.
            /// \return ::aErrNone - If the connect was successful.
            aErr discoverAndConnect(const linkType type,
                                    const uint32_t serialNumber = 0,
                                    const uint8_t model = 0);

        
            /// Connect to a link with a fully defined specifier.
            /// \return ::aErrBusy - if the module is running, starting or stopping. Try again in a bit.
            /// \return ::aErrDuplicate - If the module is already connected and running.
            /// \return ::aErrConnection - If there was an error with the connection. User needs to disconnect, then reconnect.
            /// \return ::aErrConfiguration - If the link has an invalid linkSpec.
            /// \return ::aErrNotFound - if the module cannot be found.
            /// \return ::aErrNone If the connect was successful.
            aErr connect(void);
            
            /// Connect using a pre-existing link.
            /// This member function will connect to the same BrainStem used by given Link.
            /// If a link module is found on the specified transport, a connection will be made.
            /// \param link - Reference to the link to be used.
            /// \return ::aErrInitialization - If the referenced link does not exist yet.
            /// \return ::aErrConnection - If the connection could not be made.
            /// \return ::aErrConfiguration - If the device or connection is in properly configured.
            /// \return ::aErrNone  - if the connect was successful.
            aErr connectThroughLinkModule(Link& link);
            
            /// Check to see if a module is connected.
            /// isConnected looks for a connection to an active module.
            /// \return true: connected, false: not connected.
            bool isConnected(void);
            
            /// Check the status of the module connection.
            /// \return linkStatus (see aLink.h for status values)
            linkStatus getStatus(void);

            /// Disconnect from the BrainStem module.
            /// \return ::aErrResource - If the there is no valid connection.
            /// \return ::aErrConnection - If the disconnect failed, due to a
            /// communication issue.
            /// \return ::aErrNone If the disconnect was successful.
            aErr disconnect(void);
            
            /// Reset The underlying link stream.
            /// \return ::aErrResource - If the there is no valid connection.
            /// \return ::aErrConnection - If the reset failed, due to a
            /// communication issue.
            /// \return ::aErrNone If the reset was successful.
            aErr reset(void);

            /////////////////////////////////////////////////////////////////////
            //
            // access routines
            //
            /////////////////////////////////////////////////////////////////////
            
            /// Accessor for link Name.
            /// Returns a pointer to the string representing the link. This string
            /// is part of the link, and will be destroyed with it. If you need access
            /// to the link name beyond the life of the link, then copy the char* returned.

            /// \return Pointer to character array containing the name of the link.
            const char* getName(void);

            /// Accessor for current link specification.
            /// \param spec - an allocated empty link spec reference.
            /// \return aErrNotFound - If no linkSpec set for current link.
            aErr getLinkSpecifier(linkSpec* spec);

            /// Accessor Set current link specification.
            /// \param linkSpecifier - The specifier that will replace the current spec.
            /// \return aErrBusy - If link is currently connected.
            aErr setLinkSpecifier(const linkSpec linkSpecifier);

            /// Gets the module address of the module the link is connected too.
            /// A zero is returned if no module can not be determined or
            /// if the link is not connected.
            aErr getModuleAddress(uint8_t * address);

            /////////////////////////////////////////////////////////////////////
            //
            // Send/Receive Packets to/from the Brainstem
            //
            /////////////////////////////////////////////////////////////////////
            
            /// Sends a BrainStem protocol UEI packet on the link.
            /// This is an advanced interface, please see the relevant section of the
            ///  reference manual for more information about UEIs.
            /// \param packet The command UEI packet to send.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - data too long or short.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrNone - success.
            aErr sendUEI(const uei packet);

            /// Sends a BrainStem protocol UEI packet on the link where the packet contains
            /// a subindex.
            /// This is an advanced interface, please see the relevant section of the
            ///  reference manual for more information about UEIs.
            /// \param packet The command UEI packet to send.
            /// \param subindex The subindex of the command option.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - data too long or short.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrNone - success.
            aErr sendUEI(const uei packet, const uint8_t subindex);

            /// Awaits receipt of the first available matching UEI packet
            /// from the link. The first four arguments describe the packet to wait for.
            /// When successful, the supplied uei ref is filled with the received UEI.
            /// This is an advanced interface, please see the relevant section of the
            ///  reference manual for more information about UEIs.
            /// \param module The module address.
            /// \param command The command.
            /// \param option The uei option.
            /// \param index The index of the uei entity.
            /// \param packet The uei packet reference to be filled on success.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrTimeout - no packet available.
            /// \return ::aErrNone - success.
            aErr receiveUEI(const uint8_t module,
                            const uint8_t command,
                            const uint8_t option,
                            const uint8_t index,
                            uei* packet);
            
            /// Awaits receipt of the first available matching UEI packet
            /// from the link. The first four arguments and proc describe the packet to wait for.
            /// When successful, the supplied uei ref is filled with the received UEI.
            /// This is an advanced interface, please see the relevant section of the
            ///  reference manual for more information about UEIs.
            /// \param module The module address.
            /// \param command The command.
            /// \param option The uei option.
            /// \param index The index of the uei entity.
            /// \param packet The uei packet reference to be filled on success.
            /// \param proc The callback used for determining a matching packet.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrTimeout - no packet available.
            /// \return ::aErrNone - success.
            aErr receiveUEI(const uint8_t module,
                            const uint8_t command,
                            const uint8_t option,
                            const uint8_t index,
                            uei* packet,
                            aPacketMatchPacketProc proc);

            /// Drops all existing queued packets that match.
            /// from the link.
            /// The arguments describe the packets to be matched
            /// This is an advanced interface, please see the relevant section of the
            ///  reference manual for more information about UEIs.
            /// \param module The module address.
            /// \param command The command.
            /// \param option The uei option.
            /// \param index The index of the uei entity.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrNone - success.
            aErr dropMatchingUEIPackets(const uint8_t module,
                                        const uint8_t command,
                                        const uint8_t option,
                                        const uint8_t index);

            /// Sends a raw BrainStem protocol packet on the link.
            /// where the length does not include the module or the command.
            /// address byte and can be 0 to aBRAINSTEM_MAXPACKETBYTES - 1.
            /// This is an advanced interface, please see the relevant section of the
            /// reference manual for more information about BrainStem Packet protocol.
            /// \param module The address of the destination module.
            /// \param command The length of the data being sent.
            /// \param length The length of the data being sent.
            /// \param data The data to send.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - data too long or short.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrNone - success.
            aErr sendPacket(const uint8_t module,
                            const uint8_t command,
                            const uint8_t length,
                            const uint8_t* data);

            /// Awaits receipt of the first available matching raw BrainStem protocol packet
            /// from the link where the length does not include the module or command bytes and can be zero.
            /// The provided module and match array are compared to packets available and the first match is
            /// returned.  The supplied data pointer must point to at least aBRAINSTEM_MAXPACKETBYTES - 1 bytes.
            /// When successful, the data is filled in with the packet data
            /// not including the module and command and the length pointer
            /// is updated with the length of the returned data.
            ///
            /// This is an advanced interface, please see the relevant section of the
            /// reference manual for more information about BrainStem Packet protocol.
            /// \param module The module address.
            /// \param match A byte array of the values to match for received packets.
            /// \param length The length of the match data on entry and length of the returned data filled on success.
            /// \param data The data filled on success.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrPacket - invalid module address.
            /// \return ::aErrTimeout - no packet available.
            /// \return ::aErrNone - success.
            aErr receivePacket(const uint8_t module,
                               const uint8_t* match,
                               uint8_t* length,
                               uint8_t* data);

            /////////////////////////////////////////////////////////////////////
            //
            // Routines for handling slots at the module
            //
            /////////////////////////////////////////////////////////////////////
            
            /// Loads data into a BrainStem Slot. See the relevant section of the BrainStem
            /// reference for information about BrainStem Slots and Stores.
            /// \param module - Module address.
            /// \param store - BrainStem store to access, possibilities include Internal, RAM, and SD.
            /// \param slot - The Slot within the Brainstem store to place the data.
            /// \param pData - Pointer to a buffer containing the data to load.
            /// \param length - The length in bytes of the data buffer to write.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - invalid module address.
            /// \return ::aErrCancel - The write process is closing and this call
            /// was unable to successfully complete.
            /// \return ::aErrNone - success.
            aErr loadStoreSlot(const uint8_t module,
                               const uint8_t store,
                               const uint8_t slot,
                               const uint8_t* pData,
                               const size_t length);

            /// Unloads data from a BrainStem Slot. If there are no read
            // errors but the dataLength supplied is less than the actual slot size,
            // an error of aErrOverrun is returned. See the relevant section of the BrainStem
            /// reference for information about BrainStem Slots and Stores.
            /// \param module - Module address.
            /// \param store - BrainStem store to access, possibilities include Internal, RAM, and SD.
            /// \param slot - The Slot within the Brainstem store to place the data.
            /// \param pData - Pointer to a buffer with dataLength space in bytes that will be filled by the call.
            /// \param dataLength - Expected length of the data, and at most the size of the pData buffer.
            /// \param pNRead - The number of bytes actually read.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - invalid module address.
            /// \return ::aErrCancel - The write process is closing and this call
            /// was unable to successfully complete.
            /// \return ::aErrOverrun - The read would overrun the buffer, i.e there is more data in the slot than the buffer can handle.
            /// \return ::aErrNone - success.
            aErr unloadStoreSlot(const uint8_t module,
                                 const uint8_t store,
                                 const uint8_t slot,
                                 uint8_t* pData,
                                 const size_t dataLength,
                                 size_t* pNRead);
            

            /// Returns the current size of the data loaded in the slot specified.
            /// \param module - Module address.
            /// \param store - BrainStem store to access, possibilities include Internal, RAM, and SD.
            /// \param slot - The Slot within the Brainstem store to place the data.
            /// \param size - size in bytes of the data stored in the slot.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - invalid module address.
            /// \return ::aErrCancel - The write process is closing and this request
            /// was unable to successfully complete.
            /// \return ::aErrNone - success.
            aErr storeSlotSize(const uint8_t module,
                               const uint8_t store,
                               const uint8_t slot,
                               size_t* size);

            /// Returns the maximum data capacity of the slot specified.
            /// \param module - Module address.
            /// \param store - BrainStem store to access, possibilities include Internal, RAM, and SD.
            /// \param slot - The Slot within the Brainstem store to place the data.
            /// \param capacity - size in bytes of the data stored in the slot.
            /// \return ::aErrConnection - link not connected.
            /// \return ::aErrParam - invalid module address.
            /// \return ::aErrCancel - The write process is closing and this request
            /// was unable to successfully complete.
            /// \return ::aErrNone - success.
            aErr storeSlotCapacity(const uint8_t module,
                                   const uint8_t store,
                                   const uint8_t slot,
                                   size_t* capacity);
        
            static const uint8_t STREAM_WILDCARD = 0xFF;
        
            /// Enumeration of stream packet types.
            typedef enum STREAM_PACKET {
                kSTREAM_PACKET_UNKNOWN,
                kSTREAM_PACKET_U8,
                kSTREAM_PACKET_U16,
                kSTREAM_PACKET_U32,
                kSTREAM_PACKET_BYTES,
                kSTREAM_PACKET_SUBINDEX_U8,
                kSTREAM_PACKET_SUBINDEX_U16,
                kSTREAM_PACKET_SUBINDEX_U32,
                
                kSTREAM_PACKET_LAST
            } STREAM_PACKET_t;
        
            /// Decodes the streaming packet type from a provided packet.
            /// \param packet - The packet to be interrogated.
            /// \param type - variable to be populated. Filled with kSTREAM_PACKET_UNKNOWN on failure.
            /// \return true on success; false on failure.
            static bool getStreamPacketType(const aPacket* packet, STREAM_PACKET_t* type);
        
            /// Helper function for indicating wether the packet is a subindex type.
            /// The subindex can be queried through Link::getStreamKeyElement
            /// \param type - The element to evaluate.
            /// \return true if the type contains a subindex; false if it does not.
            static bool isSubindexType(STREAM_PACKET_t type);
        
            ///Function signature for streaming callbacks.
            /// \param packet reference to streaming packet
            /// \param pRef User provided reference
            /// \return aErrNone - Success.
            /// Return value is not currently used.
            typedef std::function<
                aErr(const aPacket* packet, void* pRef)
            > streamCallback_t;
            
            /// Enables streaming for the supplied criteria.
            /// \param moduleAddress Address to filter on.
            /// \param cmd cmd to filter by (supports Wildcards)
            /// \param option option to filter by (supports Wildcards)
            /// \param index index to filter by (supports Wildcards)
            /// \param enable True - Enables streaming; False - disables streaming
            aErr enableStream(const uint8_t moduleAddress,
                              const uint8_t cmd,
                              const uint8_t option,
                              const uint8_t index,
                              const bool enable);
        
            /// Determines if the module is actively streaming.
            /// Does not indicate what is streaming, only if streaming is currently active.
            /// \param moduleAddress The devices module address.
            /// \param enabled Variable to be populated.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr isLinkStreaming(const uint8_t moduleAddress, uint8_t* enabled);
            
            /// Registers a callback function based on a specific module, cmd, option, and index.
            /// \param moduleAddress Address to filter on (supports Wildcards)
            /// \param cmd cmd to filter by (supports Wildcards)
            /// \param option option to filter by (supports Wildcards)
            /// \param index index to filter by (supports Wildcards)
            /// \param enable True - installs/updates callback and ref; False - uninstalls callback
            /// \param cb Callback to be executed when a new packet matching the criteria is received.
            /// \param pRef Pointer to user reference for use inside the callback function.
            /// \return aErrNotFound - Item not found (uninstalling only)
            /// \return aErrNone - success
            aErr registerStreamCallback(const uint8_t moduleAddress,
                                        const uint8_t cmd,
                                        const uint8_t option,
                                        const uint8_t index,
                                        const bool enable,
                                        streamCallback_t cb,
                                        void* pRef);
            
            /// Gets stream value based on the search criteria
            /// \param moduleAddress Address to filter on (supports Wildcards)
            /// \param cmd cmd to filter by (supports Wildcards)
            /// \param option option to filter by (supports Wildcards)
            /// \param index index to filter by (supports Wildcards)
            /// \return aErrStreamStale if the value has not been updated since the last read.
            /// \return aErrNotFound if no such stream element exists.
            /// \return aErrNone - success
            aErr getStreamValue(const uint8_t moduleAddress,
                                const uint8_t cmd,
                                const uint8_t option,
                                const uint8_t index,
                                const uint8_t subindex,
                                uint32_t* value);
        
            /// Gets all available stream values based on the search criteria.
            /// \param moduleAddress Address to filter on (supports Wildcards)
            /// \param cmd cmd to filter by (supports Wildcards)
            /// \param option option to filter by (supports Wildcards)
            /// \param index index to filter by (supports Wildcards)
            /// \param status map of key value pairs to be filled based on the parameters.
            /// Link::getStreamKeyElement should be used to decode the keys
            /// \return aErrParam if status is null
            /// \return aErrNone - success
            aErr getStreamStatus(const uint8_t moduleAddress,
                                 const uint8_t cmd,
                                 const uint8_t option,
                                 const uint8_t index,
                                 const uint8_t subindex,
                                 std::map<uint64_t, uint32_t>* status);
            
            /// Provides a list of active stream keys based on the supplied criteria.
            /// Exposed for unit-testing purposes only.
            /// \param moduleAddress Address to filter on (supports Wildcards)
            /// \param cmd cmd to filter by (supports Wildcards)
            /// \param option option to filter by (supports Wildcards)
            /// \param index index to filter by (supports Wildcards)
            /// \param acquireLock Option to acquire mutex before getting list elements.
            /// \return List of keys meeting the search criteria
            std::vector<uint64_t> filterActiveStreamKeys(const uint8_t moduleAddress,
                                                         const uint8_t cmd,
                                                         const uint8_t option,
                                                         const uint8_t index,
                                                         const uint8_t subindex,
                                                         const bool acquireLock);
            /// Enumeration for element types within a stream key.
            typedef enum STREAM_KEY {
                STREAM_KEY_MODULE_ADDRESS,
                STREAM_KEY_CMD,
                STREAM_KEY_OPTION,
                STREAM_KEY_INDEX,
                STREAM_KEY_SUBINDEX,
            } STREAM_KEY_t;
        
            static uint64_t createStreamKey(uint8_t moduleAddress,
                                            uint8_t cmd,
                                            uint8_t option,
                                            uint8_t index,
                                            uint8_t subindex);
        
            /// Convenience function to unpack a stream key.
            /// Note: This function will assert if an out of range STREAM_KEY_t is used.
            /// \param key The key to be unpacked
            /// \param element The element to unpack from the key.
            /// \return The requested element from the key.
            static uint8_t getStreamKeyElement(const uint64_t key, STREAM_KEY_t element);
        
            ///Convenience function to determine wether the value is a stream packet.
            /// Stream "Packets" encompass all STREAM_PACKET_t valid elements.
            /// \param packet UEI stream packet to be checked.
            /// \return Wether the packet is a stream sample or not
            static bool isStreamPacket(const aPacket* packet);
        
            /// Convenience function to determine wether the value is a stream sample.
            /// Stream "Sample" encompasses all STREAM_KEY_t except for kSTREAM_PACKET_BYTES
            /// which have a varied structure and depend on the cmd/option/index.
            /// Calling isStreamPacket prior is not required as this function will verify the packet type
            /// \param packet UEI stream packet to be checked.
            /// \return Wether the packet is a stream sample or not
            static bool isStreamSample(const aPacket* packet);
        
            /// Convenience function to unpack the stream samples timestamp and value.
            /// Calling isStreamSample prior is not required as this function will verify the packet type.
            /// \param packet UEI stream packet to be unpacked.
            /// \param timestamp Variable to be filled with stream sample timestamp. (optional)
            /// \param value Variable to be filled with the stream sample (optional).
            /// May require casting to signed value depending on the cmd/option code.
            /// \return ::aErrPacket - Not a stream packet
            /// \return ::aErrUnknown - Unknown decoding issue.
            /// \return ::aErrNone - success.
            static aErr getStreamSample(const aPacket* packet,
                                        uint64_t* timestamp = NULL,
                                        uint32_t* value = NULL,
                                        uint8_t* subindex = NULL);
        
            /// Helper function for extracting the parts of a timestamp.
            /// \param timestamp - Value acquired from Link::getStreamSample
            /// \param seconds - Seconds element from timestamp.
            /// Refers to the seconds since firmware boot.
            /// \param uSeconds - Micro second element from the timestamp.
            /// Refers to the micro seconds from firmware boot.
            /// Micro seconds rolls over to seconds. Value range: 0-99999
            static void getTimestampParts(const uint64_t timestamp,
                                          uint32_t* seconds,
                                          uint32_t* uSeconds);
        
            /////////////////////////////////////////////////////////////////////
            //
            // Routines to handle packet logging
            //
            /////////////////////////////////////////////////////////////////////

            /// Enable Packet logging.
            ///
            /// Enable packet logging for this link. Enables the packet logging buffer, and writes
            /// packet traffic out to the file specified by logname.
            /// \param logname the path and filename indicating where to write the packet log.
            /// \return aErr returns appropriate errors if it fails to enable the packet log.
            aErr enablePacketLog(const char* logname);
            
            /// Disable Packet logging.
            ///
            /// disable packet logging for this link. Disables the packet log.
            /// \return aErr returns appropriate errors if it fails to disable the debug log.
            aErr disablePacketLog(void);
            
            /// Filter function for Streaming packets.  This is used internally whenever streaming
            /// is enabled.  Exposed for unit-testing purposes only.
            /// \param packet UEI stream packet to be checked/filtered.
            /// \param ref Opaque reference handle
            static bool linkStreamFilter(const aPacket* packet, void* ref);
        
            /// For Internal use only!
            aErr getFactoryData(const uint8_t module,
                                const uint8_t command,
                                uint8_t* pData,
                                const size_t dataLength,
                                size_t* unloadedLength);
            
            /// For Internal use only!
            aErr setFactoryData(const uint8_t module,
                                const uint8_t command,
                                const uint8_t* pData,
                                const size_t dataLength);
        
        
            
        private:
            class impl; impl* zit;
        };


        /// ModuleClass:
        /// The Module class provides a generic interface to a BrainStem hardware module.
        /// The Module class is the parent class for all BrainStem modules. Each module
        /// inherits from Module and implements its hardware specific features.
        class aLIBEXPORT Module
        {
        public:
			
            /// Constructor. Implicitly creates a link object with no specifier. Most often objects
            /// created with this constructor will use linkDiscoverAndConnect to find and connect to a module.
            /// \param address The BrainStem network address of the module. The default address
            /// (or base address for modules that support address offsets) is defined in each
            /// module's "Defs.h" header.
            /// \param model Acroname model number.
            Module(const uint8_t address, const uint8_t model = 0);
            
            Module(const uint8_t address, bool bAutoNetworking, const uint8_t model = 0);
            
            /// Destructor.
            virtual ~Module(void);
            
            /// Connect using the current link specifier.
            /// \param type - Transport on which to search for available BrainStem link
            /// modules. See the \ref linkType "transport" enum for supported transports.
            /// \param serialNum - Specify a serial number to connect to a specific
            /// link module. Use 0 to connect to the first link module found.
            /// \return ::aErrBusy - if the module is already in use.
            /// \return ::aErrParam - if the type is incorrect or serialNum is not specified
            /// \return ::aErrNotFound - if the module cannot be found.
            /// \return ::aErrNone If the connect was successful.
            aErr connect(const linkType type,
                         const uint32_t serialNum);
            
            /// Connect to a link with a fully defined specifier.
            /// \param linkSpecifier - Connect to module with specifier.
            /// \return ::aErrInitialization - If there is currently no link object.
            /// \return ::aErrBusy - If the link is currently connected.
            /// \return ::aErrParam - if the specifier is incorrect.
            /// \return ::aErrNotFound - if the module cannot be found.
            /// \return ::aErrNone If the connect was successful.
            aErr connectFromSpec(const linkSpec linkSpecifier);
            
            /// A discovery-based connect.
            /// This member function will connect to the first available BrainStem
            /// found on the given transport. If the
            /// serial number is passed, it will only connect to the module with that serial number.
            /// Passing 0 as the serial number will create a link to the first link module found
            /// on the specified transport.
            /// If a link module is found on the specified transport, a connection will
            /// \param type - Transport on which to search for available BrainStem link
            /// modules. See the \ref linkType "transport" enum for supported transports.
            /// \param serialNum - Specify a serial number to connect to a specific
            /// link module. Use 0 to connect to the first link module found.
            /// \return ::aErrBusy - if the module is already in use.
            /// \return ::aErrParam - if the transport type is undefined.
            /// \return ::aErrNotFound - if the module cannot be found.
            /// \return ::aErrNone If the connect was successful.
            aErr discoverAndConnect(linkType type,
                                    const uint32_t serialNum = 0);
        
            /// Connect using link from another Module.
            /// This member function will connect to the same BrainStem used by given Module.
            /// If a link module is found on the specified transport, a connection will be made
            /// \param pModule - Pointer to a valid Module class object.
            /// \return ::aErrParam - if the module is undefined.
            /// \return ::aErrNone  - if the connect was successful.
            aErr connectThroughLinkModule(Module* pModule);
            
            /// Is the link connected to the BrainStem Module.
            bool isConnected(void);
            
            /// Check the status of the BrainStem module connection.
            /// \return linkStatus (see aLink.h for status values)
            linkStatus getStatus(void);

            /// Disconnect from the BrainStem module.
            /// \return ::aErrResource - If the there is no valid connection.
            /// \return ::aErrConnection - If the disconnect failed, due to a
            /// communication issue.
            /// \return ::aErrNone If the disconnect was successful.
            aErr disconnect(void);
            
            /// Reconnect using the current link specifier.
            /// \return ::aErrBusy - if the module is already in use.
            /// \return ::aErrParam - if the specifier is incorrect.
            /// \return ::aErrNotFound - if the module cannot be found.
            /// \return ::aErrNone If the connect was successful.
            aErr reconnect();

            /// Get the current link object.
            /// \return The link associated with the module.
            Link* getLink(void) const;
        
            /// Gets the links current network configuration
            /// \param config Variable to be filled with the config
            /// \return aErrNone on success.
            ///         aErrNotReady if the module does not have a link
            ///         aErrParam if config is NULL
            aErr getConfig(aEtherConfig* config);
        
            /// Sets the links network configuration.
            /// Configuration must be applied BEFORE connecting
            /// \param config Configuration to be applied
            /// \return aErrNone on success.
            ///         aErrPermission if the module is currently connected.
            ///         aErrNotReady if the module does not have a link
            aErr setConfig(const aEtherConfig config);

            /// Accessor to get the address of the BrainStem module associated with the instance
            /// on the host machine.  (Not to be confused with the System entity which effects the
            /// device hardware.)
            /// \return The module address.
            uint8_t getModuleAddress(void) const;
            
            /// Accessor to set the address of the BrainStem module associated with the instance
            /// on the host machine.  (Not to be confused with the System entity which effects the
            /// device hardware.)
            /// \param address The module address.
            void setModuleAddress(const uint8_t address);
            
            /// Get linkSpecifier
            /// \param spec - allocated linkspec struct will be filled with spec.
            /// \return aErrNone - If the module does not have a spec.
            aErr getLinkSpecifier(linkSpec* spec);
            
            /// Queries the module to determine if it implements a UEI. Each
            /// UEI has a command, option or variant, index and flag. The hasUEI method
            /// queries for a fully specified UEI.
            /// Returns aErrNone if the variation is supported and an appropriate error
            /// if not. This call is blocking for up to the nMSTimeout period.
            /// \param command One of the UEI commands (cmdXXX).
            /// \param option The option or variant of the command.
            /// \param index The entity index.
            /// \param flags The flags (ueiOPTION_SET or ueiOPTION_GET).
            /// \return ::aErrNone - The module supports this command and access flags.
            /// \return ::aErrMode - The module supports the command but not the access
            ///                    flag.
            /// \return ::aErrNotFound - The module does not support the command, option,
            ///                        or index.
            /// \return ::aErrTimeout - The request timed out without a response.
            /// \return ::aErrConnection - There is no active link
            aErr hasUEI(const uint8_t command,
                        const uint8_t option,
                        const uint8_t index,
                        const uint8_t flags);

            /// Queries the module to determine how many entities of the specified
            /// class are implemented by the module. Zero is a valid return value.
            /// For example, calling classQuantity with the command parameter of
            /// cmdANALOG would return the number of analog entities implemented by the module.
            /// \param command One of UEI commands (cmdXXX).
            /// \param count When the request is successful count
            ///              is updated with the number of entities found.
            /// \return ::aErrNone - Success.
            /// \return ::aErrTimeout - The request timed out without a response.
            /// \return ::aErrConnection - There is no active link.
            aErr classQuantity(const uint8_t command,
                               uint8_t* count);

            /// Queries the module to determine how many subclass entities of the specified
            /// class are implemented by the module for a given entity index. This is used
            /// for entities which may be 2-dimensional. E.g. cmdMUX subclasses are the number
            /// of channels supported by a particular mux type (index); as a specific example,
            /// a module may support 4 UART channels, so subClassQuantity(cmdMUX, aMUX_UART...)
            /// could return 4.
            /// Zero is a valid return value.
            /// \param command One of the UEI commands (cmdXXX).
            /// \param index The entity index.
            /// \param count The number of subclasses found.
            /// \return ::aErrNone - Success.
            /// \return ::aErrTimeout - The request timed out waiting for response.
            /// \return ::aErrConnection - There is no active link.
            aErr subClassQuantity(const uint8_t command,
                                  const uint8_t index,
                                  uint8_t* count);

            /// Queries the module the group assigned to an entity and index. Entities groups
            /// are used to specify when certain hardware features are fundamentally related. E.g.
            /// certain hardware modules may have some digital pins associated with an adjustable
            /// voltage rail; these digitals would be in the same group as the rail.
            /// Zero is the default group.
            /// \param command One of the UEI commands (cmdXXX).
            /// \param index The entity index.
            /// \param group Upon success, group is filled with the entities group value.
            /// \return ::aErrNone - Success.
            /// \return ::aErrTimeout - The request timed out without response.
            /// \return ::aErrConnection - There is no active link.
            aErr entityGroup(const uint8_t command,
                             const uint8_t index,
                             uint8_t* group);
            
            /// Sends a debug packet to the module containing the provided data.
            /// Modules receiving debug packets simply echo the packet back to the sender.
            /// If the round-trip is successful, the reply data will match the data sent.
            /// This method returns aErrNone when successful, if not successful,
            /// an appropriate error is returned.
            /// \param pData A pointer to an array of data to be sent in the debug packet.
            /// \param length The length of the data array.
            /// \return ::aErrNone - Success.
            /// \return ::aErrTimeout - Timeout occurred without response.
            /// \return ::aErrConnection - No active link exists.
            aErr debug(const uint8_t* pData,
                       const uint8_t length);
            
            /// Sets the networking mode of the module object.
            /// By default the module object is configure to automatically adjust
            /// its address based on the devices current module address. So that,
            /// if the device has a software or hardware offset it will still be
            /// able to communication with the device. If advanced networking is required
            /// the auto networking mode can be turned off.
            /// \param mode True/1 for Auto Networking, False/0 for manual networking
            void setNetworkingMode(const bool mode);
            
            aErr enablePacketLog(const char* packetLogName = "StemDebug");
            
            aErr disablePacketLog(void);

        private:
            Module();
            Link* m_pLink;
            uint8_t m_address;
            bool m_bAutoNetworking;
            uint8_t m_model;
            
            aErr _autoNetwork(void);
        };
        

        /// \defgroup EntityReturnValues Common EntityClass Return Values
        /// Common EntityClass Return Values
        /// @{
        ///     - ::aErrNone - Action completed successfully.
        ///     - ::aErrTimeout - Request timed out without response.
        ///     - ::aErrConnection - No active link.
        /// @}

        /// EntityClass:
        /// The EntityClass is the base class for interacting with BrainStem UEI entities.
        /// All BrainStem UEI classes inherit from EntityClass. Advanced users may use
        /// EntityClass to extend BrainStem functionality specific to their needs.
        class aLIBEXPORT EntityClass
        {
        public:

            /// Constructor.
            EntityClass(void);

            /// Destructor.
            virtual ~EntityClass(void);
        
            /// init.
            ///
            /// Initialize the entity class.
            /// \param pModule The BrainStem module object.
            /// \param command The command of the UEI.
            /// \param index The index of the UEI entity.
            void init(Module* pModule,
                      const uint8_t command,
                      const uint8_t index);

            /// A callUEI is a setUEI that has no data length.
            /// \param option An option for the UEI.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr callUEI(const uint8_t option);


            /// Set a byte value.
            /// \param option The option for the UEI.
            /// \param byteValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI8(const uint8_t option,
                         const uint8_t byteValue);

            /// Set a byte value with a subindex.
            /// \param option The option for the UEI.
            /// \param param of the option.
            /// \param byteValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI8(const uint8_t option,
                         const uint8_t param,
                         const uint8_t byteValue);

            /// Get a byte value.
            /// \param option The option for the UEI.
            /// \param byteValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI8(const uint8_t option,
                         uint8_t* byteValue);

            /// Get a byte value with a parameter.
            /// \param option The option for the UEI.
            /// \param param The parameter.
            /// \param byteValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI8(const uint8_t option,
                         const uint8_t param,
                         uint8_t* byteValue);

            /// Set a 2-byte value.
            /// \param option The option for the UEI.
            /// \param shortValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI16(const uint8_t option,
                          const uint16_t shortValue);

            /// Set a 2-byte value with a parameter.
            /// \param option The option for the UEI.
            /// \param param The parameter.
            /// \param shortValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI16(const uint8_t option,
                          const uint8_t param,
                          const uint16_t shortValue);

            /// Get a 2-byte value.
            /// \param option The option for the UEI.
            /// \param shortValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI16(const uint8_t option,
                          uint16_t* shortValue);

            /// Get a 2-byte value with a parameter.
            /// \param option The option for the UEI.
            /// \param param The parameter.
            /// \param shortValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI16(const uint8_t option,
                          const uint8_t param,
                          uint16_t* shortValue);

            /// Set a 4-byte value.
            /// \param option The option for the UEI.
            /// \param intValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI32(const uint8_t option,
                          const uint32_t intValue);

            /// Set a 4-byte value, with a subindex parameter.
            /// \param option The option for the UEI.
            /// \param subIndex The subindex to set.
            /// \param intValue The value.
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEI32(const uint8_t option,
                          const uint8_t subIndex,
                          const uint32_t intValue);

            /// Get a 4-byte value.
            /// \param option The option for the UEI.
            /// \param intValue The 4 byte value
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI32(const uint8_t option,
                          uint32_t* intValue);

            /// Get a 4-byte value with parameter.
            /// \param option The option for the UEI.
            /// \param param The parameter.
            /// \param intValue The 4 byte value
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEI32(const uint8_t option,
                          const uint8_t param,
                          uint32_t* intValue);

            /// Set a multi-byte value.
            /// \param option The option for the UEI.
            /// \param bufPtr The pointer to a data buffer
            /// \param bufLen The length of the data buffer
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setUEIBytes(const uint8_t option,
                             const uint8_t* bufPtr,
                             const size_t bufLen);
            
            /// Unloads UEI Bytes data as byte data
            /// \param option The option for the UEI.
            /// \param buf Start of where data should be stored..
            /// \param bufLength Size of the buffer
            /// \param unloadedLength Amount of data unloaded (in bytes)
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEIBytes(const uint8_t option,
                              uint8_t* buf,
                              const size_t bufLength,
                              size_t* unloadedLength);
        
            //Helper function for evaluating the sequence byte of a UEI Bytes call.
            /// \param packet UEI packet to be checked/filtered.
            /// \return The sequence number of the byte.
            static uint8_t getUEIBytesSequence(const aPacket* packet);
            
            //Helper function for evaluating the continue bit of a UEI Bytes call.
            /// \param packet UEI packet to be checked/filtered.
            /// \return True - Continue bit is set (more packets to come); False - Continue bit is not set (first or last packet).
            static bool getUEIBytesContinue(const aPacket* packet);


            //Helper function for UEIBytes get checks, specifically checking and fixing unload size
            /// \param unloadedLength Amount of data unloaded (in bytes)
            /// \param valueSize The base type size in this array
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr getUEIBytesCheck(size_t* unloadedLength,
                                    const size_t valueSize);

            /// Get the UEI entity index.
            /// \return The 1 byte index of the UEI entity.
            uint8_t getIndex(void) const;
            
            /// Drain all packets matching this UEI from the packet fifo.
            ///
            /// This functionality is useful in rare cases where packet
            /// synchronization is lost and a valid return packet is not
            /// accessible.
            aErr drainUEI(const uint8_t option);
            
            /// Filter function for UEI Bytes calls. Exposed for unit-testing purposes only.
            /// \param packet UEI packet to be checked/filtered.
            /// \param ref Opaque reference handle
            static uint8_t sUEIBytesFilter(const aPacket* packet, const void* ref);
        
            /// Enables streaming for all possible option codes within the cmd and index the entity was created for.
            /// \param enabled The state to be applied. 0 = Disabled; 1 = enabled
            /// \return Returns \ref EntityReturnValues "common entity" return values
            aErr setStreamEnabled(uint8_t enabled);
        
            /// Registers a callback function based on a specific option code.
            /// Option code applies to the cmd and index of the called API.
            /// \param option option to filter by (supports Wildcards)
            /// \param enable True - installs/updates callback and ref; False - uninstalls callback
            /// \param cb Callback to be executed when a new packet matching the criteria is received.
            /// \param pRef Pointer to user reference for use inside the callback function.
            /// \return aErrNotFound - Item not found (uninstalling only)
            /// \return aErrNone - success
            aErr registerOptionCallback(const uint8_t option,
                                        const bool enable,
                                        Link::streamCallback_t cb,
                                        void* pRef);
        
            /// Gets all available stream values associated with the cmd and index of the called API.
            /// Keys can be decoded with Link::getStreamKeyElement.
            /// \param status map of option value pairs to be filled.
            /// Option codes are based on the cmd and index of the calling API.
            /// \return aErrParam if status is null
            /// \return aErrResource - if the link is not valid
            /// \return aErrNone - success
            aErr getStreamStatus(std::map<uint64_t, uint32_t>* status);

        protected:

            aErr sendPacket(const uint8_t command,
                            const uint8_t length,
                            const uint8_t* data);

            aErr receivePacket(const uint8_t* match,
                               uint8_t* length,
                               uint8_t* data);

            aErr getUEI(const uint8_t option,
                        uei* d);

            aErr setUEI(const uint8_t option,
                        uei* d);

            aErr setUEI(const uint8_t option,
                        const uint8_t subIndex,
                        uei* d);

            aErr awaitUEI32Val(const uint32_t option,
                               uint32_t* intValue,
                               const uint32_t msTimeout);
        
            
            
            class impl; impl* zit;
        };

    } // namespace BrainStem
} // namespace Acroname

#endif // __BrainStem_core_H__
