//
// devsyn.h - include file for TANGO api device asynchronous calls
//
//
// Copyright (C) :      2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015
//                        European Synchrotron Radiation Facility
//                      BP 220, Grenoble 38043
//                      FRANCE
//
// This file is part of Tango.
//
// Tango 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.
//
// Tango 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 Tango.  If not, see <http://www.gnu.org/licenses/>.

#ifndef _DEVASYN_H
#define _DEVASYN_H

#include <tango/tango.h>

#include <map>

namespace Tango
{

//------------------------------------------------------------------------------

class DeviceProxy;
class Connection;
class DeviceData;
class DeviceAttribute;
class NamedDevFailedList;
class EventData;
class AttrConfEventData;
class DataReadyEventData;
class DevIntrChangeEventData;
class PipeEventData;
class EventDataList;
class AttrConfEventDataList;
class DataReadyEventDataList;
class DevIntrChangeEventDataList;
class PipeEventDataList;
class EventConsumer;
class EventConsumerKeepAliveThread;

/********************************************************************************
 *                                                                              *
 *                         CmdDoneEvent class                                        *
 *                                                                                 *
 *******************************************************************************/

/**
 * Asynchronous command execution callback data
 *
 * This class is used to pass data to the callback method in asynchronous callback model for command execution.
 *
 *
 * @headerfile tango.h
 * @ingroup Client
 */

class CmdDoneEvent
{
  public:
    ///@privatesection
    CmdDoneEvent(DeviceProxy *dev, std::string &cmd, DeviceData &arg, DevErrorList &err_in) :
        device(dev),
        cmd_name(cmd),
        argout(arg),
        errors(err_in)
    {
        err = errors.length() != 0;
    }

    ///@publicsection
    Tango::DeviceProxy *device; ///< The DeviceProxy object on which the call was executed
    std::string &cmd_name;      ///< The command name
    DeviceData &argout;         ///< The command argout
    bool err;                   ///< A boolean flag set to true if the command failed. False otherwise
    DevErrorList &errors;       ///< The error stack

  private:
    class CmdDoneEventExt
    {
      public:
        CmdDoneEventExt() { }
    };

    std::unique_ptr<CmdDoneEventExt> ext;
};

/********************************************************************************
 *                                                                                 *
 *                         AttrReadEvent class                                        *
 *                                                                                 *
 *******************************************************************************/

/**
 * Asynchronous read attribute execution callback data
 *
 * This class is used to pass data to the callback method in asynchronous callback model for read_attribute(s)
 * execution
 *
 *
 * @headerfile tango.h
 * @ingroup Client
 */

class AttrReadEvent
{
  public:
    ///@privatesection
    AttrReadEvent(DeviceProxy *dev,
                  std::vector<std::string> &att_names,
                  std::vector<DeviceAttribute> *arg,
                  DevErrorList &err_in) :
        device(dev),
        attr_names(att_names),
        argout(arg),
        errors(err_in)
    {
        err = errors.length() != 0;
    }

    ///@publicsection
    Tango::DeviceProxy *device;           ///< The DeviceProxy object on which the call was executed
    std::vector<std::string> &attr_names; ///< The attribute name list
    std::vector<DeviceAttribute> *argout; ///< The attribute data
    bool err;                             ///< A boolean flag set to true if the request failed. False otherwise
    DevErrorList &errors;                 ///< The error stack

  private:
    class AttrReadEventExt
    {
      public:
        AttrReadEventExt() { }
    };

    std::unique_ptr<AttrReadEventExt> ext;
};

/********************************************************************************
 *                                                                                 *
 *                         AttrWrittenEvent class                                    *
 *                                                                                 *
 *******************************************************************************/

/**
 * Asynchronous write attribute execution callback data
 *
 * This class is used to pass data to the callback method in asynchronous callback model for write_attribute(s)
 * execution.
 *
 *
 * @headerfile tango.h
 * @ingroup Client
 */

class AttrWrittenEvent
{
  public:
    ///@privatesection
    AttrWrittenEvent(DeviceProxy *dev, std::vector<std::string> &att_names, NamedDevFailedList &err_in) :
        device(dev),
        attr_names(att_names),
        errors(err_in)
    {
        err = errors.call_failed();
    }

    ///@publicsection
    Tango::DeviceProxy *device;           ///< The DeviceProxy object on which the call was executed
    std::vector<std::string> &attr_names; ///< The attribute name list
    bool err;                             ///< A boolean flag set to true if the request failed. False otherwise
    NamedDevFailedList &errors;           ///< The error stack

  private:
    class AttrWrittenEventExt
    {
      public:
        AttrWrittenEventExt() { }
    };

    std::unique_ptr<AttrWrittenEventExt> ext;
};

/********************************************************************************
 *                                                                                 *
 *                         CallBack class                                            *
 *                                                                                 *
 *******************************************************************************/

/**
 * Event and asynchronous (callback model) calls base class
 *
 * When using the event push model (callback automatically executed), there are some cases (same callback
 * used for events coming from different devices hosted in device server process running on different hosts)
 * where the callback method could be executed concurently by different threads started by the ORB. The
 * user has to code his callback method in a thread safe manner.
 *
 *
 * @headerfile tango.h
 * @ingroup Client
 */

class CallBack
{
    friend class EventConsumer;
    friend class EventConsumerKeepAliveThread;

  public:
#ifdef GEN_DOC
    /**
     * Asynchronous command execution callback method
     *
     * This method is defined as being empty and must be overloaded by the user when the asynchronous callback
     * model is used. This is the method which will be executed when the server reply from a command_inout is
     * received in both push and pull sub-mode.
     *
     * @param cde The command data
     */
    virtual void cmd_ended(CmdDoneEvent *cde) { }

    /**
     * Asynchronous read attribute execution callback method
     *
     * This method is defined as being empty and must be overloaded by the user when the asynchronous callback
     * model is used. This is the method which will be executed when the server reply from a read_attribute(s) is
     * received in both push and pull sub-mode.
     *
     * @param are The read attribute data
     */
    virtual void attr_read(AttrReadEvent *are) { }

    /**
     * Asynchronous write attribute execution callback method
     *
     * This method is defined as being empty and must be overloaded by the user when the asynchronous callback
     * model is used. This is the method which will be executed when the server reply from a write_attribute(s)
     * is received in both push and pull sub-mode.
     *
     * @param awe The write attribute data
     */
    virtual void attr_written(AttrWrittenEvent *awe) { }

    /**
     * Event callback method
     *
     * This method is defined as being empty and must be overloaded by the user when events are used. This is
     * the method which will be executed when the server send event(s) to the client.
     *
     * @param ed The event data
     */
    virtual void push_event(EventData *ed) { }

    /**
     * attribute configuration change event callback method
     *
     * This method is defined as being empty and must be overloaded by the user when events are used. This
     * is the method which will be executed when the server send attribute configuration change event(s) to the
     * client.
     *
     * @param ace The attribute configuration change event data
     */
    virtual void push_event(AttrConfEventData *ace) { }

    /**
     * Data ready event callback method
     *
     * This method is defined as being empty and must be overloaded by the user when events are used. This is
     * the method which will be executed when the server send attribute data ready event(s) to the client.
     *
     * @param dre The data ready event data
     */
    virtual void push_event(DataReadyEventData *dre) { }

    /**
     * Device interface change event callback method
     *
     * This method is defined as being empty and must be overloaded by the user when events are used. This is
     * the method which will be executed when the server send device interface change event(s) to the client.
     *
     * @param dic The device interface change event data
     */
    virtual void push_event(DevIntrChangeEventData *dic) { }

    /**
     * Pipe event callback method
     *
     * This method is defined as being empty and must be overloaded by the user when events are used. This is
     * the method which will be executed when the server send pipe event(s) to the client.
     *
     * @param ped The pipe event data
     */
    virtual void push_event(PipeEventData *ped) { }
#else
    virtual void cmd_ended(CmdDoneEvent *) { }

    virtual void attr_read(AttrReadEvent *) { }

    virtual void attr_written(AttrWrittenEvent *) { }

    virtual void push_event(EventData *) { }

    virtual void push_event(AttrConfEventData *) { }

    virtual void push_event(DataReadyEventData *) { }

    virtual void push_event(DevIntrChangeEventData *) { }

    virtual void push_event(PipeEventData *) { }
#endif

    /// @privatesection
    virtual ~CallBack() { }

  private:
    class CallBackExt
    {
      public:
        CallBackExt() { }
    };

    std::unique_ptr<CallBackExt> ext;
};

//------------------------------------------------------------------------------

class UniqIdent : public omni_mutex
{
  public:
    UniqIdent()
    {
        omni_mutex_lock sync(*this);
        ctr = 0;
    }

    long get_ident()
    {
        omni_mutex_lock sync(*this);
        return ++ctr;
    }

    long ctr;
};

class TgRequest
{
  public:
    enum ReqType
    {
        CMD_INOUT,
        READ_ATTR,
        WRITE_ATTR_SINGLE,
        WRITE_ATTR
    };

    TgRequest(CORBA::Request_ptr re, ReqType ty) :
        request(re),
        req_type(ty),
        cb_ptr(nullptr),
        arrived(false),
        dev(nullptr)
    {
    }

    TgRequest(CORBA::Request_ptr re, ReqType ty, CallBack *cb) :
        request(re),
        req_type(ty),
        cb_ptr(cb),
        arrived(false),
        dev(nullptr)
    {
    }

    TgRequest(Tango::Connection *con, ReqType ty, CallBack *cb) :
        request(nullptr),
        req_type(ty),
        cb_ptr(cb),
        arrived(false),
        dev(con)
    {
    }

    CORBA::Request_ptr request;
    ReqType req_type;
    CallBack *cb_ptr;
    bool arrived;
    Connection *dev;
};

class AsynReq : public omni_mutex
{
  public:
    AsynReq(UniqIdent *ptr) :
        ui_ptr(ptr),
        cond(this)
    {
    }

    ~AsynReq()
    {
        delete ui_ptr;
    }

    TgRequest &get_request(long);
    TgRequest &get_request(CORBA::Request_ptr);
    TgRequest *get_request(Tango::Connection *);

    long store_request(CORBA::Request_ptr, TgRequest::ReqType);
    void store_request(CORBA::Request_ptr, CallBack *, Connection *, TgRequest::ReqType);

    void remove_request(long);
    void remove_request(Connection *, CORBA::Request_ptr);

    size_t get_request_nb()
    {
        omni_mutex_lock sync(*this);
        return asyn_poll_req_table.size();
    }

    size_t get_cb_request_nb()
    {
        omni_mutex_lock sync(*this);
        return cb_req_table.size();
    }

    size_t get_cb_request_nb_i()
    {
        return cb_req_table.size();
    }

    void mark_as_arrived(CORBA::Request_ptr req);

    std::multimap<Connection *, TgRequest> &get_cb_dev_table()
    {
        return cb_dev_table;
    }

    void mark_as_cancelled(long);
    void mark_all_polling_as_cancelled();

    void wait()
    {
        cond.wait();
    }

    void signal()
    {
        omni_mutex_lock sync(*this);
        cond.signal();
    }

  protected:
    std::map<long, TgRequest> asyn_poll_req_table;
    UniqIdent *ui_ptr;

    std::multimap<Connection *, TgRequest> cb_dev_table;
    std::map<CORBA::Request_ptr, TgRequest> cb_req_table;

    std::vector<long> cancelled_request;

  private:
    omni_condition cond;
    bool remove_cancelled_request(long);
};

} // namespace Tango

#endif /* _DEVASYN_H */
