/*
 * 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 <initializer_list>
#include <vector>
#include <stdexcept>
#include <DeviceModel.hpp>
#include <VirtualDevice.hpp>
#include <StorageObject.hpp>
#include <Exception.hpp>

/**
 * @file DirectlyConnectedDevice.hpp
 * @brief A directly-connected device is able to send messages to, and receive messages
 * from, the IoT server. The directly-connected device is assigned an
 * <em>activation identifier</em> when the device is registered on the server.
 * When the directly-connected device is activated, the server assigns an
 * <em>endpoint identifer</em> that is used by the <code>DirectlyConnectedDevice</code>
 * for sending messages to, and receiving messages from, the server.
 */
namespace iotdcl {
    class DirectlyConnectedDevice {
    public:

        /**
         * @brief Constructs a new DirectlyConnectedDevice instance that will load
         * the device configuration from the given file path and password.
         * @param filePath the path of the configuration file
         * @param password the configuration file password
         * @throw std::invalid_argument if \a filePath or password is NULL.
         * @throw GeneralException otherwise.
         */
        DirectlyConnectedDevice(const std::string &filePath, const std::string &password) throw (GeneralException, std::invalid_argument);
        
        /**
         * @brief Destructor
         */
        virtual ~DirectlyConnectedDevice();

        /**
         * @brief Return the endpoint identifier of this directly-connected
         * device. The endpoint identifier is assigned by the server
         * as part of the activation process.
         * @return the endpoint identifier of this directly-connected
         * device.
         * @see activate()
         */
        const std::string& getEndpointId() const;

        /**
         * Returns whether the device has been activated.
         *
         * @return whether the device has been activated.
         */
        bool isActivated();

        /**
         * @bief Activate the device. The device will be activated on the server if
         * necessary. When the device is activated on the server, the server
         * assigns an endpoint identifier to the device.
         * 
         * The user should call the isActivated()
         * method prior to calling activate.
         * @param deviceModelUrls should contain the device model type URNs of this directly connected device.
         *                The device is activated with the given device models.
         * @see isActivated()
         * @throw std::invalid_argument if \a device_models is NULL.
         * @throw CanNotAuthorizeException if there are problems with authorization.
         * @throw GeneralException otherwise.
         */
        void activate(const std::initializer_list<std::string> &deviceModelUrls) throw (GeneralException, CanNotAuthorizeException, std::invalid_argument);

        /**
         * @brief Get the DeviceModel for the device model urn.
         * @param deviceModelUrl the URN of the device model
         * @return A representation of the device model
         * @throw std::invalid_argument if \a device_models is NULL.
         * @throw CanNotAuthorizeException if there are problems with authorization.
         * @throw GeneralException otherwise.
         * @throw std::invalid_argument if \a device_models is NULL.
         * @throw CanNotAuthorizeException if there are problems with authorization.
         * @throw GeneralException otherwise.
         */
        DeviceModel& getDeviceModel(const std::string &deviceModelUrl) throw (GeneralException, CanNotAuthorizeException, std::invalid_argument);

        /**
         * @brief Create a new iotdcl::StorageObject with the given object name and mime&ndash;type.
         * If contentType is NULL, the mime&ndash;type defaults to "application/octet&ndash;stream".
         * @param name the unique name to be used to reference the content in storage
         * @param contentType The mime-type of the content
         * @return a StorageObject
         * @throw iotdcl::GeneralException if there is an error raised by the runtime,
         * or an abnormal response from the storage cloud
         * @throw iotdcl::CanNotAuthorizeException if there is an exception establishing a secure connection to the storage cloud
         */
        StorageObject& createStorageObject(const std::string &name, const std::string &contentType) throw (GeneralException, CanNotAuthorizeException);

        /**
         * @brief Create a new iotdcl::StorageObject from the URI for a named object in storage.
         * @param uri the URI of the object in the storage cloud
         * @return a reference to StorageObject for the object in storage
         * @throw iotdcl::GeneralException if there is an error raised by the runtime,
         * or an abnormal response from the storage cloud
         * @throw iotdcl::CanNotAuthorizeException if there is an exception establishing a secure connection to the storage cloud
         */
        StorageObject& createStorageObject(const std::string &uri) throw (GeneralException, CanNotAuthorizeException);

        /**
         * @brief Create a iotcs::AbstractVirtualDevice instance with the given device model
         * for the given device identifier. This method creates a new {@code VirtualDevice}
         * instance for the given parameters. The client library does not
         * cache previously created VirtualDevice objects.
         * @param endpointId The device identifier of the device being modeled
         * @param model The device model URN that this device implements
         * @return a new AbstractVirtualDevice
         * @throw std::invalid_argument if \a device_models is NULL or endpointId is invalid.
         * @throw CanNotAuthorizeException if there are problems with authorization.
         * @throw GeneralException otherwise.
         */
        VirtualDevice& createVirtualDevice(const std::string &endpointId, DeviceModel &model) throw (GeneralException, CanNotAuthorizeException, std::invalid_argument);
    private:
        std::string endpointId;
        std::vector<DeviceModel*> dm_list;
        std::vector<VirtualDevice*> vd_list;
        std::vector<StorageObject*> eo_list;
        DirectlyConnectedDevice(const DirectlyConnectedDevice&); // no implementation
        DirectlyConnectedDevice& operator=(const DirectlyConnectedDevice&); // no implementation
    };
};
