/*
 * Copyright © 2016 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, telemetryWebsocketService, types) {


    var deviceAttributesSubscriptionMap = {};

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

    return service;

    function getTenantDevices(pageLink) {
        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, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function getCustomerDevices(customerId, pageLink) {
        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, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function getDevice(deviceId) {
        var deferred = $q.defer();
        var url = '/api/device/' + deviceId;
        $http.get(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        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(response) {
            deferred.reject(response.data);
        });
        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(response) {
            deferred.reject(response.data);
        });
        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(response) {
            deferred.reject(response.data);
        });
        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(response) {
            deferred.reject(response.data);
        });
        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() {
            deferred.resolve();
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    function unassignDeviceFromCustomer(deviceId) {
        var deferred = $q.defer();
        var url = '/api/customer/device/' + deviceId;
        $http.delete(url).then(function success() {
            deferred.resolve();
        }, function fail(response) {
            deferred.reject(response.data);
        });
        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 in dataKeys) {
                        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) {
        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);
        if (deferred) {
            deferred.resolve();
        }
    }

    function getDeviceAttributes(deviceId, attributeScope, query, successCallback) {
        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);
                }
            } else {
                das.subscriptionCallback = function(attributes) {
                    processDeviceAttributes(attributes, query, deferred, successCallback);
                    das.subscriptionCallback = function(attributes) {
                        processDeviceAttributes(attributes, query, null, successCallback, true);
                    }
                }
            }
        } else {
            var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope;
            $http.get(url, null).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) {
                    onSubscriptionData(data, subscriptionId);
                }
            };
            telemetryWebsocketService.subscribe(subscriber);
            deviceAttributesSubscription = {
                subscriber: subscriber,
                attributes: null
            }
            deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
        }
        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 in attributes) {
            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;
    }

}
