/*
 * 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 $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';

/* eslint-disable angular/angularelement */

/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils,
                                         datasourceService, deviceService, visibleRect, isEdit, stDiff, widget, deviceAliasList, widgetType) {

    var vm = this;

    $scope.$timeout = $timeout;
    $scope.$q = $q;
    $scope.$injector = $injector;
    $scope.tbRaf = tbRaf;

    $scope.rpcRejection = null;
    $scope.rpcErrorText = null;
    $scope.rpcEnabled = false;
    $scope.executingRpcRequest = false;
    $scope.executingPromises = [];

    var gridsterItemInited = false;

    var datasourceListeners = [];
    var targetDeviceAliasId = null;
    var targetDeviceId = null;
    var originalTimewindow = null;
    var subscriptionTimewindow = {
        fixedWindow: null,
        realtimeWindowMs: null,
        aggregation: null
    };
    var dataUpdateCaf = null;

    /*
     *   data = array of datasourceData
     *   datasourceData = {
     *   			tbDatasource,
     *   			dataKey,     { name, config }
     *   			data = array of [time, value]
     *   }
     *
     *
     */

    var widgetContext = {
        inited: false,
        $scope: $scope,
        $container: $('#container', $element),
        $containerParent: $($element),
        width: 0,
        height: 0,
        isEdit: isEdit,
        isMobile: false,
        settings: widget.config.settings,
        datasources: widget.config.datasources,
        data: [],
        timeWindow: {
            stDiff: stDiff
        },
        timewindowFunctions: {
            onUpdateTimewindow: onUpdateTimewindow,
            onResetTimewindow: onResetTimewindow
        },
        controlApi: {
            sendOneWayCommand: function(method, params, timeout) {
                return sendCommand(true, method, params, timeout);
            },
            sendTwoWayCommand: function(method, params, timeout) {
                return sendCommand(false, method, params, timeout);
            }
        }
    };

    var widgetTypeInstance;
    try {
        widgetTypeInstance = new widgetType(widgetContext);
    } catch (e) {
        handleWidgetException(e);
        widgetTypeInstance = {};
    }
    if (!widgetTypeInstance.onInit) {
        widgetTypeInstance.onInit = function() {};
    }
    if (!widgetTypeInstance.onDataUpdated) {
        widgetTypeInstance.onDataUpdated = function() {};
    }
    if (!widgetTypeInstance.onResize) {
        widgetTypeInstance.onResize = function() {};
    }
    if (!widgetTypeInstance.onEditModeChanged) {
        widgetTypeInstance.onEditModeChanged = function() {};
    }
    if (!widgetTypeInstance.onMobileModeChanged) {
        widgetTypeInstance.onMobileModeChanged = function() {};
    }
    if (!widgetTypeInstance.onDestroy) {
        widgetTypeInstance.onDestroy = function() {};
    }

    //var bounds = {top: 0, left: 0, bottom: 0, right: 0};
    //TODO: widgets visibility
    /*var visible = false;*/

    $scope.clearRpcError = function() {
        $scope.rpcRejection = null;
        $scope.rpcErrorText = null;
    }

    vm.gridsterItemInitialized = gridsterItemInitialized;

    //TODO: widgets visibility
    /*vm.visibleRectChanged = visibleRectChanged;

    function visibleRectChanged(newVisibleRect) {
        visibleRect = newVisibleRect;
        updateVisibility();
    }*/

    initialize();

    function handleWidgetException(e) {
        $log.error(e);
        $scope.widgetErrorData = utils.processWidgetException(e);
    }

    function onInit() {
        if (!widgetContext.inited) {
            widgetContext.inited = true;
            try {
                widgetTypeInstance.onInit();
            } catch (e) {
                handleWidgetException(e);
            }
            if (widgetContext.dataUpdatePending) {
                widgetContext.dataUpdatePending = false;
                onDataUpdated();
            }
        }
    }

    function updateTimewindow() {
        widgetContext.timeWindow.interval = subscriptionTimewindow.aggregation.interval || 1000;
        if (subscriptionTimewindow.realtimeWindowMs) {
            widgetContext.timeWindow.maxTime = (new Date).getTime() + widgetContext.timeWindow.stDiff;
            widgetContext.timeWindow.minTime = widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
        } else if (subscriptionTimewindow.fixedWindow) {
            widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
            widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
        }
    }

    function onDataUpdated() {
        if (widgetContext.inited) {
            if (dataUpdateCaf) {
                dataUpdateCaf();
                dataUpdateCaf = null;
            }
            dataUpdateCaf = tbRaf(function() {
                    try {
                        widgetTypeInstance.onDataUpdated();
                    } catch (e) {
                        handleWidgetException(e);
                    }
                });
        } else {
            widgetContext.dataUpdatePending = true;
        }
    }

    function checkSize() {
        var width = widgetContext.$containerParent.width();
        var height = widgetContext.$containerParent.height();
        var sizeChanged = false;

        if (!widgetContext.width || widgetContext.width != width || !widgetContext.height || widgetContext.height != height) {
            if (width > 0 && height > 0) {
                widgetContext.$container.css('height', height + 'px');
                widgetContext.$container.css('width', width + 'px');
                widgetContext.width = width;
                widgetContext.height = height;
                sizeChanged = true;
            }
        }
        return sizeChanged;
    }

    function onResize() {
        if (checkSize()) {
            if (widgetContext.inited) {
                tbRaf(function() {
                    try {
                        widgetTypeInstance.onResize();
                    } catch (e) {
                        handleWidgetException(e);
                    }
                });
            } else if (gridsterItemInited) {
                onInit();
            }
        }
    }

    function gridsterItemInitialized(item) {
        if (item && item.gridster) {
            widgetContext.isMobile = item.gridster.isMobile;
            gridsterItemInited = true;
            if (checkSize()) {
                onInit();
            }
            // gridsterItemElement = $(item.$element);
            //updateVisibility();
        }
    }

    function onEditModeChanged(isEdit) {
        if (widgetContext.isEdit != isEdit) {
            widgetContext.isEdit = isEdit;
            if (widgetContext.inited) {
                tbRaf(function() {
                    try {
                        widgetTypeInstance.onEditModeChanged();
                    } catch (e) {
                        handleWidgetException(e);
                    }
                });
            }
        }
    }

    function onMobileModeChanged(isMobile) {
        if (widgetContext.isMobile != isMobile) {
            widgetContext.isMobile = isMobile;
            if (widgetContext.inited) {
                tbRaf(function() {
                    try {
                        widgetTypeInstance.onMobileModeChanged();
                    } catch (e) {
                        handleWidgetException(e);
                    }
                });
            }
        }
    }

    function onDestroy() {
        unsubscribe();
        if (widgetContext.inited) {
            widgetContext.inited = false;
            widgetContext.dataUpdatePending = false;
            try {
                widgetTypeInstance.onDestroy();
            } catch (e) {
                handleWidgetException(e);
            }
        }
    }

    function onRestart() {
        onDestroy();
        onInit();
    }

    function initialize() {
        if (widget.type !== types.widgetType.rpc.value) {
            for (var i in widget.config.datasources) {
                var datasource = angular.copy(widget.config.datasources[i]);
                for (var a in datasource.dataKeys) {
                    var dataKey = datasource.dataKeys[a];
                    var datasourceData = {
                        datasource: datasource,
                        dataKey: dataKey,
                        data: []
                    };
                    widgetContext.data.push(datasourceData);
                }
            }
        } else {
            if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
                targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
                if (deviceAliasList[targetDeviceAliasId]) {
                    targetDeviceId = deviceAliasList[targetDeviceAliasId].deviceId;
                }
            }
            if (targetDeviceId) {
                $scope.rpcEnabled = true;
            } else {
                $scope.rpcEnabled = $scope.widgetEditMode ? true : false;
            }
        }

        $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
            onEditModeChanged(isEdit);
        });

        addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef

        $scope.$watch(function () {
            return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
        }, function () {
            //updateBounds();
            $scope.$emit("widgetPositionChanged", widget);
        });

        $scope.$on('gridster-item-resized', function (event, item) {
            if (!widgetContext.isMobile) {
                widget.sizeX = item.sizeX;
                widget.sizeY = item.sizeY;
            }
        });

        $scope.$on('mobileModeChanged', function (event, newIsMobile) {
            onMobileModeChanged(newIsMobile);
        });

        $scope.$on('deviceAliasListChanged', function (event, newDeviceAliasList) {
            deviceAliasList = newDeviceAliasList;
            if (widget.type === types.widgetType.rpc.value) {
                if (targetDeviceAliasId) {
                    var deviceId = null;
                    if (deviceAliasList[targetDeviceAliasId]) {
                        deviceId = deviceAliasList[targetDeviceAliasId].deviceId;
                    }
                    if (!angular.equals(deviceId, targetDeviceId)) {
                        targetDeviceId = deviceId;
                        if (targetDeviceId) {
                            $scope.rpcEnabled = true;
                        } else {
                            $scope.rpcEnabled = $scope.widgetEditMode ? true : false;
                        }
                        onRestart();
                    }
                }
            } else {
                checkSubscriptions();
            }
        });

        $scope.$on("$destroy", function () {
            removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
            onDestroy();
        });

        if (widget.type === types.widgetType.timeseries.value) {
            $scope.$watch(function () {
                return widget.config.timewindow;
            }, function (newTimewindow, prevTimewindow) {
                if (!angular.equals(newTimewindow, prevTimewindow)) {
                    unsubscribe();
                    subscribe();
                }
            });
        }
        subscribe();
    }

    function sendCommand(oneWayElseTwoWay, method, params, timeout) {
        if (!$scope.rpcEnabled) {
            return $q.reject();
        }

        if ($scope.rpcRejection && $scope.rpcRejection.status !== 408) {
            $scope.rpcRejection = null;
            $scope.rpcErrorText = null;
        }

        var requestBody = {
            method: method,
            params: params
        };

        if (timeout && timeout > 0) {
            requestBody.timeout = timeout;
        }

        var deferred = $q.defer();
        $scope.executingRpcRequest = true;
        if ($scope.widgetEditMode) {
            $timeout(function() {
                $scope.executingRpcRequest = false;
                if (oneWayElseTwoWay) {
                    deferred.resolve();
                } else {
                    deferred.resolve(requestBody);
                }
            }, 500);
        } else {
            $scope.executingPromises.push(deferred.promise);
            var targetSendFunction = oneWayElseTwoWay ? deviceService.sendOneWayRpcCommand : deviceService.sendTwoWayRpcCommand;
            targetSendFunction(targetDeviceId, requestBody).then(
                function success(responseBody) {
                    $scope.rpcRejection = null;
                    $scope.rpcErrorText = null;
                    var index = $scope.executingPromises.indexOf(deferred.promise);
                    if (index >= 0) {
                        $scope.executingPromises.splice( index, 1 );
                    }
                    $scope.executingRpcRequest = $scope.executingPromises.length > 0;
                    deferred.resolve(responseBody);
                },
                function fail(rejection) {
                    var index = $scope.executingPromises.indexOf(deferred.promise);
                    if (index >= 0) {
                        $scope.executingPromises.splice( index, 1 );
                    }
                    $scope.executingRpcRequest = $scope.executingPromises.length > 0;
                    if (!$scope.executingRpcRequest || rejection.status === 408) {
                        $scope.rpcRejection = rejection;
                        if (rejection.status === 408) {
                            $scope.rpcErrorText = 'Device is offline.';
                        } else {
                            $scope.rpcErrorText =  'Error : ' + rejection.status + ' - ' + rejection.statusText;
                            if (rejection.data && rejection.data.length > 0) {
                                $scope.rpcErrorText += '</br>';
                                $scope.rpcErrorText += rejection.data;
                            }
                        }
                    }
                    deferred.reject(rejection);
                }
            );
        }
        return deferred.promise;
    }

    //TODO: widgets visibility
    /*function updateVisibility(forceRedraw) {
        if (visibleRect) {
            forceRedraw = forceRedraw || visibleRect.containerResized;
            var newVisible = false;
            if (visibleRect.isMobile && gridsterItemElement) {
                var topPx = gridsterItemElement.position().top;
                var bottomPx = topPx + widget.sizeY * visibleRect.curRowHeight;
                newVisible = !(topPx > visibleRect.bottomPx ||
                bottomPx < visibleRect.topPx);
            } else {
                newVisible = !(bounds.left > visibleRect.right ||
                bounds.right < visibleRect.left ||
                bounds.top > visibleRect.bottom ||
                bounds.bottom < visibleRect.top);
            }
            if (visible != newVisible) {
                visible = newVisible;
                if (visible) {
                    onRedraw(50);
                }
            } else if (forceRedraw && visible) {
                onRedraw(50);
            }
        }
    }*/

/*    function updateBounds() {
        bounds = {
            top: widget.row,
            left: widget.col,
            bottom: widget.row + widget.sizeY,
            right: widget.col + widget.sizeX
        };
        updateVisibility(true);
        onRedraw();
    }*/

    function onResetTimewindow() {
        if (originalTimewindow) {
            widget.config.timewindow = angular.copy(originalTimewindow);
            originalTimewindow = null;
        }
    }

    function onUpdateTimewindow(startTimeMs, endTimeMs) {
        if (!originalTimewindow) {
            originalTimewindow = angular.copy(widget.config.timewindow);
        }
        widget.config.timewindow = {
            history: {
                fixedTimewindow: {
                    startTimeMs: startTimeMs,
                    endTimeMs: endTimeMs
                }
            },
            aggregation: angular.copy(widget.config.timewindow.aggregation)
        };
    }

    function dataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
        var update = true;
        if (widget.type === types.widgetType.latest.value) {
            var prevData = widgetContext.data[datasourceIndex + dataKeyIndex].data;
            if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) {
                var prevValue = prevData[0][1];
                if (prevValue === sourceData.data[0][1]) {
                    update = false;
                }
            }
        }
        if (update) {
            if (subscriptionTimewindow.realtimeWindowMs) {
                updateTimewindow();
            }
            widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data;
            onDataUpdated();
        }
    }

    function checkSubscriptions() {
        if (widget.type !== types.widgetType.rpc.value) {
            var subscriptionsChanged = false;
            for (var i in datasourceListeners) {
                var listener = datasourceListeners[i];
                var deviceId = null;
                var aliasName = null;
                if (listener.datasource.type === types.datasourceType.device) {
                    if (deviceAliasList[listener.datasource.deviceAliasId]) {
                        deviceId = deviceAliasList[listener.datasource.deviceAliasId].deviceId;
                        aliasName = deviceAliasList[listener.datasource.deviceAliasId].alias;
                    }
                    if (!angular.equals(deviceId, listener.deviceId) ||
                        !angular.equals(aliasName, listener.datasource.name)) {
                        subscriptionsChanged = true;
                        break;
                    }
                }
            }
            if (subscriptionsChanged) {
                unsubscribe();
                subscribe();
            }
        }
    }

    function unsubscribe() {
        if (widget.type !== types.widgetType.rpc.value) {
            for (var i in datasourceListeners) {
                var listener = datasourceListeners[i];
                datasourceService.unsubscribeFromDatasource(listener);
            }
            datasourceListeners = [];
        }
    }

    function subscribe() {
        if (widget.type !== types.widgetType.rpc.value) {
            var index = 0;
            subscriptionTimewindow.fixedWindow = null;
            subscriptionTimewindow.realtimeWindowMs = null;
            subscriptionTimewindow.aggregation = {
                limit: 200,
                type: types.aggregation.avg.value
            };
            if (widget.type === types.widgetType.timeseries.value &&
                angular.isDefined(widget.config.timewindow)) {
                var timeWindow = 0;
                if (angular.isDefined(widget.config.timewindow.aggregation)) {
                    subscriptionTimewindow.aggregation = {
                        limit: widget.config.timewindow.aggregation.limit || 200,
                        type: widget.config.timewindow.aggregation.type || types.aggregation.avg.value
                    };
                }

                if (angular.isDefined(widget.config.timewindow.realtime)) {
                    subscriptionTimewindow.realtimeWindowMs = widget.config.timewindow.realtime.timewindowMs;
                    subscriptionTimewindow.startTs = (new Date).getTime() + widgetContext.timeWindow.stDiff - subscriptionTimewindow.realtimeWindowMs;
                    timeWindow = subscriptionTimewindow.realtimeWindowMs;
                } else if (angular.isDefined(widget.config.timewindow.history)) {
                    if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) {
                        var currentTime = (new Date).getTime();
                        subscriptionTimewindow.fixedWindow = {
                            startTimeMs: currentTime - widget.config.timewindow.history.timewindowMs,
                            endTimeMs: currentTime
                        }
                        timeWindow = widget.config.timewindow.history.timewindowMs;
                    } else {
                        subscriptionTimewindow.fixedWindow = {
                            startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs,
                            endTimeMs: widget.config.timewindow.history.fixedTimewindow.endTimeMs
                        }
                        timeWindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
                    }
                    subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs;
                }
                var aggregation = subscriptionTimewindow.aggregation;
                var noAggregation = aggregation.type === types.aggregation.none.value;
                var interval = Math.floor(timeWindow / aggregation.limit);
                if (!noAggregation) {
                    aggregation.interval = Math.max(interval, 1000);
                    aggregation.limit = Math.ceil(interval/aggregation.interval * aggregation.limit);
                    aggregation.timeWindow = aggregation.interval * aggregation.limit;
                } else {
                    aggregation.timeWindow = interval * aggregation.limit;
                    aggregation.interval = 1000;
                }
                updateTimewindow();
                if (subscriptionTimewindow.fixedWindow) {
                    onDataUpdated();
                }
            }
            for (var i in widget.config.datasources) {
                var datasource = widget.config.datasources[i];
                var deviceId = null;
                if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
                    if (deviceAliasList[datasource.deviceAliasId]) {
                        deviceId = deviceAliasList[datasource.deviceAliasId].deviceId;
                        datasource.name = deviceAliasList[datasource.deviceAliasId].alias;
                    }
                } else {
                    datasource.name = types.datasourceType.function;
                }
                var listener = {
                    widget: widget,
                    subscriptionTimewindow: subscriptionTimewindow,
                    datasource: datasource,
                    deviceId: deviceId,
                    dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
                        dataUpdated(data, datasourceIndex, dataKeyIndex);
                    },
                    datasourceIndex: index
                };

                for (var a = 0; a < datasource.dataKeys.length; a++) {
                    widgetContext.data[index + a].data = [];
                }

                index += datasource.dataKeys.length;

                datasourceListeners.push(listener);
                datasourceService.subscribeToDatasource(listener);
            }
        }
    }

}

/* eslint-enable angular/angularelement */