/*****************************************************************************
 *
 * $Id$
 *
 * Copyright (C) 2009 - 2012  Richard Hacker <lerich@gmx.net>
 *                            Florian Pose <fp@igh-essen.com>
 *
 * This file is part of the PdCom library.
 *
 * The PdCom library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The PdCom library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the PdCom Library. If not, see <http://www.gnu.org/licenses/>.
 *
 * vim: tw=78 spelllang=en spell
 *
 ****************************************************************************/

#ifndef PDCOM_PROCESS_H
#define PDCOM_PROCESS_H

#include "Exception.h"
#include "Time.h"

#include <list>
#include <map>
#include <set>
#include <string>

/** Process::sendBroadcast() is available.
 */
#define PDCOM_HAVE_BROADCAST

/****************************************************************************/

namespace PdCom {

class ProtocolHandler;
class Variable;
class ProcessStreambuf;

/****************************************************************************/

/** Process interface class.
 *
 * This is the main class of PdCom. A Process is the abstraction of an
 * existing real-time process, providing methods to access the process'
 * variables.
 *
 * Process shall provide non-real-time process data communication to real-time
 * processes, independent of the protocol. Currently, only the XML-based MSR
 * protocol used by the EtherLab project (http://etherlab.org/en) is
 * implemented, but other protocols can be added easily.
 *
 * It is important to note that PdCom does not communicate with the real-time
 * process directly. System calls are kept to a minimum and only portable
 * common C/C++ library functionality is required. Communication input and
 * output has to be implemented by the user of this library, which enables the
 * use of this library within an external select() loop. Instead of read()ing
 * and write()ing directly to the stream (which require system calls and can
 * block), these functions are left to the implementation of PdCom. Process
 * only provides a method to parse a buffer with incoming data from the
 * real-time process, and another to indicate that it wishes to send data to
 * the real-time process.
 *
 * This method provides maximum portability across various platforms and
 * programming environments.
 *
 * The physical communication (network, socket, etc) has to be implemented by
 * subclassing Process and reimplementing its virtual methods. The example/
 * directory contains an example implementation.
 *
 * The methods to access the process variables should only be used after
 * sigConnected() is called or isConnected() returns \a true.
 */
class Process {
    friend class Variable;
    friend class ProtocolHandler;

    public:
        /** Constructor.
         *
         * \param buflen Buffer length for outgoing communication to
         * the real-time process.
         */
        Process(unsigned int buflen = 4096);

        /** Destructor.
         */
        virtual ~Process();

        /** Set to true to disable changing variables in the process
         */
        bool readOnly;

        /** Get information map from Protocol Handler.
         *
         * The return map \b must contain:
         * \arg \c protocol: Name of the protocol (PdCom|MSR).
         *
         * The return map \b can contain:
         * \arg \c features: List of protocol specific features.
         * \arg \c app: Application name.
         * \arg \c appversion: Application version.
         * \arg \c version: Protocol version number.
         *
         * Other information is allowed. The list can change depending
         * on the state of protocol initialisation.
         *
         * If no protocol handler is initialised, an empty map is returned.
         *
         * \return map of name->feature
         */
        std::map<std::string, std::string> getProtocolInformation() const;

        /** Process variable set.
         *
         * Used as return value of getVariables().
         */
        typedef std::set<Variable *> VariableSet;

        /** Get a set of all variable paths.
         *
         * This method returns the path of all known process variables as a
         * set.
         *
         * If this method is called prior to the completion of protocol
         * initialisation, the set may be incomplete.
         *
         * \return Set of variables.
         */
        const VariableSet &getVariables() const;

        /** Find a variable by path.
         *
         * This searches the variable set for a variable with the given path.
         *
         * If this method is called prior to the completion of protocol
         * initialisation, it may be possible that a path is not found
         * even though it exists in the real-time process.
         *
         * \retval Pointer to the variable
         * \retval NULL if not found.
         */
        Variable *findVariable(
                const std::string &path /**< Path of the variable to find. */
                ) const;

        /** Process connected.
         *
         * This is \a true, after sigConnected() was called and the variable
         * set if filled.
         *
         * \retval True if protocol initialisation has been completed.
         */
        bool isConnected() const;

        /** Check output buffer.
         *
         * Poll the status of the output buffer.
         *
         * \retval True if the output buffer has data to be sent.
         * \retval False if there is no data in the output buffer.
         */
        bool hasData() const;

        /** Structure for client interaction
         */
        typedef struct {
            std::string prompt; /**< Prompt to show the user */
            std::string help;   /**< Help string */
            std::string response; /**< Users response, possibly containing
                                   *   a default */
        } ClientInteraction;

        /** Prompt the client for interaction.
         *
         * This method is called by protocol handlers to request information
         * required for login/authentication. Present the user with the list,
         * put the user's answer in response.
         *
         * The following prompts are standard across various protocols.
         * These can then be localised.
         * - Username
         * - Hostname
         * - Applicationname
         *
         * Various protocols have different values for \a prompt:
         *
         * MSR Protocol:
         * \arg \c Username Enter user name.
         * \arg \c Hostname Enter host name.
         * \arg \c Applicationname Enter name of application.
         *
         * \param title Title for client interaction.
         * \param description Description of the interaction.
         * \param help Detailed help instructions.
         * \param il Interaction list.
         *
         * \retval True for a positive response.
         * \retval False for a cancel.
         */
        virtual bool clientInteraction(
                const std::string& title,
                const std::string& description,
                const std::string& help,
                std::list<ClientInteraction>& il
                );

        /** Reset to initial state.
         *
         * This will cause the Process object to discard everything it knows
         * about the real-time process. Call this if the communication
         * channel is cut and needs to be set up again, for example.
         *
         * References to Variable will be invalid after this call.
         * All variable subscriptions will be cleared automatically, thereby
         * calling Subscriber::notifyDelete().
         */
        void reset();

        /** Successfully connected to process.
         *
         * This method is called when the protocol handler has completed
         * protocol initialisation. Before this method is called, process
         * variables may not be complete and calls to getVariables() and
         * findVariable() should be refrained from as they could deliver
         * incomplete results.
         *
         * This signal is called within the context of newData().
         *
         * Reimplement this method to receive the signal.
         */
        virtual void sigConnected();

        /** Request to send data to the process.
         *
         * This is a signal is generated by the protocol handler notifying
         * that there is data in its output buffer waiting to be transmitted.
         *
         * This signal is called within the context of newData().
         *
         * The data is only transmitted upon calling writeReady() and its
         * subsequent call to sendData().
         *
         * Reimplement this in your derived class. The default implementation
         * calls writeReady() until the output buffer is empty.
         */
        virtual void sendRequest();

        /** Ready to write.
         *
         * Call this method to notify the Process object that data can be
         * written to the process. It in turn calls sendData() with the
         * actual data to be transmitted.
         *
         * \retval <0 the (negative) return values from the call to
         * sendData().
         * \retval >0 There are still data waiting to be transmitted.
         * \retval 0 Transmission completed.
         */
        int writeReady();

        /** Request to send data.
         *
         * Reimplement this method in your derived class. This method is
         * called in the writeReady() context and it requests to send \a len
         * bytes from \a buf to the real-time process.
         *
         * Return the number of bytes written, negative numbers for errors.
         * Negative values are returned in the call to writeReady().
         */
        virtual int sendData(
                const char *buf, /**< Buffer containing the data to send. */
                size_t len /**< Number of bytes in \a buf. */
                ) = 0;

        /** New data has arrived.
         *
         * Call this method for the protocol handler to process the new data
         * that has arrived from the real-time process.
         *
         * \throw ProtocolException when there is an unrecoverable protocol
         *        error. When this happens, the process has to be reset().
         *        However, resetting may be unsuccessful due to the error
         *        being caused again.
         *
         * \return Number of bytes processed
         */
        size_t newData(
                const char *buf, /**< Buffer with new data. */
                size_t len /**< Number of bytes in \a buf. */
                );

        /** Log levels.
         *
         * \sa processMessage()
         * \sa protocolLog()
         * */
        typedef enum {
            LogError,   /**< Error level */
            LogWarn,    /**< Warning level */
            LogInfo,    /**< Infomational level */
            LogDebug    /**< Debug level */
        } LogLevel_t;

        /** Message from the real-time process.
         *
         * Reimplement this method to receive process messages.
         *
         * This signal is called within the context of newData()
         *
         * The default implementation will put these messages on stdout.
         */
        virtual void processMessage(
                const Time& time, /**< Time when message was sent */
                LogLevel_t level,    /**< Severity level */
                unsigned int messageNo, /**< Message Number */
                const std::string& message /**< Plain text */
                ) const;

        /** Message from the protocol handler.
         *
         * Reimplement this method to receive various messages that are
         * generated by the protocol handler.
         *
         * This signal is called within the context of newData()
         *
         * The default implementation puts these messages on stderr.
         */
        virtual void protocolLog(
                LogLevel_t level,     /**< Severity level */
                const std::string& message /**< Plain text */
                ) const;

        /** Send a broadcast message to the server and other clients.
         *
         * Check PDCOM_HAVE_BROADCAST define for availability.
         */
        void sendBroadcast(const std::string &,
                const std::string &attr = "text");

    private:
        PdCom::ProcessStreambuf * const sb; /**< Stream buffer. */
        std::ostream * const os; /**< Output stream. */
        std::ios_base::fmtflags os_flags; /**< Initial flags of the #os. */
        LogLevel_t logLevel;

        ProtocolHandler *handler; /**< The protocol-specific handler. */

        VariableSet variableSet;

        /** Delete a variable from the set.
         *
         * Called by Variable.
         */
        void variableDeleted(Variable* pv);

        /** Add a variable to the set.
         *
         * Called by Variable.
         */
        void newVariable(Variable *pv);
        void rmVariable(Variable *pv);

        /** Called by #handler when it has finished initialisation. */
        void protocolInitialised();
};

/****************************************************************************/

inline const std::set<Variable *> &Process::getVariables() const
{
	return variableSet;
}

/****************************************************************************/

} // namespace PdCom

#endif // PDCOM_PROCESS_H

/****************************************************************************/
