/*
 * 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 moment from 'moment';
import tinycolor from 'tinycolor2';

import thinsboardLedLight from '../components/led-light.directive';

import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
import TbDigitalGauge from '../widget/lib/digital-gauge';

import 'oclazyload';
import cssjs from '../../vendor/css.js/css';

import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';

export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thinsboardLedLight, thingsboardTypes, thingsboardUtils])
    .factory('widgetService', WidgetService)
    .name;

/*@ngInject*/
function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {

    $window.$ = $;
    $window.moment = moment;
    $window.tinycolor = tinycolor;
    $window.lazyLoad = $ocLazyLoad;

    $window.TbAnalogueLinearGauge = TbAnalogueLinearGauge;
    $window.TbAnalogueRadialGauge = TbAnalogueRadialGauge;
    $window.TbDigitalGauge = TbDigitalGauge;

    var cssParser = new cssjs();
    cssParser.testMode = false;

    var missingWidgetType;
    var errorWidgetType;

    var editingWidgetType;

    var widgetsInfoInMemoryCache = {};

    var allWidgetsBundles = undefined;
    var systemWidgetsBundles = undefined;
    var tenantWidgetsBundles = undefined;

    $rootScope.widgetServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
        invalidateWidgetsBundleCache();
    });

    initEditingWidgetType();
    initWidgetPlaceholders();

    var service = {
        getWidgetTemplate: getWidgetTemplate,
        getSystemWidgetsBundles: getSystemWidgetsBundles,
        getTenantWidgetsBundles: getTenantWidgetsBundles,
        getAllWidgetsBundles: getAllWidgetsBundles,
        getSystemWidgetsBundlesByPageLink: getSystemWidgetsBundlesByPageLink,
        getTenantWidgetsBundlesByPageLink: getTenantWidgetsBundlesByPageLink,
        getAllWidgetsBundlesByPageLink: getAllWidgetsBundlesByPageLink,
        getWidgetsBundleByAlias: getWidgetsBundleByAlias,
        saveWidgetsBundle: saveWidgetsBundle,
        getWidgetsBundle: getWidgetsBundle,
        deleteWidgetsBundle: deleteWidgetsBundle,
        getBundleWidgetTypes: getBundleWidgetTypes,
        getWidgetInfo: getWidgetInfo,
        getInstantWidgetInfo: getInstantWidgetInfo,
        deleteWidgetType: deleteWidgetType,
        saveWidgetType: saveWidgetType,
        getWidgetType: getWidgetType,
        getWidgetTypeById: getWidgetTypeById,
        toWidgetInfo: toWidgetInfo
    }

    return service;

    function initEditingWidgetType() {
        if ($rootScope.widgetEditMode) {
            editingWidgetType =
                toWidgetType({
                    widgetName: $rootScope.editWidgetInfo.widgetName,
                    alias: 'customWidget',
                    type: $rootScope.editWidgetInfo.type,
                    sizeX: $rootScope.editWidgetInfo.sizeX,
                    sizeY: $rootScope.editWidgetInfo.sizeY,
                    resources: $rootScope.editWidgetInfo.resources,
                    templateHtml: $rootScope.editWidgetInfo.templateHtml,
                    templateCss: $rootScope.editWidgetInfo.templateCss,
                    controllerScript: $rootScope.editWidgetInfo.controllerScript,
                    settingsSchema: $rootScope.editWidgetInfo.settingsSchema,
                    dataKeySettingsSchema: $rootScope.editWidgetInfo.dataKeySettingsSchema,
                    defaultConfig: $rootScope.editWidgetInfo.defaultConfig
            }, {id: '1'}, { id: types.id.nullUid }, 'customWidgetBundle');
        }
    }

    function initWidgetPlaceholders() {

        missingWidgetType = {
            widgetName: 'Widget type not found',
            alias: 'undefined',
            sizeX: 8,
            sizeY: 6,
            resources: [],
            templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-not-found</div></div>',
            templateCss: '',
            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
            settingsSchema: '{}\n',
            dataKeySettingsSchema: '{}\n',
            defaultConfig: '{\n' +
            '"title": "Widget type not found",\n' +
            '"datasources": [],\n' +
            '"settings": {}\n' +
            '}\n'
        };

        errorWidgetType = {
            widgetName: 'Error loading widget',
            alias: 'error',
            sizeX: 8,
            sizeY: 6,
            resources: [],
            templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>',
            templateCss: '',
            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
            settingsSchema: '{}\n',
            dataKeySettingsSchema: '{}\n',
            defaultConfig: '{\n' +
            '"title": "Widget failed to load",\n' +
            '"datasources": [],\n' +
            '"settings": {}\n' +
            '}\n'
        };
    }

    function toWidgetInfo(widgetType) {

        var widgetInfo = {
            widgetName: widgetType.name,
            alias: widgetType.alias
        }

        var descriptor = widgetType.descriptor;

        widgetInfo.type = descriptor.type;
        widgetInfo.sizeX = descriptor.sizeX;
        widgetInfo.sizeY = descriptor.sizeY;
        widgetInfo.resources = descriptor.resources;
        widgetInfo.templateHtml = descriptor.templateHtml;
        widgetInfo.templateCss = descriptor.templateCss;
        widgetInfo.controllerScript = descriptor.controllerScript;
        widgetInfo.settingsSchema = descriptor.settingsSchema;
        widgetInfo.dataKeySettingsSchema = descriptor.dataKeySettingsSchema;
        widgetInfo.defaultConfig = descriptor.defaultConfig;

        return widgetInfo;
    }

    function toWidgetType(widgetInfo, id, tenantId, bundleAlias) {
        var widgetType = {
            id: id,
            tenantId: tenantId,
            bundleAlias: bundleAlias,
            alias: widgetInfo.alias,
            name: widgetInfo.widgetName
        }

        var descriptor = {
            type: widgetInfo.type,
            sizeX: widgetInfo.sizeX,
            sizeY: widgetInfo.sizeY,
            resources: widgetInfo.resources,
            templateHtml: widgetInfo.templateHtml,
            templateCss: widgetInfo.templateCss,
            controllerScript: widgetInfo.controllerScript,
            settingsSchema: widgetInfo.settingsSchema,
            dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
            defaultConfig: widgetInfo.defaultConfig
        }

        widgetType.descriptor = descriptor;

        return widgetType;
    }

    function getWidgetTemplate(type) {
        var deferred = $q.defer();
        var templateWidgetType = types.widgetType.timeseries;
        for (var t in types.widgetType) {
            var widgetType = types.widgetType[t];
            if (widgetType.value === type) {
                templateWidgetType = widgetType;
                break;
            }
        }
        getWidgetType(templateWidgetType.template.bundleAlias,
                      templateWidgetType.template.alias, true).then(
                          function success(widgetType) {
                              var widgetInfo = toWidgetInfo(widgetType);
                              widgetInfo.alias = undefined;
                              deferred.resolve(widgetInfo);
                          },
                          function fail() {
                              deferred.reject();
                          }
        );
        return deferred.promise;
    }

    /** Cache functions **/

    function getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
        return widgetsInfoInMemoryCache[key];
    }

    function putWidgetInfoToCache(widgetInfo, bundleAlias, widgetTypeAlias, isSystem) {
        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
        widgetsInfoInMemoryCache[key] = widgetInfo;
    }

    function deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
        delete widgetsInfoInMemoryCache[key];
    }

    function deleteWidgetsBundleFromCache(bundleAlias, isSystem) {
        var key = (isSystem ? 'sys_' : '') + bundleAlias;
        for (var cacheKey in widgetsInfoInMemoryCache) {
            if (cacheKey.startsWith(key)) {
                delete widgetsInfoInMemoryCache[cacheKey];
            }
        }
    }

    /** Bundle functions **/

    function invalidateWidgetsBundleCache() {
        allWidgetsBundles = undefined;
        systemWidgetsBundles = undefined;
        tenantWidgetsBundles = undefined;
    }

    function loadWidgetsBundleCache() {
        var deferred = $q.defer();
        if (!allWidgetsBundles) {
            var url = '/api/widgetsBundles';
            $http.get(url, null).then(function success(response) {
                allWidgetsBundles = response.data;
                systemWidgetsBundles = [];
                tenantWidgetsBundles = [];
                allWidgetsBundles = $filter('orderBy')(allWidgetsBundles, ['+title', '-createdTime']);
                for (var i = 0; i < allWidgetsBundles.length; i++) {
                    var widgetsBundle = allWidgetsBundles[i];
                    if (widgetsBundle.tenantId.id === types.id.nullUid) {
                        systemWidgetsBundles.push(widgetsBundle);
                    } else {
                        tenantWidgetsBundles.push(widgetsBundle);
                    }
                }
                deferred.resolve();
            }, function fail() {
                deferred.reject();
            });
        } else {
            deferred.resolve();
        }
        return deferred.promise;
    }


    function getSystemWidgetsBundles() {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                deferred.resolve(systemWidgetsBundles);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getTenantWidgetsBundles() {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                deferred.resolve(tenantWidgetsBundles);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getAllWidgetsBundles() {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                deferred.resolve(allWidgetsBundles);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getSystemWidgetsBundlesByPageLink(pageLink) {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                utils.filterSearchTextEntities(systemWidgetsBundles, 'title', pageLink, deferred);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getTenantWidgetsBundlesByPageLink(pageLink) {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                utils.filterSearchTextEntities(tenantWidgetsBundles, 'title', pageLink, deferred);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getAllWidgetsBundlesByPageLink(pageLink) {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                utils.filterSearchTextEntities(allWidgetsBundles, 'title', pageLink, deferred);
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function getWidgetsBundleByAlias(bundleAlias) {
        var deferred = $q.defer();
        loadWidgetsBundleCache().then(
            function success() {
                var widgetsBundles = $filter('filter')(allWidgetsBundles, {alias: bundleAlias});
                if (widgetsBundles.length > 0) {
                    deferred.resolve(widgetsBundles[0]);
                } else {
                    deferred.resolve();
                }
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

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

    function getWidgetsBundle(widgetsBundleId) {
        var deferred = $q.defer();

        var url = '/api/widgetsBundle/' + widgetsBundleId;
        $http.get(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });

        return deferred.promise;
    }

    function deleteWidgetsBundle(widgetsBundleId) {
        var deferred = $q.defer();

        getWidgetsBundle(widgetsBundleId).then(
            function success(response) {
                var widgetsBundle = response;
                var url = '/api/widgetsBundle/' + widgetsBundleId;
                $http.delete(url).then(function success() {
                    invalidateWidgetsBundleCache();
                    deleteWidgetsBundleFromCache(widgetsBundle.alias,
                        widgetsBundle.tenantId.id === types.id.nullUid);
                    deferred.resolve();
                }, function fail(response) {
                    deferred.reject(response.data);
                });
            },
            function fail() {
                deferred.reject();
            }
        );

        return deferred.promise;
    }

    function getBundleWidgetTypes(bundleAlias, isSystem) {
        var deferred = $q.defer();
        var url = '/api/widgetTypes?isSystem=' + (isSystem ? 'true' : 'false') +
                    '&bundleAlias='+bundleAlias;
        $http.get(url, null).then(function success(response) {
            deferred.resolve(response.data);
        }, function fail(response) {
            deferred.reject(response.data);
        });
        return deferred.promise;
    }

    /** Widget type functions **/

    function getInstantWidgetInfo(widget) {
        var widgetInfo = getWidgetInfoFromCache(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
        if (widgetInfo) {
            return widgetInfo;
        } else {
            return {};
        }
    }

    function getWidgetInfo(bundleAlias, widgetTypeAlias, isSystem) {
        var deferred = $q.defer();
        var widgetInfo = getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
        if (widgetInfo) {
            deferred.resolve(widgetInfo);
        } else {
            if ($rootScope.widgetEditMode) {
                loadWidget(editingWidgetType, bundleAlias, isSystem, deferred);
            } else {
                getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
                    function success(widgetType) {
                        loadWidget(widgetType, bundleAlias, isSystem, deferred);
                    }, function fail() {
                        deferred.resolve(missingWidgetType);
                    }
                );
            }
        }
        return deferred.promise;
    }

    function loadWidget(widgetType, bundleAlias, isSystem, deferred) {
        var widgetInfo = toWidgetInfo(widgetType);
        loadWidgetResources(widgetInfo, bundleAlias, isSystem).then(
            function success() {
                putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
                deferred.resolve(widgetInfo);
            }, function fail(errorMessages) {
                widgetInfo = angular.copy(errorWidgetType);
                for (var e in errorMessages) {
                    var error = errorMessages[e];
                    widgetInfo.templateHtml += '<div class="tb-widget-error-msg">' + error + '</div>';
                }
                widgetInfo.templateHtml += '</div>';
                deferred.resolve(widgetInfo);
            }
        );
    }

    function getWidgetType(bundleAlias, widgetTypeAlias, isSystem) {
        var deferred = $q.defer();
        var url = '/api/widgetType?isSystem=' + (isSystem ? 'true' : 'false') +
            '&bundleAlias='+bundleAlias+'&alias='+widgetTypeAlias;
        $http.get(url, null).then(function success(response) {
            var widgetType = response.data;
            deferred.resolve(widgetType);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

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

    function deleteWidgetType(bundleAlias, widgetTypeAlias, isSystem) {
        var deferred = $q.defer();
        getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
            function success(widgetType) {
                var url = '/api/widgetType/' + widgetType.id.id;
                $http.delete(url).then(function success() {
                    deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
                    deferred.resolve();
                }, function fail() {
                    deferred.reject();
                });
            },
            function fail() {
                deferred.reject();
            }
        );
        return deferred.promise;
    }

    function saveWidgetType(widgetInfo, id, bundleAlias) {
        var deferred = $q.defer();
        var widgetType = toWidgetType(widgetInfo, id, undefined, bundleAlias);
        var url = '/api/widgetType';
        $http.post(url, widgetType).then(function success(response) {
            var widgetType = response.data;
            deleteWidgetInfoFromCache(widgetType.bundleAlias, widgetType.alias, widgetType.tenantId.id === types.id.nullUid);
            deferred.resolve(widgetType);
        }, function fail() {
            deferred.reject();
        });
        return deferred.promise;
    }

    function loadWidgetResources(widgetInfo, bundleAlias, isSystem) {

        var deferred = $q.defer();
        var errors = [];

        var widgetNamespace = "widget-type-" + (isSystem ? 'sys-' : '') + bundleAlias + '-' + widgetInfo.alias;
        cssParser.cssPreviewNamespace = widgetNamespace;
        cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);

        function loadNextOrComplete(i) {
            i++;
            if (i < widgetInfo.resources.length) {
                loadNext(i);
            } else {
                if (errors.length > 0) {
                    deferred.reject(errors);
                } else {
                    deferred.resolve();
                }
            }
        }

        function loadNext(i) {
            var resourceUrl = widgetInfo.resources[i].url;
            if (resourceUrl && resourceUrl.length > 0) {
                $ocLazyLoad.load(resourceUrl).then(
                    function success() {
                        loadNextOrComplete(i);
                    },
                    function fail() {
                        errors.push('Failed to load widget resource: \'' + resourceUrl + '\'');
                        loadNextOrComplete(i);
                    }
                );
            } else {
                loadNextOrComplete(i);
            }
        }

        if (widgetInfo.resources.length > 0) {
            loadNext(0);
        } else {
            deferred.resolve();
        }

        return deferred.promise;
    }

}
