/*
 * Copyright (c) 2017, 2018, 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.
 */

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <ctime>
#include <utility>
#include <mutex>
#include <stdlib.h> // for free())
/* include methods for device client*/
#include "iotcs_virtual_device.h"
#include "device_model/device_model_private_ext.h"
#include <AbstractVirtualDevice.hpp>
#include <VirtualDevice.hpp>
#include <Alert.hpp>
#include <Data.hpp>
#include <DeviceModel.hpp>
#include "iotcs_device.h"
#include <iotcs/VirtualDeviceImpl.hpp>
#include <device_model/ChangeEventImpl.hpp>
#include <device_model/ErrorEventImpl.hpp>
#include <StorageObject.hpp>
#include <scs/StorageObjectImpl.hpp>
#include <util/CPPUtils.hpp>
#ifdef __MBED__
#include "mbed.h"
#endif

using namespace std;

namespace iotdcl {
    class ChangeCallback;
    class ErrorCallback;
    class Callable;

    MUTEX_TYPE VirtualDeviceImpl::mapMutex;
    map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ChangeCallback*> > VirtualDeviceImpl::globalOnChangeCallbacks;
    map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ErrorCallback*> > VirtualDeviceImpl::globalOnErrorCallbacks;
    map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ChangeCallback*> > > VirtualDeviceImpl::attributeOnChangeCallbacks;
    map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ErrorCallback*> > > VirtualDeviceImpl::attributeOnErrorCallbacks;
    map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const Callable*> > > VirtualDeviceImpl::onActionCallbacks;

    VirtualDeviceImpl::VirtualDeviceImpl(const string &endpointId, DeviceModelImpl &dm) : deviceModel(dm){
        this->endpointId = string(endpointId);
        iotcs_result rv = iotcs_create_virtual_device_handle(this->endpointId.c_str(), dm.handle, &this->handle);
        checkResult(rv);
    }

    VirtualDeviceImpl::~VirtualDeviceImpl() {
        mapMutex.lock();
        {
            map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ChangeCallback*> >::iterator it = globalOnChangeCallbacks.find(handle);
            if (it != globalOnChangeCallbacks.end() && it->second.first == this) {
                globalOnChangeCallbacks.erase(it);
            }
        }
        {
            map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ErrorCallback*> >::iterator it = globalOnErrorCallbacks.find(handle);
            if (it != globalOnErrorCallbacks.end() && it->second.first == this) {
                globalOnErrorCallbacks.erase(it);
            }
        }
        {
            map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ChangeCallback*> > >::iterator it = attributeOnChangeCallbacks.find(handle);
            if (it != attributeOnChangeCallbacks.end() && it->second.first == this) {
                attributeOnChangeCallbacks.erase(it);
            }
        }
        {
            map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ErrorCallback*> > >::iterator it = attributeOnErrorCallbacks.find(handle);
            if (it != attributeOnErrorCallbacks.end() && it->second.first == this) {
                attributeOnErrorCallbacks.erase(it);
            }
        }
        {
            map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const Callable*> > >::iterator it = onActionCallbacks.find(handle);
            if (it != onActionCallbacks.end() && it->second.first == this) {
                onActionCallbacks.erase(it);
            }
        }
        iotcs_free_virtual_device_handle(handle);
        mapMutex.unlock();
        std::for_each(ah_list.begin(), ah_list.end(), delete_pointer_element<Alert*>());
        std::for_each(dh_list.begin(), dh_list.end(), delete_pointer_element<Data*>());
        ah_list.clear();
        dh_list.clear();
    }

    DeviceModel& VirtualDeviceImpl::getDeviceModel() {
        return deviceModel;
    }

    const string& VirtualDeviceImpl::getEndpointId() const {
        return endpointId;
    }

    Alert& VirtualDeviceImpl::createAlert(const string &format) {
        Alert* result = AlertImpl::createAlert(this, this->handle, format);
        ah_list.push_back(result);
        return *result;
    }

    Data& VirtualDeviceImpl::createData(const string &format) {
        Data* result = DataImpl::createData(this, this->handle, format);
        dh_list.push_back(result);
        return *result;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const int& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_integer(handle, attributeName.c_str(), value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const float& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_float(handle, attributeName.c_str(), value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const double& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_float(handle, attributeName.c_str(), (float)value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const string &value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_string(handle, attributeName.c_str(), value.c_str());
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const char * const &value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_string(handle, attributeName.c_str(), value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const bool& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_boolean(handle, attributeName.c_str(), value ? IOTCS_TRUE : IOTCS_FALSE);
        checkResult(rv);
        return *this;
    }
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    template <>
    VirtualDevice& VirtualDevice::offer(const string &attributeName, const int& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_offer_integer(handle, attributeName.c_str(), value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::offer(const string &attributeName, const float& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_offer_float(handle, attributeName.c_str(), value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::offer(const string &attributeName, const double& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_offer_float(handle, attributeName.c_str(), (float)value);
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::offer(const string &attributeName, const bool& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_offer_boolean(handle, attributeName.c_str(), value ? IOTCS_TRUE : IOTCS_FALSE);
        checkResult(rv);
        return *this;
    }
#endif
    VirtualDevice& VirtualDevice::set(const string &attributeName, StorageObject* value) throw(std::invalid_argument) {
        iotcs_result rv = IOTCS_RESULT_INVALID_ARGUMENT;
        StorageObjectImpl::mapMutex.lock();
        std::map<iotcs_storage_object_handle, StorageObjectImpl*>::const_iterator it = StorageObjectImpl::globalSO.find(value->c_handler());
        if (it != StorageObjectImpl::globalSO.end()) {
            it->second->setVirtualDevice(this);
        }
        StorageObjectImpl::mapMutex.unlock();
        rv = iotcs_virtual_device_set_storage_object(handle, attributeName.c_str(), value->c_handler());
        checkResult(rv);
        return *this;
    }

    template <>
    VirtualDevice& VirtualDevice::set(const string &attributeName, const time_t& value) throw(std::invalid_argument) {
        iotcs_result rv = iotcs_virtual_device_set_date_time(handle, attributeName.c_str(), static_cast<iotcs_date_time> (value));
        checkResult(rv);
        return *this;
    }

    template <>
    int VirtualDevice::get(const string &attributeName) {
        int value;
        iotcs_result rv = iotcs_virtual_device_get_integer(handle, attributeName.c_str(), &value);
        checkResult(rv);
        return value;
    }

    template <>
    float VirtualDevice::get(const string &attributeName) {
        float value;
        iotcs_result rv = iotcs_virtual_device_get_float(handle, attributeName.c_str(), &value);
        checkResult(rv);
        return value;
    }

    template <>
    string VirtualDevice::get(const string &attributeName) {
        const char *value;
        iotcs_result rv = iotcs_virtual_device_get_string(handle, attributeName.c_str(), &value);
        checkResult(rv);
        string str(value);
        free((void*) value);
        return str;
    }

    template <>
    bool VirtualDevice::get(const string &attributeName) {
        iotcs_bool value;
        iotcs_result rv = iotcs_virtual_device_get_boolean(handle, attributeName.c_str(), &value);
        checkResult(rv);
        return (bool)value;
    }

    template <>
    time_t VirtualDevice::get(const string &attributeName) {
        iotcs_date_time value;
        iotcs_result rv = iotcs_virtual_device_get_date_time(handle, attributeName.c_str(), &value);
        checkResult(rv);
        time_t time = value;
        return time;
    }

    template <>
    StorageObject& VirtualDevice::get(const string &attributeName) {
        iotcs_storage_object_handle value = NULL;
        iotcs_virtual_device_get_storage_object(handle, attributeName.c_str(), &value);
        StorageObjectImpl *ptr = new StorageObjectImpl(value);
        eo_list.push_back(ptr);
        return *ptr;
    }

    VirtualDevice::~VirtualDevice() {
        std::for_each(eo_list.begin(), eo_list.end(), delete_pointer_element<StorageObject*>());
        eo_list.clear();
    }

    VirtualDevice& VirtualDeviceImpl::update(void) {
        iotcs_virtual_device_start_update(handle);
        return *this;
    }

    void VirtualDeviceImpl::finish(void) {
        iotcs_virtual_device_finish_update(handle);
    }

    void VirtualDeviceImpl::setOnChange(const ChangeCallback *callback) {
        mapMutex.lock();
        pair<VirtualDeviceImpl*, const ChangeCallback*> rv = globalOnChangeCallbacks[handle];
        globalOnChangeCallbacks[handle] = make_pair(this, callback);
        iotcs_virtual_device_set_on_change(handle, VirtualDeviceImpl::globalOnChangeCallback);
        mapMutex.unlock();

    }

    void VirtualDeviceImpl::setOnError(const ErrorCallback *callback) {
        mapMutex.lock();
        pair<VirtualDeviceImpl*, const ErrorCallback*> rv = globalOnErrorCallbacks[handle];
        globalOnErrorCallbacks[handle] = make_pair(this, callback);
        iotcs_virtual_device_set_on_error(handle, VirtualDeviceImpl::globalOnErrorCallback);
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::setOnChange(const string &attributeName, const ChangeCallback *callback) {
        mapMutex.lock();
        pair<VirtualDeviceImpl*, map<string, const ChangeCallback*> > mapping = attributeOnChangeCallbacks[handle];
        mapping.first = this;
        const ChangeCallback* rv = mapping.second[attributeName];
        mapping.second[attributeName] = callback;
        attributeOnChangeCallbacks[handle] = mapping;
        iotcs_virtual_device_attribute_set_on_change(handle, attributeName.c_str(), VirtualDeviceImpl::attributeOnChangeCallback);
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::setOnError(const string &attributeName, const ErrorCallback *callback) {
        mapMutex.lock();
        pair<VirtualDeviceImpl*, map<string, const ErrorCallback*> > mapping = attributeOnErrorCallbacks[handle];
        mapping.first = this;
        const ErrorCallback* rv = mapping.second[attributeName];
        mapping.second[attributeName] = callback;
        attributeOnErrorCallbacks[handle] = mapping;
        iotcs_virtual_device_attribute_set_on_error(handle, attributeName.c_str(), VirtualDeviceImpl::attributeOnErrorCallback);
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::setCallable(const string &actionName, const Callable *callback) {
        mapMutex.lock();
        pair<VirtualDeviceImpl*, map<string, const Callable*> > mapping = onActionCallbacks[handle];
        mapping.first = this;
        const Callable* rv = mapping.second[actionName];
        mapping.second[actionName] = callback;
        onActionCallbacks[handle] = mapping;
        iotcs_virtual_device_set_callback_cpp(handle, actionName.c_str(), VirtualDeviceImpl::onActionCallback);
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::globalOnChangeCallback(iotcs_virtual_device_change_event *event) {
        mapMutex.lock();
        map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ChangeCallback*> >::const_iterator it = globalOnChangeCallbacks.find(event->virtual_device_handle);
        if (it != globalOnChangeCallbacks.end()) {
            NamedValue value(event->named_value);
            try {
                ChangeEventImpl evt(it->second.first, &value);
                if (it->second.second) {
                    it->second.second->onChange(evt);
                }
            } catch (...) {
                // ignored
            }
        }
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::globalOnErrorCallback(iotcs_virtual_device_error_event *event) {
        mapMutex.lock();
        map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, const ErrorCallback*> >::const_iterator it = globalOnErrorCallbacks.find(event->virtual_device_handle);
        if (it != globalOnErrorCallbacks.end()) {
            NamedValue value(event->named_value);
            try {
                string message = string(event->message);
                ErrorEventImpl evt(it->second.first, &value, &message);
                if (it->second.second) {
                    it->second.second->onError(evt);
                }
            } catch (...) {
                // ignored
            }
        }
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::attributeOnChangeCallback(iotcs_virtual_device_change_event *event) {
        mapMutex.lock();
        map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ChangeCallback*> > >::const_iterator it = attributeOnChangeCallbacks.find(event->virtual_device_handle);
        if (it != attributeOnChangeCallbacks.end()) {
            string name(event->named_value.name);
            map<string, const ChangeCallback*>::const_iterator attr = it->second.second.find(name);
            if (attr != it->second.second.end()) {
                NamedValue value(event->named_value);
                try {
                    ChangeEventImpl evt(it->second.first, &value);
                    if (attr->second) {
                        attr->second->onChange(evt);
                    }
                } catch (...) {
                    // ignored
                }
            }
        }
        mapMutex.unlock();
    }

    void VirtualDeviceImpl::attributeOnErrorCallback(iotcs_virtual_device_error_event *event) {
        mapMutex.lock();
        map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const ErrorCallback*> > >::const_iterator it = attributeOnErrorCallbacks.find(event->virtual_device_handle);
        if (it != attributeOnErrorCallbacks.end()) {
            string name(event->named_value.name);
            map<string, const ErrorCallback*>::const_iterator attr = it->second.second.find(name);
            if (attr != it->second.second.end()) {
                NamedValue value(event->named_value);
                try {
                    string message = string(event->message);
                    ErrorEventImpl evt(it->second.first, &value, &message);
                    if (attr->second) {
                        attr->second->onError(evt);
                    }
                } catch (...) {
                    // ignored
                }
            }
        }
        mapMutex.unlock();
    }
static const char * VALUE = "value";

    void VirtualDeviceImpl::onActionCallback(iotcs_virtual_device_handle virtual_device_handle, char* action_name, iotcs_typed_value argument) {
        mapMutex.lock();
        map<iotcs_virtual_device_handle, pair<VirtualDeviceImpl*, map<string, const Callable*> > >::const_iterator it = onActionCallbacks.find(virtual_device_handle);
        if (it != onActionCallbacks.end()) {
            string name(action_name);
            map<string, const Callable*>::const_iterator attr = it->second.second.find(name);
            if (attr != it->second.second.end()) {
                iotcs_named_value tvalue;
                tvalue.name = VALUE;
                tvalue.typed_value = argument;
                tvalue.next = NULL;
                const NamedValue value(tvalue);
                try {
                    if (attr->second) {
                        attr->second->call(*it->second.first, value);
                    }
                } catch (...) {
                    // ignored
                }
            }
        }
        mapMutex.unlock();
    }
};
