/*
 * Copyright © 2016-2017 The Thingsboard Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import thingsboardTypes from '../common/types.constant';

export default angular.module('thingsboard.api.device', [thingsboardTypes])
    .factory('deviceService', DeviceService)
    .name;

/*@ngInject*/
function DeviceService($http, $q, $filter, userService, customerService, telemetryWebsocketService, types) {


    var deviceAttributesSubscriptionMap = {};

    var service = {
        assignDeviceToCustomer: assignDeviceToCustomer,
        deleteDevice: deleteDevice,
        getCustomerDevices: getCustomerDevices,
        getDevice: getDevice,
        getDevices: getDevices,
        processDeviceAliases: processDeviceAliases,
        checkDeviceAlias: checkDeviceAlias,
        fetchAliasDeviceByNameFilter: fetchAliasDeviceByNameFilter,
        getDeviceCredentials: getDeviceCredentials,
        getDeviceKeys: getDeviceKeys,
        getDeviceTimeseriesValues: getDeviceTimeseriesValues,
        getTenantDevices: getTenantDevices,
        saveDevice: saveDevice,
        saveDeviceCredentials: saveDeviceCredentials,
        unassignDeviceFromCustomer: unassignDeviceFromCustomer,
        makeDevicePublic: makeDevicePublic,
        getDeviceAttributes: getDeviceAttributes,
        subscribeForDeviceAttributes: subscribeForDeviceAttributes,
        unsubscribeForDeviceAttributes: unsubscribeForDeviceAttributes,
        saveDeviceAttributes: saveDeviceAttributes,
        deleteDeviceAttributes: deleteDeviceAttributes,
        sendOneWayRpcCommand: sendOneWayRpcCommand,
        sendTwoWayRpcCommand: sendTwoWayRpcCommand
    }

    return service;

    function getTenantDevices(pageLink, applyCustomersInfo, config) {
        var deferred = $q.defer();
        var url = '/api/tenant/devices?limit=' + pageLink.limit;
        if (angular.isDefined(pageLink.textSearch)) {
            url += '&textSearch=' + pageLink.textSearch;
        }
        if (angular.isDefined(pageLink.idOffset)) {
            url += '&idOffset=' + pageLink.idOffset;
        }
        if (angular.isDefined(pageLink.textOffset)) {
            url += '&textOffset=' + pageLink.textOffset;
        }
        $http.get(url, config).then(function success(response) {
            if (applyCustomersInfo) {
                customerService.applyAssignedCustomersInfo(response.data.data).then(
                    function success(data) {
                        response.data.data = data;
                        deferred.resolve(response.data);
                    },
                    function fail() {
                        deferred.reject();
                    }
                );
            } else {
                deferred.resolve(response.data);
            }
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
        var deferred = $q.defer();
        var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
        if (angular.isDefined(pageLink.textSearch)) {
            url += '&textSearch=' + pageLink.textSearch;
        }
        if (angular.isDefined(pageLink.idOffset)) {
            url += '&idOffset=' + pageLink.idOffset;
        }
        if (angular.isDefined(pageLink.textOffset)) {
            url += '&textOffset=' + pageLink.textOffset;
        }
        $http.get(url, config).then(function success(response) {
            if (applyCustomersInfo) {
                customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
                    function success(data) {
                        response.data.data = data;
                        deferred.resolve(response.data);
                    },
                    function fail() {
                        deferred.reject();
                    }
                );
            } else {
                deferred.resolve(response.data);
            }
        }, function fail() {
            deferred.reject();
        });

        return deferred.promise;
    }

    function getDevice(deviceId, ignoreErrors, config) {
        var deferred = $q.defer();
        var url = '/api/device/' + deviceId;
        if (!config) {
            config = {};
        }
        config = Object.assign(config, { ignoreErrors: ignoreErrors });
        $http.get(url, config).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function getDevices(deviceIds, config) {
        var deferred = $q.defer();
        var ids = '';
        for (var i=0;i<deviceIds.length;i++) {
            if (i>0) {
                ids += ',';
            }
            ids += deviceIds[i];
        }
        var url = '/api/devices?deviceIds=' + ids;
        $http.get(url, config).then(function success(response) {
            var devices = response.data;
            devices.sort(function (device1, device2) {
               var id1 =  device1.id.id;
               var id2 =  device2.id.id;
               var index1 = deviceIds.indexOf(id1);
               var index2 = deviceIds.indexOf(id2);
               return index1 - index2;
            });
            deferred.resolve(devices);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function fetchAliasDeviceByNameFilter(deviceNameFilter, limit, applyCustomersInfo, config) {
        var deferred = $q.defer();
        var user = userService.getCurrentUser();
        var promise;
        var pageLink = {limit: limit, textSearch: deviceNameFilter};
        if (user.authority === 'CUSTOMER_USER') {
            var customerId = user.customerId;
            promise = getCustomerDevices(customerId, pageLink, applyCustomersInfo, config);
        } else {
            promise = getTenantDevices(pageLink, applyCustomersInfo, config);
        }
        promise.then(
            function success(result) {
                if (result.data && result.data.length > 0) {
                    deferred.resolve(result.data);
                } else {
                    deferred.resolve(null);
                }
            },
            function fail() {
                deferred.resolve(null);
            }
        );
        return deferred.promise;
    }

    function deviceToDeviceInfo(device) {
        return { name: device.name, id: device.id.id };
    }

    function devicesToDevicesInfo(devices) {
        var devicesInfo = [];
        for (var d = 0; d < devices.length; d++) {
            devicesInfo.push(deviceToDeviceInfo(devices[d]));
        }
        return devicesInfo;
    }

    function processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred) {
        if (index < aliasIds.length) {
            var aliasId = aliasIds[index];
            var deviceAlias = deviceAliases[aliasId];
            var alias = deviceAlias.alias;
            if (!deviceAlias.deviceFilter) {
                getDevice(deviceAlias.deviceId).then(
                    function success(device) {
                        var resolvedAlias = {alias: alias, deviceId: device.id.id};
                        resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
                        resolution.aliasesInfo.deviceAliasesInfo[aliasId] = [
                            deviceToDeviceInfo(device)
                        ];
                        index++;
                        processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                    },
                    function fail() {
                        if (!resolution.error) {
                            resolution.error = 'dashboard.invalid-aliases-config';
                        }
                        index++;
                        processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                    }
                );
            } else {
                var deviceFilter = deviceAlias.deviceFilter;
                if (deviceFilter.useFilter) {
                    var deviceNameFilter = deviceFilter.deviceNameFilter;
                    fetchAliasDeviceByNameFilter(deviceNameFilter, 100, false).then(
                        function(devices) {
                            if (devices && devices != null) {
                                var resolvedAlias = {alias: alias, deviceId: devices[0].id.id};
                                resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
                                resolution.aliasesInfo.deviceAliasesInfo[aliasId] = devicesToDevicesInfo(devices);
                                index++;
                                processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                            } else {
                                if (!resolution.error) {
                                    resolution.error = 'dashboard.invalid-aliases-config';
                                }
                                index++;
                                processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                            }
                        });
                } else {
                    var deviceList = deviceFilter.deviceList;
                    getDevices(deviceList).then(
                        function success(devices) {
                            if (devices && devices.length > 0) {
                                var resolvedAlias = {alias: alias, deviceId: devices[0].id.id};
                                resolution.aliasesInfo.deviceAliases[aliasId] = resolvedAlias;
                                resolution.aliasesInfo.deviceAliasesInfo[aliasId] = devicesToDevicesInfo(devices);
                                index++;
                                processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                            } else {
                                if (!resolution.error) {
                                    resolution.error = 'dashboard.invalid-aliases-config';
                                }
                                index++;
                                processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                            }
                        },
                        function fail() {
                            if (!resolution.error) {
                                resolution.error = 'dashboard.invalid-aliases-config';
                            }
                            index++;
                            processDeviceAlias(index, aliasIds, deviceAliases, resolution, deferred);
                        }
                    );
                }
            }
        } else {
            deferred.resolve(resolution);
        }
    }

    function processDeviceAliases(deviceAliases) {
        var deferred = $q.defer();
        var resolution = {
            aliasesInfo: {
                deviceAliases: {},
                deviceAliasesInfo: {}
            }
        };
        var aliasIds = [];
        if (deviceAliases) {
            for (var aliasId in deviceAliases) {
                aliasIds.push(aliasId);
            }
        }
        processDeviceAlias(0, aliasIds, deviceAliases, resolution, deferred);
        return deferred.promise;
    }

    function checkDeviceAlias(deviceAlias) {
        var deferred = $q.defer();
        var deviceFilter;
        if (deviceAlias.deviceId) {
            deviceFilter = {
                useFilter: false,
                deviceNameFilter: '',
                deviceList: [deviceAlias.deviceId]
            }
        } else {
            deviceFilter = deviceAlias.deviceFilter;
        }
        var promise;
        if (deviceFilter.useFilter) {
            var deviceNameFilter = deviceFilter.deviceNameFilter;
            promise = fetchAliasDeviceByNameFilter(deviceNameFilter, 1, false);
        } else {
            var deviceList = deviceFilter.deviceList;
            promise = getDevices(deviceList);
        }
        promise.then(
            function success(devices) {
                if (devices && devices.length > 0) {
                    deferred.resolve(true);
                } else {
                    deferred.resolve(false);
                }
            },
            function fail() {
                deferred.resolve(false);
            }
        );
        return deferred.promise;
    }

    function saveDevice(device) {
        var deferred = $q.defer();
        var url = '/api/device';
        $http.post(url, device).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function deleteDevice(deviceId) {
        var deferred = $q.defer();
        var url = '/api/device/' + deviceId;
        $http.delete(url).then(function success() {
            deferred.resolve();
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function getDeviceCredentials(deviceId) {
        var deferred = $q.defer();
        var url = '/api/device/' + deviceId + '/credentials';
        $http.get(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function saveDeviceCredentials(deviceCredentials) {
        var deferred = $q.defer();
        var url = '/api/device/credentials';
        $http.post(url, deviceCredentials).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function assignDeviceToCustomer(customerId, deviceId) {
        var deferred = $q.defer();
        var url = '/api/customer/' + customerId + '/device/' + deviceId;
        $http.post(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function unassignDeviceFromCustomer(deviceId) {
        var deferred = $q.defer();
        var url = '/api/customer/device/' + deviceId;
        $http.delete(url).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function makeDevicePublic(deviceId) {
        var deferred = $q.defer();
        var url = '/api/customer/public/device/' + deviceId;
        $http.post(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function getDeviceKeys(deviceId, query, type) {
        var deferred = $q.defer();
        var url = '/api/plugins/telemetry/' + deviceId + '/keys/';
        if (type === types.dataKeyType.timeseries) {
            url += 'timeseries';
        } else if (type === types.dataKeyType.attribute) {
            url += 'attributes';
        }
        $http.get(url, null).then(function success(response) {
            var result = [];
            if (response.data) {
                if (query) {
                    var dataKeys = response.data;
                    var lowercaseQuery = angular.lowercase(query);
                    for (var i=0; i<dataKeys.length;i++) {
                        if (angular.lowercase(dataKeys[i]).indexOf(lowercaseQuery) === 0) {
                            result.push(dataKeys[i]);
                        }
                    }
                } else {
                    result = response.data;
                }
            }
            deferred.resolve(result);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function getDeviceTimeseriesValues(deviceId, keys, startTs, endTs, limit) {
        var deferred = $q.defer();
        var url = '/api/plugins/telemetry/' + deviceId + '/values/timeseries';
        url += '?keys=' + keys;
        url += '&startTs=' + startTs;
        url += '&endTs=' + endTs;
        if (angular.isDefined(limit)) {
            url += '&limit=' + limit;
        }
        $http.get(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function processDeviceAttributes(attributes, query, deferred, successCallback, update, apply) {
        attributes = $filter('orderBy')(attributes, query.order);
        if (query.search != null) {
            attributes = $filter('filter')(attributes, {key: query.search});
        }
        var responseData = {
            count: attributes.length
        }
        var startIndex = query.limit * (query.page - 1);
        responseData.data = attributes.slice(startIndex, startIndex + query.limit);
        successCallback(responseData, update, apply);
        if (deferred) {
            deferred.resolve();
        }
    }

    function getDeviceAttributes(deviceId, attributeScope, query, successCallback, config) {
        var deferred = $q.defer();
        var subscriptionId = deviceId + attributeScope;
        var das = deviceAttributesSubscriptionMap[subscriptionId];
        if (das) {
            if (das.attributes) {
                processDeviceAttributes(das.attributes, query, deferred, successCallback);
                das.subscriptionCallback = function(attributes) {
                    processDeviceAttributes(attributes, query, null, successCallback, true, true);
                }
            } else {
                das.subscriptionCallback = function(attributes) {
                    processDeviceAttributes(attributes, query, deferred, successCallback, false, true);
                    das.subscriptionCallback = function(attributes) {
                        processDeviceAttributes(attributes, query, null, successCallback, true, true);
                    }
                }
            }
        } else {
            var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope;
            $http.get(url, config).then(function success(response) {
                processDeviceAttributes(response.data, query, deferred, successCallback);
            }, function fail() {
                deferred.reject();
            });
        }
        return deferred;
    }

    function onSubscriptionData(data, subscriptionId) {
        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
        if (deviceAttributesSubscription) {
            if (!deviceAttributesSubscription.attributes) {
                deviceAttributesSubscription.attributes = [];
                deviceAttributesSubscription.keys = {};
            }
            var attributes = deviceAttributesSubscription.attributes;
            var keys = deviceAttributesSubscription.keys;
            for (var key in data) {
                var index = keys[key];
                var attribute;
                if (index > -1) {
                    attribute = attributes[index];
                } else {
                    attribute = {
                        key: key
                    };
                    index = attributes.push(attribute)-1;
                    keys[key] = index;
                }
                var attrData = data[key][0];
                attribute.lastUpdateTs = attrData[0];
                attribute.value = attrData[1];
            }
            if (deviceAttributesSubscription.subscriptionCallback) {
                deviceAttributesSubscription.subscriptionCallback(attributes);
            }
        }
    }

    function subscribeForDeviceAttributes(deviceId, attributeScope) {
        var subscriptionId = deviceId + attributeScope;
        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
        if (!deviceAttributesSubscription) {
            var subscriptionCommand = {
                deviceId: deviceId,
                scope: attributeScope
            };

            var type = attributeScope === types.latestTelemetry.value ?
                types.dataKeyType.timeseries : types.dataKeyType.attribute;

            var subscriber = {
                subscriptionCommand: subscriptionCommand,
                type: type,
                onData: function (data) {
                    if (data.data) {
                        onSubscriptionData(data.data, subscriptionId);
                    }
                }
            };
            deviceAttributesSubscription = {
                subscriber: subscriber,
                attributes: null
            }
            deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
            telemetryWebsocketService.subscribe(subscriber);
        }
        return subscriptionId;
    }
    function unsubscribeForDeviceAttributes(subscriptionId) {
        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
        if (deviceAttributesSubscription) {
            telemetryWebsocketService.unsubscribe(deviceAttributesSubscription.subscriber);
            delete deviceAttributesSubscriptionMap[subscriptionId];
        }
    }

    function saveDeviceAttributes(deviceId, attributeScope, attributes) {
        var deferred = $q.defer();
        var attributesData = {};
        for (var a=0; a<attributes.length;a++) {
            attributesData[attributes[a].key] = attributes[a].value;
        }
        var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope;
        $http.post(url, attributesData).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function deleteDeviceAttributes(deviceId, attributeScope, attributes) {
        var deferred = $q.defer();
        var keys = '';
        for (var i = 0; i < attributes.length; i++) {
            if (i > 0) {
                keys += ',';
            }
            keys += attributes[i].key;
        }
        var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope + '?keys=' + keys;
        $http.delete(url).then(function success() {
            deferred.resolve();
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function sendOneWayRpcCommand(deviceId, requestBody) {
        var deferred = $q.defer();
        var url = '/api/plugins/rpc/oneway/' + deviceId;
        $http.post(url, requestBody).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(rejection) {
            deferred.reject(rejection);
        });
        return deferred.promise;
    }

    function sendTwoWayRpcCommand(deviceId, requestBody) {
        var deferred = $q.defer();
        var url = '/api/plugins/rpc/twoway/' + deviceId;
        $http.post(url, requestBody).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(rejection) {
            deferred.reject(rejection);
        });
        return deferred.promise;
    }

}
