/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#pragma once

#include <string>
#include <DeviceModel.hpp>
#include <NamedValue.hpp>

/**
 * @file    AbstractVirtualDevice.hpp
 */
namespace iotdcl {
    class VirtualDevice;

    /**
     * @brief An event passed to a callback.
     * @param <V> the type of iotdcl::AbstractVirtualDevice
     */
    template <class V> class Event {
    public:
        /**
         * @brief Destructor
         */
        virtual ~Event() {
        }

        /**
         * @brief Get the virtual device that is the source of the event
         * @return the virtual device, never NULL
         */
        virtual const V& getVirtualDevice() const = 0;

        /**
         * @brief Get the name-value pair from the event.
         * @return the value, never {@code null}
         * @see iotdcl::ChangeCallback
         * @see iotdcl::ErrorCallback
         */
        virtual NamedValue* getNamedValue() const = 0;
    protected:
        Event() {
        }
    };

    /**
     * An event passed to the onChange callback.
     * @see iotdcl::ChangeCallback
     * @see iotdcl::Event
     */
    template <class V> class ChangeEvent: public Event<V> {
    public:
        /**
         * @brief Destructor
         */
        virtual ~ChangeEvent() {
        }
    };

    /**
     * A callback interface for receiving an event when the value
     * of an attribute has changed.
     * @see iotdcl::AbstractVirtualDevice::setOnChange((const ChangeCallback *))
     */
    class ChangeCallback {
    public:
        /**
         * Callback for receiving an event when the value of an attribute
         * has changed. This callback will never be invoked by the
         * client-library with a null argument.
         * @param event An event indicating the attribute for which the value has changed
         */
        virtual void onChange(const ChangeEvent<VirtualDevice>& event) const = 0;
    };

    /**
     * An event passed to the {@code onError} callback to indicate an
     * error has occurred when setting one or more attributes.
     * @param <V> the type of iotdcl::AbstractVirtualDevice
     * @see iotdcl::ErrorCallback
     * @see iotdcl::Event
     */
    template <class V> class ErrorEvent: public Event<V> {
    public:
        /**
         * @brief Get the error message.
         * @return An error message
         */
        virtual const std::string getMessage() const = 0;
        virtual ~ErrorEvent() {
        }
    };

    /**
     * @brief A callback interface for errors in delivery of messages.
     */
    class ErrorCallback {
    public:
        /**
         * @brief Notify that an error occured when attempting to deliver messages
         * to the server. This callback indicates that the messages have
         * not been delivered to the server and are no longer queued.
         * @param event the messages that were not delivered
         * @see iotcs::ErrorEvent
         */
        virtual void onError(const ErrorEvent<VirtualDevice>& event) const = 0;
    };
    /**
     * @brief AbstractVirtualDevice is a representation of a device model implemented by an endpoint.
     * A device model is a specification of the attributes, formats, and resources
     * available on the endpoint.
     * <p>
     *     The iotdcl::AbstractVirtualDevice API is common to both the enterprise client and the
     *     device client. The semantics of the API are also the same. The processing
     *     model on the enterprise client is different, however, from the processing
     *     model on the device client.
     * <p>
     *     On the enterprise client, an enterprise application calls the
     *     iotdcl::AbstractVirtualDevice set methods in order to affect a change on the physical
     *     device. The enterprise client application receives confirmation of
     *     that the change was accepted via a
     *     iotdcl::ChangeCallback.
     *     The iotdcl::ChangeCallback indicates to the enterprise client that
     *     the value of the attribute on the device has changed. This callback may
     *     come as a result of the enterprise client calling set() on the
     *     attribute, or it may come unsolicited as a result of the device-client
     *     updating the value from the physical device.
     * <p>
     *     The enterprise client may also receive an
     *     iotdcl::ErrorCallback
     *     if the attribute value change was not accepted. This could be because the
     *     request timed out, the connection failed, or the change was explicitly
     *     rejected. The iotdcl::ErrorCallback indicates to the enterprise client
     *     that it should roll-back the attribute value to the last known value.
     * <p>
     *     On the device client, the application will call set() on a
     *     iotdcl::AbstractVirtualDevice attribute. This will cause a message to be sent to the
     *     server.  The iotdcl::ErrorCallback is invoked if there is an
     *     error sending the message to the server.
     *     Any enterprise application that is monitoring the device
     *     will be notified via the iotdcl::ChangeCallback that the value has changed.
     *     When the client-library receives a request from the server, the
     *     request is forwarded to a handler which invokes the
     *     iotdcl::ChangeCallback on the requested attribute. 
     *     A GET request simply calls get() on the attribute.
     */
    template<class V> class AbstractVirtualDevice {
    public:

        /**
         * @brief Destructor
         */
        virtual ~AbstractVirtualDevice() {
        }

        /**
         * @brief Get the device model of this device object
         * @return the device model
         */
        virtual const DeviceModel& getDeviceModel() = 0;

        /**
         * @brief Get the endpoint id of the device
         * @return the device endpoint id
         */
        virtual const std::string& getEndpointId() const = 0;

        /**
         * @brief Set the value of an attribute.
         * This method is used by the application
         * to synchronize the iotdcl::AbstractVirtualDevice state with the physical device.
         * This results in a REST API call to the server. For an enterprise
         * client, an endpoint resource REST API call is made to the server. For
         * a device client, an endpoint DATA message REST API call is made to the
         * server.
         * The value is validated according to the constraints in the device model.
         *
         * @param attributeName the name of an attribute from the device type model
         * @param value the value to set
         * @return this iotdcl::AbstractVirtualDevice instance
         */
        template <typename T> V& set(const std::string &attributeName, T& value) {
            V *this_ptr(dynamic_cast<V*>(this));
            if (this_ptr != nullptr) {
                this_ptr->template set<T>(attributeName, value);
            }
            return *this_ptr;
        }

        /**
         * @brief Get the value of an attribute.
         * This method returns the current value
         * held by the virtual device model. For an enterprise client, no
         * REST API call is made to the server. For a device client, no call is
         * made to the physical device.
         * @param attributeName the name of an attribute from the device type model
         * @return the attribute value
         */
        template <typename T> T get(const std::string &attributeName) {
            V *this_ptr(dynamic_cast<V*>(this));
            if (this_ptr != nullptr) {
                return this_ptr->template get<T>(attributeName);
            }
            return 0;
        }

        /**
         * @brief Set a mark for beginning an update transaction. By default, each call to
         * &quot;set&quot; automatically sends the value
         * to the server. The update() call allows more than one
         * value to be set on this iotdcl::AbstractVirtualDevice object without sending values
         * to the server. The values are sent to the server when the
         * finish() method is called, which also marks the end of the
         * update transaction.
         * For example<br>
         * {@code
         * virtualDevice.update().set("min", 10).set("max", 20).finish();
         * }
         * @return this iotdcl::AbstractVirtualDevice instance
         * @see finish()
         */
        virtual V& update(void) = 0;

        /**
         * @brief Causes the values set in an update transaction to be sent to the
         * server and clears the update-transaction mark.
         * @see update()
         */
        virtual void finish(void) = 0;

        /**
         * Set a callback that is invoked when the value of any attribute in the
         * device model  is changed from the outside.
         * Local attribute changes don't provoke the callback invocation.
         * <p>
         * Note that it is possible to set a callback for all attributes and to set
         * a callback for a specific attribute via setOnChange(const std::string&,const ChangeCallback*).
         * If there is a callback for the specific attribute and for all attributes,
         * both callbacks will be invoked.
         * @param callback a callback to invoke when an attribute value changes,
         * if NULL, the existing callback will be removed
         * @see AbstractVirtualDevice.setOnChange(const std::string,const ChangeCallback*)
         */
        virtual void setOnChange(const ChangeCallback *callback) = 0;

        /**
         * Set a callback that is invoked if an error occurs when setting the
         * value of any attribute in the device model, or an error occurs when
         * raising an {@code Alert}.
         * <p>
         * Note that it is possible to set a callback for all attributes and to set
         * a callback for a specific attribute via setOnError(const std::string &, const ErrorCallback*).
         * If there is a callback for the specific attribute and for all attributes,
         * both callbacks will be invoked.
         * @param callback a callback to invoke when there is an error setting a value,
         * if NULL, the existing callback will be removed
         * @see AbstractVirtualDevice.setOnError(const std::string &, const ErrorCallback*)
         */
        virtual void setOnError(const ErrorCallback *callback) = 0;
        /**
         * Set a callback that is invoked when the value of the given attribute in the
         * device model is changed from the outside.
         * Local attribute changes don't provoke the callback invocation.
         * This is a convenience for calling
         * {@code
         * DeviceField deviceField = deviceModel.getAttribute("temperature");
         * deviceField.setOnChange(temperatureChangeCallback);
         * }
         * <p>
         * Note that it is possible to set a callback for a specific attribute and
         * to set a callback for all attributes via setOnChange(const ChangeCallback *).
         * If there is a callback for the specific attribute and for all attributes,
         * both callbacks will be invoked.
         * @param attributeName the name of an attribute from the device type model
         * @param callback a callback to invoke when an attribute value changes,
         * if NULL, the existing callback will be removed
         */
        virtual void setOnChange(const std::string &attributeName, const ChangeCallback *callback) = 0;

        /**
         * Set a callback that is invoked if an error occurs when setting the
         * value of any attribute in the device model or an error occurs when
         * raising an {@code Alert}.
         * This is a convenience for calling
         * {@code
         * DeviceField deviceField = deviceModel.getAttribute("temperature");
         * deviceField.setOnError(temperatureErrorCallback);
         * }
         * <p>
         * Note that it is possible to set a callback for a specific attribute and
         * to set a callback for all attributes via setOnError(const ErrorCallback*).
         * If there is a callback for the specific attribute and for all attributes,
         * both callbacks will be invoked.
         * @param attributeName the name of an attribute from the device type model
         * @param callback a callback to invoke when there is an error setting the
         *                 attribute value, if NULL, the existing callback will be removed
         */
        virtual void setOnError(const std::string &attributeName, const ErrorCallback *callback) = 0;
    };
};
