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

#ifndef TANGO_SERVER_ATTRIBUTE_TEMPL_H
#define TANGO_SERVER_ATTRIBUTE_TEMPL_H

#include <tango/server/Attribute.h>

#include <tango/client/Database.h>
#include <tango/server/DeviceClass.h>
#include <tango/server/DeviceImpl.h>
#include <tango/server/tango_clock.h>

#include <string>
#include <type_traits>

namespace Tango
{

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        Attribute::check_hard_coded_properties()
//
// description :
//        Check if the user tries to change attribute properties considered as hard coded
//      Throw exception in case of
//
// args :
//         in :
//            - user_conf : The attribute configuration sent by the user
//
//------------------------------------------------------------------------------------------------------------------

template <typename T>
void Attribute::check_hard_coded_properties(const T &user_conf)
{
    //
    // Check attribute name
    //

    std::string user_att_name(user_conf.name.in());
    std::transform(user_att_name.begin(), user_att_name.end(), user_att_name.begin(), ::tolower);
    if(user_att_name != get_name_lower())
    {
        throw_hard_coded_prop("name");
    }

    //
    // Check data type
    //

    if(user_conf.data_type != data_type)
    {
        throw_hard_coded_prop("data_type");
    }

    //
    // Check data format
    //

    if(user_conf.data_format != data_format)
    {
        throw_hard_coded_prop("data_format");
    }

    //
    // Check writable
    //

    if(user_conf.writable != writable)
    {
        throw_hard_coded_prop("writable");
    }

    //
    // Check max_dim_x
    //

    if(user_conf.max_dim_x != max_x)
    {
        throw_hard_coded_prop("max_dim_x");
    }

    //
    // Check max_dim_y
    //

    if(user_conf.max_dim_y != max_y)
    {
        throw_hard_coded_prop("max_dim_y");
    }

    //
    // Check writable_attr_name
    //

    std::string local_w_name(writable_attr_name);
    std::transform(local_w_name.begin(), local_w_name.end(), local_w_name.begin(), ::tolower);
    std::string user_w_name(user_conf.writable_attr_name.in());
    std::transform(user_w_name.begin(), user_w_name.end(), user_w_name.begin(), ::tolower);

    if(user_w_name != local_w_name)
    {
        throw_hard_coded_prop("writable_attr_name");
    }
}

//+-----------------------------------------------------------------------------------------------------------------
//
// method :
//        Attribute::set_hard_coded_properties()
//
// description :
//        Set some "hard coded" attribute properties. This method is used only in case of forwarded attribute
//
// args :
//         in :
//            - user_conf : The attribute configuration sent by the user
//
//------------------------------------------------------------------------------------------------------------------

template <typename T>
void Attribute::set_hard_coded_properties(const T &user_conf)
{
    data_type = user_conf.data_type;
    data_format = user_conf.data_format;
    writable = user_conf.writable;
    max_x = user_conf.max_dim_x;
    max_y = user_conf.max_dim_y;
    writable_attr_name = user_conf.writable_attr_name;
}

//+-------------------------------------------------------------------------------------------------------------------
//
// method :
//        Attribute::Attribute_2_AttributeValue_base
//
// description :
//        Build an AttributeValue_X object (the base part) from the Attribute object content
//
// arguments
//         in :
//            - d : The device to which the attribute belongs to
//        out :
//            - ptr : Pointer to the AttributeValue_X object to be filled in
//
//--------------------------------------------------------------------------------------------------------------------

template <typename T>
void Attribute::Attribute_2_AttributeValue_base(T *ptr, Tango::DeviceImpl *d)
{
    if((name_lower == "state") || (name_lower == "status"))
    {
        ptr->quality = Tango::ATTR_VALID;

        if(name_lower == "state")
        {
            ptr->value.dev_state_att(d->get_state());
        }
        else
        {
            Tango::DevVarStringArray str_seq(1);
            str_seq.length(1);
            str_seq[0] = CORBA::string_dup(d->get_status().c_str());

            ptr->value.string_att_value(str_seq);
        }

        ptr->time = make_TimeVal(std::chrono::system_clock::now());
        ptr->r_dim.dim_x = 1;
        ptr->r_dim.dim_y = 0;
        ptr->w_dim.dim_x = 0;
        ptr->w_dim.dim_y = 0;

        ptr->name = CORBA::string_dup(name.c_str());
        ptr->data_format = data_format;
    }
    else
    {
        if(quality != Tango::ATTR_INVALID)
        {
            MultiAttribute *m_attr = d->get_device_attr();

            // Add the attribute setpoint to the value sequence

            if((writable == Tango::READ_WRITE) || (writable == Tango::READ_WITH_WRITE))
            {
                m_attr->add_write_value(*this);
            }

            // check for alarms to position the data quality value.
            if(is_alarmed().any())
            {
                check_alarm();
            }

            ptr->r_dim.dim_x = dim_x;
            ptr->r_dim.dim_y = dim_y;
            if((writable == Tango::READ_WRITE) || (writable == Tango::READ_WITH_WRITE))
            {
                WAttribute &assoc_att = m_attr->get_w_attr_by_ind(get_assoc_ind());
                ptr->w_dim.dim_x = assoc_att.get_w_dim_x();
                ptr->w_dim.dim_y = assoc_att.get_w_dim_y();
            }
            else
            {
                ptr->w_dim.dim_x = 0;
                ptr->w_dim.dim_y = 0;
            }
        }
        else
        {
            ptr->r_dim.dim_x = 0;
            ptr->r_dim.dim_y = 0;
            ptr->w_dim.dim_x = 0;
            ptr->w_dim.dim_y = 0;
            ptr->value.union_no_data(true);
        }

        ptr->time = when;
        ptr->quality = quality;
        ptr->data_format = data_format;
        ptr->name = CORBA::string_dup(name.c_str());
    }
}

template <typename T, typename V>
void Attribute::AttrValUnion_fake_copy(const T *src, V *dst)
{
    switch(src->value._d())
    {
    case ATT_BOOL:
    {
        const DevVarBooleanArray &tmp_seq = src->value.bool_att_value();
        DevVarBooleanArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<bool *>(tmp_seq.get_buffer()), false);
        dst->value.bool_att_value(tmp_seq_4);
    }
    break;

    case ATT_SHORT:
    {
        const DevVarShortArray &tmp_seq = src->value.short_att_value();
        DevVarShortArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<short *>(tmp_seq.get_buffer()), false);
        dst->value.short_att_value(tmp_seq_4);
    }
    break;

    case ATT_LONG:
    {
        const DevVarLongArray &tmp_seq = src->value.long_att_value();
        DevVarLongArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevLong *>(tmp_seq.get_buffer()), false);
        dst->value.long_att_value(tmp_seq_4);
    }
    break;

    case ATT_LONG64:
    {
        const DevVarLong64Array &tmp_seq = src->value.long64_att_value();
        DevVarLong64Array tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevLong64 *>(tmp_seq.get_buffer()), false);
        dst->value.long64_att_value(tmp_seq_4);
    }
    break;

    case ATT_FLOAT:
    {
        const DevVarFloatArray &tmp_seq = src->value.float_att_value();
        DevVarFloatArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<float *>(tmp_seq.get_buffer()), false);
        dst->value.float_att_value(tmp_seq_4);
    }
    break;

    case ATT_DOUBLE:
    {
        const DevVarDoubleArray &tmp_seq = src->value.double_att_value();
        DevVarDoubleArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<double *>(tmp_seq.get_buffer()), false);
        dst->value.double_att_value(tmp_seq_4);
    }
    break;

    case ATT_UCHAR:
    {
        const DevVarCharArray &tmp_seq = src->value.uchar_att_value();
        DevVarCharArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<unsigned char *>(tmp_seq.get_buffer()), false);
        dst->value.uchar_att_value(tmp_seq_4);
    }
    break;

    case ATT_USHORT:
    {
        const DevVarUShortArray &tmp_seq = src->value.ushort_att_value();
        DevVarUShortArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevUShort *>(tmp_seq.get_buffer()), false);
        dst->value.ushort_att_value(tmp_seq_4);
    }
    break;

    case ATT_ULONG:
    {
        const DevVarULongArray &tmp_seq = src->value.ulong_att_value();
        DevVarULongArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevULong *>(tmp_seq.get_buffer()), false);
        dst->value.ulong_att_value(tmp_seq_4);
    }
    break;

    case ATT_ULONG64:
    {
        const DevVarULong64Array &tmp_seq = src->value.ulong64_att_value();
        DevVarULong64Array tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevULong64 *>(tmp_seq.get_buffer()), false);
        dst->value.ulong64_att_value(tmp_seq_4);
    }
    break;

    case ATT_STRING:
    {
        const DevVarStringArray &tmp_seq = src->value.string_att_value();
        DevVarStringArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevString *>(tmp_seq.get_buffer()), false);
        dst->value.string_att_value(tmp_seq_4);
    }
    break;

    case ATT_STATE:
    {
        const DevVarStateArray &tmp_seq = src->value.state_att_value();
        DevVarStateArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevState *>(tmp_seq.get_buffer()), false);
        dst->value.state_att_value(tmp_seq_4);
    }
    break;

    case DEVICE_STATE:
    {
        const DevState &sta = src->value.dev_state_att();
        dst->value.dev_state_att(sta);
    }
    break;

    case ATT_ENCODED:
    {
        const DevVarEncodedArray &tmp_seq = src->value.encoded_att_value();
        DevVarEncodedArray tmp_seq_4(
            tmp_seq.length(), tmp_seq.length(), const_cast<DevEncoded *>(tmp_seq.get_buffer()), false);
        dst->value.encoded_att_value(tmp_seq_4);
    }
    break;

    case ATT_NO_DATA:
        dst->value.union_no_data(true);
        break;
    }
}

template <typename T>
void Attribute::AttrValUnion_2_Any(const T *src, CORBA::Any &dst)
{
    switch(src->value._d())
    {
    case ATT_BOOL:
    {
        const DevVarBooleanArray &tmp_seq = src->value.bool_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_SHORT:
    {
        const DevVarShortArray &tmp_seq = src->value.short_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_LONG:
    {
        const DevVarLongArray &tmp_seq = src->value.long_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_LONG64:
    {
        const DevVarLong64Array &tmp_seq = src->value.long64_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_FLOAT:
    {
        const DevVarFloatArray &tmp_seq = src->value.float_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_DOUBLE:
    {
        const DevVarDoubleArray &tmp_seq = src->value.double_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_UCHAR:
    {
        const DevVarCharArray &tmp_seq = src->value.uchar_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_USHORT:
    {
        const DevVarUShortArray &tmp_seq = src->value.ushort_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_ULONG:
    {
        const DevVarULongArray &tmp_seq = src->value.ulong_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_ULONG64:
    {
        const DevVarULong64Array &tmp_seq = src->value.ulong64_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_STRING:
    {
        const DevVarStringArray &tmp_seq = src->value.string_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_STATE:
    {
        const DevVarStateArray &tmp_seq = src->value.state_att_value();
        dst <<= tmp_seq;
    }
    break;

    case DEVICE_STATE:
    {
        const DevState &sta = src->value.dev_state_att();
        dst <<= sta;
    }
    break;

    case ATT_ENCODED:
    {
        const DevVarEncodedArray &tmp_seq = src->value.encoded_att_value();
        dst <<= tmp_seq;
    }
    break;

    case ATT_NO_DATA:
        break;
    }
}

template <class T>
inline void Attribute::_extract_value(CORBA::Any &dest)
{
    auto *ptr = get_value_storage<T>();
    dest <<= *ptr;
    delete_seq();
}

/// @cond DO_NOT_DOCUMENT

/*
 * method: Attribute::set_value
 *
 * These methods store the attribute value inside the Attribute object, set current time as readout time
 * and calculate attribute quality factor (if warning/alarm levels are defined)
 *
 * The value will be later send to the client either as result of attribute readout of event value,
 * or utilized to define current device State (when reading device State and warning/alarm levels are defined)
 *
 * After readout or in case of failure value will be destroyed.
 *
 * When release is true, we expect p_data to be allocated with operator new for SCALAR attributes and operator new[]
 * otherwise. For the DevString's, when release is true we expect value to have been allocated with Tango::string_dup or
 * equivalent.
 *
 * @param T *p_data: pointer to value
 * @param long x=1: the attribute x dimension
 * @param long y=0: the attribute y dimension
 * @param bool release: The release flag. If true, memory pointed to by p_data will be
 *           freed after being send to the client. Default value is false.
 * @exception DevFailed If the attribute data type is not coherent, enum labels are not defined, wrong size of data, or
 * wrong Enum value.
 *
 */

// Special set_value realisation for scalar C++11 scoped enum with short as underlying data type or old enum
template <class T, std::enable_if_t<std::is_enum_v<T> && !std::is_same_v<T, Tango::DevState>, T> *>
inline void Attribute::set_value(T *enum_ptr, long x, long y, bool release)
{
    TANGO_LOG_DEBUG << "Attribute::set_value() called " << std::endl;

    //
    // Throw exception if attribute data type is not correct
    //

    if(data_type != Tango::DEV_ENUM)
    {
        delete_data_if_needed(enum_ptr, release);

        std::stringstream o;

        o << "Invalid incoming data type " << Tango::DEV_ENUM << " for attribute " << name
          << ". Attribute data type is " << (CmdArgType) data_type << std::ends;

        TANGO_THROW_EXCEPTION(API_AttrOptProp, o.str());
    }

    bool short_enum = std::is_same_v<short, std::underlying_type_t<T>>;
    bool uns_int_enum = std::is_same_v<unsigned int, std::underlying_type_t<T>>;

    if(!short_enum && !uns_int_enum)
    {
        delete_data_if_needed(enum_ptr, release);

        std::stringstream o;
        o << "Invalid enumeration type. Supported types are C++11 scoped enum with short as underlying data type or "
             "old enum."
          << std::endl;

        TANGO_THROW_EXCEPTION(API_IncompatibleArgumentType, o.str());
    }

    //
    // Check if the input type is an enum and if it is from the valid type
    //

    if(std::is_enum_v<T> == false)
    {
        delete_data_if_needed(enum_ptr, release);
        TANGO_THROW_EXCEPTION(API_IncompatibleArgumentType, "The input argument data type is not an enumeration");
    }

    //
    // Check if enum labels are defined
    //

    if(enum_labels.size() == 0)
    {
        delete_data_if_needed(enum_ptr, release);

        std::stringstream o;
        o << "Attribute " << name << " data type is enum but no enum labels are defined!";

        TANGO_THROW_EXCEPTION(API_AttrOptProp, o.str());
    }

    //
    // Check enum type
    //

    DeviceImpl *dev = get_att_device();
    Tango::DeviceClass *dev_class = dev->get_device_class();
    Tango::MultiClassAttribute *mca = dev_class->get_class_attr();
    Tango::Attr &att = mca->get_attr(name);

    if(!att.same_type(typeid(T)))
    {
        delete_data_if_needed(enum_ptr, release);

        std::stringstream o;
        o << "Invalid enumeration type. Requested enum type is " << att.get_enum_type();
        TANGO_THROW_EXCEPTION(API_IncompatibleArgumentType, o.str());
    }

    //
    // Check that data size is less than the given max
    //

    if((x > max_x) || (y > max_y))
    {
        delete_data_if_needed(enum_ptr, release);

        std::stringstream o;

        o << "Data size for attribute " << name << " [" << x << ", " << y << "]"
          << " exceeds given limit [" << max_x << ", " << max_y << "]" << std::ends;

        TANGO_THROW_EXCEPTION(API_AttrOptProp, o.str());
    }

    //
    // Compute data size and set default quality to valid.
    //

    dim_x = x;
    dim_y = y;
    set_data_size();
    quality = Tango::ATTR_VALID;

    //
    // Throw exception if pointer is null and data_size != 0
    //

    if(data_size != 0)
    {
        CHECK_PTR(enum_ptr, name);
    }

    if(data_size > enum_nb)
    {
        if(enum_nb != 0)
        {
            delete[] loc_enum_ptr;
        }
        loc_enum_ptr = new short[data_size];
        enum_nb = data_size;
    }

    short max_val = (short) enum_labels.size() - 1;
    for(std::uint32_t i = 0; i < data_size; i++)
    {
        loc_enum_ptr[i] = (short) enum_ptr[i];
        if(loc_enum_ptr[i] < 0 || loc_enum_ptr[i] > max_val)
        {
            delete_data_if_needed(enum_ptr, release);
            enum_nb = 0;

            std::stringstream o;
            o << "Wrong value for attribute " << name << ". Element " << i << " (value = " << loc_enum_ptr[i]
              << ") is negative or above the limit defined by the enum (" << max_val << ").";

            delete[] loc_enum_ptr;

            TANGO_THROW_EXCEPTION(API_AttrOptProp, o.str());
        }
    }

    delete_data_if_needed(enum_ptr, release);

    if((data_format == Tango::SCALAR) && (release))
    {
        attribute_value.set(std::make_unique<Tango::DevVarShortArray>(data_size, data_size, loc_enum_ptr, false));
    }
    else
    {
        attribute_value.set(std::make_unique<Tango::DevVarShortArray>(data_size, data_size, loc_enum_ptr, release));
    }

    //
    // Reset alarm flags
    //

    alarm.reset();

    //
    // Get time
    //

    set_time();
}

/// @endcond

} // namespace Tango
#endif // TANGO_SERVER_ATTRIBUTE_TEMPL_H
