/*
 * 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 "iotcs_virtual_device.h"
#include <string>
#include <stdexcept>
#include <mutex>
#include <map>
#ifdef __MBED__
#include "mbed.h"
#include "rtos.h"
#define MUTEX_TYPE Mutex
#else
#define MUTEX_TYPE std::mutex
#endif

/**
 * @file Alert.hpp
 * An Alert to be sent to the server. The alert is sent by calling the
 * raise() method. The time of the alert is set when raise()
 * is called, allowing raise() to be called more than once.
 * <p>
 * The set() method returns the Alert instance to allow
 * the fields of the alert to be set in fluent style.
 * @see VirtualDevice::createAlert(const std::string &)
 */
namespace iotdcl {
    class Alert {
    public:
        /**
         * @brief Destructor
          */
        virtual ~Alert();
        /**
         * @brief Set the value of a field in the {@code Alert}. The fields are
          * determined by the format given when the Alert is created.
          * The value is validated according to the constraints in the format.
          * If the value is not valid, an IllegalArgumentException is raised.
          * <p>All fields defined in the format that are {@code "optional" : true}
          * must be set before calling raise().</p>
          * @param <T> the type of the value.
          * @param attributeName the name of a field from the alert format
          * @param value the value to set
          * @return this Alert instance
          * @see VirtualDevice::createAlert(String)
          * @throw std::invalid_argument if the value is not valid,
          * or attributeName is NULL
          */
        template <typename T> Alert& set(const std::string &attributeName, const T& value) throw(std::invalid_argument);
        /**
         * @brief Send the alert. The event time is set when this method is called.
          * <p>
          * The onError handler of the parent virtual device will be called
          * if there is error sending the alert ***exception is thrown.
          * <p>All fields defined in the format that are {@code "optional" : true}
         * must be set before calling raise(). If raise() is called
         * before setting all {@code "optional" : true} fields, an std::invalid_argument.</p>
         * @throw std::invalid_argument
         */
        void raise(void) throw(std::invalid_argument);

        /**
         * Set a callback that is invoked if an error occurs when
         * raising an {@code Alert}.
         *
         * @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(ErrorCallback *callback);
    protected:
        Alert(VirtualDevice* device, iotcs_virtual_device_handle handler, const std::string &alertName) throw (std::invalid_argument);
        static void onErrorCallback(void* handler, iotcs_virtual_device_error_event *event);
    private:
        Alert();
        iotcs_alert_handle handle;
        VirtualDevice* device;
        static MUTEX_TYPE mapMutex;
        static std::map<void*, std::pair<VirtualDevice*, ErrorCallback*> > onErrorCallbacks;
    };
};
