/*
 * SPDX-FileCopyrightText: 2004 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#ifndef TANGO_SERVER_ATTR_H
#define TANGO_SERVER_ATTR_H

#include <tango/common/tango_const.h>
#include <tango/server/AttrProperty.h>
#include <tango/server/MultiClassAttribute.h>
#include <tango/server/UserDefaultAttrProp.h>

#include <memory>
#include <string>
#include <vector>

namespace Tango
{

class WAttribute;
class Attribute;

/**
 * User class to create a no dimension attribute object.
 *
 * Information from this class and information fetched out from the Tango
 * database allows the Tango core software to create the Attribute object
 * for the attribute created by the user.
 *
 *
 * @headerfile tango.h
 * @ingroup Server
 */

class Attr
{
  public:
    /**@name Constructors
     * Two constructors are defined for this class */
    //@{
    /**
     * Constructs a newly allocated Attr object.
     * The attribute display level is set to OPERATOR.
     *
     * @param     name    The attribute name
     * @param    data_type    The attribute data type
     * @param    w_type    The attribute type (read, write, read with write ...)
     * @param    assoc    Name of the associated writable attribute. This is used
     * only the read with write attribute
     *
     */
    Attr(const char *name,
         long data_type,
         Tango::AttrWriteType w_type = Tango::READ,
         const char *assoc = AssocWritNotSpec);

    /**
     * Constructs a newly allocated Attr object.
     *
     * @param     name    The attribute name
     * @param    data_type    The attribute data type
     * @param    disp    The attribute display level
     * @param    w_type    The attribute type (read, write, read with write ...)
     * @param    assoc    Name of the associated writable attribute. This is used
     * only the read with write attribute
     *
     */
    Attr(const char *name,
         long data_type,
         Tango::DispLevel disp,
         Tango::AttrWriteType w_type = Tango::READ,
         const char *assoc = AssocWritNotSpec);

    /**
     * Constructs a newly allocated Attr object.
     *
     * @param     name    The attribute name
     * @param    disp    The attribute display level
     *
     */
    Attr(const char *name, Tango::DispLevel disp = Tango::OPERATOR);

    //@}

    /**@name Destructor
     * Only one destructor is defined for this class */
    //@{
    /**
     * The object destructor.
     */
    virtual ~Attr();
    //@}

    /**@name Miscellaneous methods*/
    //@{
    /**
     * Set default attribute properties
     *
     * @param    prop    The user default property class
     */
    void set_default_properties(UserDefaultAttrProp &prop);

    /**
     * Set the attribute display level
     *
     * @param    level    The attribute display level
     */
    void set_disp_level(Tango::DispLevel level)
    {
        disp_level = level;
    }

    /**
     * Set the attribute polling update period
     *
     * @param    update    The attribute polling period (in mS)
     */
    void set_polling_period(long update)
    {
        poll_period = update;
    }

    /**
     * Set the attribute as memorized in database (only for scalar and writable
     * attribute)
     * By default the setpoint will be written to the attribute during initialisation!
     * Use method set_memorized_init() with false as argument if you don't want this feature
     */
    void set_memorized();

    /**
     * Set the initialisation flag for memorized attributes
     *     true  = the setpoint value will be written to the attribute on initialisation
     *  false = only the attribute setpoint is initialised. No action is taken on the attribute
     *
     * @param    write_on_init    If true the setpoint value will be written to the attribute on initialisation
     */
    void set_memorized_init(bool write_on_init)
    {
        mem_init = write_on_init;
    }

    /**
     * Set a flag to indicate that the server fires change events manually without
     * the polling to be started for the attribute.
     * If the detect parameter is set to true, the criteria specified for the change
     * event are verified and the event is only pushed if they are fulfilled.
     * If detect is set to false the event is fired without checking!
     *
     * @param implemented True when the server fires change events manually.
     * @param detect Triggers the verification of the change event properties when set to true.
     */
    void set_change_event(bool implemented, bool detect)
    {
        fire_change_event = implemented;
        check_change_event = detect;
    }

    /**
     * Check if the change event is fired manually for this attribute.
     *
     * @return A boolean set to true if a manual fire change event is implemented.
     */
    bool is_change_event()
    {
        return fire_change_event;
    }

    /**
     * Check if the change event criteria should be checked when firing
     * the event manually.
     *
     * @return A boolean set to true if a change event criteria will be checked.
     */
    bool is_check_change_criteria()
    {
        return check_change_event;
    }

    /**
     * Set a flag to indicate that the server fires alarm events manually without
     * the polling to be started for the attribute.
     * If the detect parameter is set to true, the criteria specified for the alarm
     * event are verified and the event is only pushed if they are fulfilled.
     * If detect is set to false the event is fired without checking!
     *
     * @param implemented True when the server fires alarm events manually.
     * @param detect Triggers the verification of the alarm event properties when set to true.
     */
    void set_alarm_event(bool implemented, bool detect)
    {
        fire_alarm_event = implemented;
        check_alarm_event = detect;
    }

    /**
     * Check if the alarm event is fired manually for this attribute.
     *
     * @return A boolean set to true if a manual fire alarm event is implemented.
     */
    bool is_alarm_event()
    {
        return fire_alarm_event;
    }

    /**
     * Check if the alarm event criteria should be checked when firing
     * the event manually.
     *
     * @return A boolean set to true if a alarm event criteria will be checked.
     */
    bool is_check_alarm_criteria()
    {
        return check_alarm_event;
    }

    /**
     * Set a flag to indicate that the server fires archive events manually without
     * the polling to be started for the attribute
     * If the detect parameter is set to true, the criteria specified for the archive
     * event are verified and the event is only pushed if they are fulfilled.
     * If detect is set to false the event is fired without checking!
     *
     * @param implemented True when the server fires archive events manually.
     * @param detect Triggers the verification of the archive event properties when set to true.
     */
    void set_archive_event(bool implemented, bool detect)
    {
        fire_archive_event = implemented;
        check_archive_event = detect;
    }

    /**
     * Check if the archive event is fired manually for this attribute.
     *
     * @return A boolean set to true if a manual fire archive event is implemented.
     */
    bool is_archive_event()
    {
        return fire_archive_event;
    }

    /**
     * Check if the archive event criteria should be checked when firing
     * the event manually.
     *
     * @return A boolean set to true if a archive event criteria will be checked.
     */
    bool is_check_archive_criteria()
    {
        return check_archive_event;
    }

    /**
     * Set a flag to indicate that the server fires data ready events
     *
     * @param implemented True when the server fires data ready events
     */
    void set_data_ready_event(bool implemented)
    {
        fire_dr_event = implemented;
    }

    /**
     * Check if the data ready event is fired for this attribute.
     *
     * @return A boolean set to true if firing data ready event is implemented.
     */
    bool is_data_ready_event()
    {
        return fire_dr_event;
    }

    //@}

    /// @privatesection
    Attr(const Attr &);

    std::string &get_name()
    {
        return name;
    }

    std::string const &get_name() const
    {
        return name;
    }

    Tango::AttrDataFormat get_format()
    {
        return format;
    }

    Tango::AttrWriteType get_writable()
    {
        return writable;
    }

    long get_type()
    {
        return type;
    }

    Tango::DispLevel get_disp_level()
    {
        return disp_level;
    }

    long get_polling_period()
    {
        return poll_period;
    }

    bool get_memorized()
    {
        return mem;
    }

    bool get_memorized_init()
    {
        return mem_init;
    }

    std::string &get_assoc()
    {
        return assoc_name;
    }

    const std::string &get_cl_name()
    {
        return cl_name;
    }

    void set_cl_name(const std::string &cl)
    {
        cl_name = cl;
    }

    bool is_assoc()
    {
        return assoc_name != AssocWritNotSpec;
    }

    std::vector<AttrProperty> &get_class_properties()
    {
        return class_properties;
    }

    std::vector<AttrProperty> &get_user_default_properties()
    {
        return user_default_properties;
    }

    void set_class_properties(const std::vector<AttrProperty> &in_prop)
    {
        class_properties = in_prop;
    }

    void check_type();

    virtual bool is_fwd()
    {
        return false;
    }

    virtual void read(DeviceImpl *, Attribute &) { }

    virtual void write(DeviceImpl *, WAttribute &) { }

    virtual bool is_allowed(DeviceImpl *, AttReqType)
    {
        return true;
    }

    virtual bool same_type(const std::type_info &)
    {
        return false;
    }

    virtual std::string get_enum_type()
    {
        return std::string("Unknown");
    }

  protected:
    /// @privatesection
    std::string name;
    Tango::AttrDataFormat format;
    Tango::AttrWriteType writable;
    long type;
    std::string assoc_name;
    bool mem;
    bool mem_init;

    //
    // Ported from the extension class
    //

    Tango::DispLevel disp_level; // Display  level
    long poll_period;            // Polling period

    bool fire_change_event;
    bool fire_alarm_event;
    bool fire_archive_event;
    bool check_change_event;
    bool check_alarm_event;
    bool check_archive_event;
    bool fire_dr_event;

    std::vector<AttrProperty> class_properties;
    std::vector<AttrProperty> user_default_properties;

    void convert_def_prop(const std::string &, double &);
    void validate_def_prop(const std::string &, const char *);
    void validate_def_change_prop(const std::string &, const char *);
    void throw_incoherent_def_prop(const char *, const char *);
    void throw_invalid_def_prop(const char *, const char *);

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

    std::unique_ptr<AttrExt> ext; // Class extension

    std::string cl_name;
};

} // namespace Tango

#endif /* TANGO_SERVER_ATTRDESC_H */
