/*
 * 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 <AbstractVirtualDevice.hpp>
#include <Alert.hpp>
#include <Data.hpp>
#include <string>
#include "iotcs_virtual_device.h"
#include "iotcs_config.h"
#include <NamedValue.hpp>
#include <vector>
#include <stdexcept>

/**
 * @file VirtualDevice.hpp
 * @brief VirtualDevice for a device-client adds methods to handle write-only and
 * executable actions.
 */
namespace iotdcl {
    class Callable;

    class VirtualDevice: public AbstractVirtualDevice<VirtualDevice> {
        public:
            virtual ~VirtualDevice();
            /**
             * @brief Set the value of an attribute.
             * This method is used by the application
             * to synchronize the #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
			 * @throw std::invalid_argument if the value is not valid,
			 * or attributeName is NULL
             */
            template <typename T> VirtualDevice& set(const std::string &attributeName, const T& value) throw(std::invalid_argument);

#if defined IOTCS_IMPLICIT_EDGE_COMPUTING || defined IOTCS_DOXYGEN
            /**
             * @brief Offer to set the value of an attribute.
			 * The attribute value is set depending upon any policy that may have been
             * configured for the attribute. If there is no policy for the given attribute, offer
			 * behaves as if the VirtualDevice::set() method were called.
			 * 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
			 * @throw std::invalid_argument if the value is not valid,
			 * or attributeName is NULL
             */
            template <typename T> VirtualDevice& offer(const std::string &attributeName, const T& value) throw(std::invalid_argument);
#endif
            /**
             * @brief Set the value of an StorageObject attribute.
             * This method is used by the application
             * to synchronize the #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 StorageObject value to set
             * @return this iotdcl::AbstractVirtualDevice instance
			 * @throw std::invalid_argument if the value is not valid,
			 * or attributeName is NULL
             */
            VirtualDevice& set(const std::string &attributeName, StorageObject* value) throw(std::invalid_argument);

            /**
             * @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);
            /**
             * @brief Create an iotdcl::Alert for this VirtualDevice. The alert will be created with
             * the given format URN.
             * @param format the alert format URN.
             * @return an iotdcl::Alert
             */
            virtual Alert& createAlert(const std::string &format) = 0;
            /**
             * @brief Create a custom iotdcl::Data object for this VirtualDevice.
             * The custom iotdcl::Data object will be created with the given format URN.
             * @param format the custom data message format URN.
             * @return a custom iotdcl::Data object
             */
            virtual Data& createData(const std::string &format) = 0;
            /**
             * @brief Set callback for handling an action
             * @param actionName The action name
             * @param callable The interface to invoke
             */
            virtual void setCallable(const std::string &actionName, const Callable *callback) = 0;
        protected:
            iotcs_virtual_device_handle handle;
            std::vector<StorageObject*> eo_list;
    };
    /**
     * @brief Callback interface for actions in the device model
     */
    class Callable {
    public:
        /**
         * @brief The method called for handling a device model action.
         * For an execute action, the generic type should be {@code Void}.
         * The client library will pass NULL to the call() method
         * if the action is executable. For a write-only action, the generic
         * type should match the expected data type of the action.
         * @param virtualDevice the VirtualDevice on which the action is
         *                      being invoked
         * @param data the data
         */
        virtual void call( VirtualDevice &virtualDevice, const NamedValue &data) const = 0;
        /**
         * @brief Desctructor
         */
        virtual ~Callable() {
        }
    };
};
