text: /*
   * Copyright © 2016-2018 dujoy.cn
   */
  /* eslint-disable import/no-unresolved, import/default */
  
  import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
  
  /* eslint-enable import/no-unresolved, import/default */
  
  import tinycolor from 'tinycolor2';
  import jsonSchemaDefaults from 'json-schema-defaults';
  import base64js from 'base64-js';
  import {utf8Encode, utf8Decode} from './utf8-support';
  
  import thingsboardTypes from './types.constant';
  
  export default angular.module('thingsboard.utils', [thingsboardTypes])
      .factory('utils', Utils)
      .name;
  
  const varsRegex = /\$\{([^\}]*)\}/g;
  
  /*@ngInject*/
  function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) {
  
      var predefinedFunctions = {},
          predefinedFunctionsList = [],
          materialColors = [],
          materialIcons = [];
  
      var commonMaterialIcons = [ 'more_horiz', 'more_vert', 'open_in_new', 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
          'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
          'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
          'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
          'share', 'add', 'edit', 'done' ];
  
      predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
      predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
      predefinedFunctions['Random'] =
          "var value = prevValue + Math.random() * 100 - 50;\n" +
          "var multiplier = Math.pow(10, 2 || 0);\n" +
          "var value = Math.round(value * multiplier) / multiplier;\n" +
          "if (value < -1000) {\n" +
          "	value = -1000;\n" +
          "} else if (value > 1000) {\n" +
          "	value = 1000;\n" +
          "}\n" +
          "return value;";
  
      for (var func in predefinedFunctions) {
          predefinedFunctionsList.push(func);
      }
  
      var colorPalettes = ['blue', 'green', 'red', 'amber', 'blue-grey', 'purple', 'light-green', 'indigo', 'pink', 'yellow', 'light-blue', 'orange', 'deep-purple', 'lime', 'teal', 'brown', 'cyan', 'deep-orange', 'grey'];
      var colorSpectrum = ['500', 'A700', '600', '700', '800', '900', '300', '400', 'A200', 'A400'];
  
      angular.forEach($mdColorPalette, function (value, key) {
          angular.forEach(value, function (color, label) {
              if (colorSpectrum.indexOf(label) > -1) {
                  var rgb = 'rgb(' + color.value[0] + ',' + color.value[1] + ',' + color.value[2] + ')';
                  color = tinycolor(rgb);
                  var isDark = color.isDark();
                  var colorItem = {
                      value: color.toHexString(),
                      group: key,
                      label: label,
                      isDark: isDark
                  };
                  materialColors.push(colorItem);
              }
          });
      });
  
      materialColors.sort(function (colorItem1, colorItem2) {
          var spectrumIndex1 = colorSpectrum.indexOf(colorItem1.label);
          var spectrumIndex2 = colorSpectrum.indexOf(colorItem2.label);
          var result = spectrumIndex1 - spectrumIndex2;
          if (result === 0) {
              var paletteIndex1 = colorPalettes.indexOf(colorItem1.group);
              var paletteIndex2 = colorPalettes.indexOf(colorItem2.group);
              result = paletteIndex1 - paletteIndex2;
          }
          return result;
      });
  
      var defaultDataKey = {
          name: 'f(x)',
          type: types.dataKeyType.function,
          label: 'Sin',
          color: getMaterialColor(0),
          funcBody: getPredefinedFunctionBody('Sin'),
          settings: {},
          _hash: Math.random()
      };
  
      var defaultDatasource = {
          type: types.datasourceType.function,
          name: types.datasourceType.function,
          dataKeys: [angular.copy(defaultDataKey)]
      };
  
      var defaultAlarmFields = [
          types.alarmFields.createdTime.keyName,
          types.alarmFields.originator.keyName,
          types.alarmFields.type.keyName,
          types.alarmFields.severity.keyName,
          types.alarmFields.status.keyName
      ];
  
      var defaultAlarmDataKeys = [];
      for (var i=0;i<defaultAlarmFields.length;i++) {
          var name = defaultAlarmFields[i];
          var dataKey = {
              name: name,
              type: types.dataKeyType.alarm,
              label: $translate.instant(types.alarmFields[name].name)+'',
              color: getMaterialColor(i),
              settings: {},
              _hash: Math.random()
          };
          defaultAlarmDataKeys.push(dataKey);
      }
  
      var imageAspectMap = {};
  
      var service = {
          getDefaultDatasource: getDefaultDatasource,
          generateObjectFromJsonSchema: generateObjectFromJsonSchema,
          getDefaultDatasourceJson: getDefaultDatasourceJson,
          getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
          getMaterialColor: getMaterialColor,
          getMaterialIcons: getMaterialIcons,
          getCommonMaterialIcons: getCommonMaterialIcons,
          getPredefinedFunctionBody: getPredefinedFunctionBody,
          getPredefinedFunctionsList: getPredefinedFunctionsList,
          genMaterialColor: genMaterialColor,
          objectHashCode: objectHashCode,
          parseException: parseException,
          processWidgetException: processWidgetException,
          isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
          filterSearchTextEntities: filterSearchTextEntities,
          guid: guid,
          cleanCopy: cleanCopy,
          isLocalUrl: isLocalUrl,
          validateDatasources: validateDatasources,
          createKey: createKey,
          createLabelFromDatasource: createLabelFromDatasource,
          insertVariable: insertVariable,
          customTranslation: customTranslation,
          objToBase64: objToBase64,
          base64toObj: base64toObj,
          loadImageAspect: loadImageAspect
      }
  
      return service;
  
      function getPredefinedFunctionsList() {
          return predefinedFunctionsList;
      }
  
      function getPredefinedFunctionBody(func) {
          return predefinedFunctions[func];
      }
  
      function getMaterialColor(index) {
          var colorIndex = index % materialColors.length;
          return materialColors[colorIndex].value;
      }
  
      function getMaterialIcons() {
          var deferred = $q.defer();
          if (materialIcons.length) {
              deferred.resolve(materialIcons);
          } else {
              $timeout(function() {
                  var codepointsArray = materialIconsCodepoints.split("\n");
                  codepointsArray.forEach(function (codepoint) {
                      if (codepoint && codepoint.length) {
                          var values = codepoint.split(' ');
                          if (values && values.length == 2) {
                              materialIcons.push(values[0]);
                          }
                      }
                  });
                  deferred.resolve(materialIcons);
              });
          }
          return deferred.promise;
      }
  
      function getCommonMaterialIcons() {
          return commonMaterialIcons;
      }
  
      function genMaterialColor(str) {
          var hash = Math.abs(hashCode(str));
          return getMaterialColor(hash);
      }
  
      function hashCode(str) {
          var hash = 0;
          var i, char;
          if (str.length == 0) return hash;
          for (i = 0; i < str.length; i++) {
              char = str.charCodeAt(i);
              hash = ((hash << 5) - hash) + char;
              hash = hash & hash; // Convert to 32bit integer
          }
          return hash;
      }
  
      function objectHashCode(obj) {
          var hash = 0;
          if (obj) {
              var str = angular.toJson(obj);
              hash = hashCode(str);
          }
          return hash;
      }
  
      function parseException(exception, lineOffset) {
          var data = {};
          if (exception) {
              if (angular.isString(exception) || exception instanceof String) {
                  data.message = exception;
              } else {
                  if (exception.name) {
                      data.name = exception.name;
                  } else {
                      data.name = 'UnknownError';
                  }
                  if (exception.message) {
                      data.message = exception.message;
                  }
                  if (exception.lineNumber) {
                      data.lineNumber = exception.lineNumber;
                      if (exception.columnNumber) {
                          data.columnNumber = exception.columnNumber;
                      }
                  } else if (exception.stack) {
                      var lineInfoRegexp = /(.*<anonymous>):(\d*)(:)?(\d*)?/g;
                      var lineInfoGroups = lineInfoRegexp.exec(exception.stack);
                      if (lineInfoGroups != null && lineInfoGroups.length >= 3) {
                          if (angular.isUndefined(lineOffset)) {
                              lineOffset = -2;
                          }
                          data.lineNumber = Number(lineInfoGroups[2]) + lineOffset;
                          if (lineInfoGroups.length >= 5) {
                              data.columnNumber = lineInfoGroups[4];
                          }
                      }
                  }
              }
          }
          return data;
      }
  
      function processWidgetException(exception) {
          var parentScope = $window.parent.angular.element($window.frameElement).scope();
          var data = parseException(exception, -5);
          if ($rootScope.widgetEditMode) {
              parentScope.$emit('widgetException', data);
              parentScope.$apply();
          }
          return data;
      }
  
      function getDefaultDatasource(dataKeySchema) {
          var datasource = angular.copy(defaultDatasource);
          if (angular.isDefined(dataKeySchema)) {
              datasource.dataKeys[0].settings = generateObjectFromJsonSchema(dataKeySchema);
          }
          return datasource;
      }
  
      function generateObjectFromJsonSchema(schema) {
          var obj = jsonSchemaDefaults(schema);
          deleteNullProperties(obj);
          return obj;
      }
  
      function deleteNullProperties(obj) {
          if (angular.isUndefined(obj) || obj == null) {
              return;
          }
          for (var propName in obj) {
              if (obj[propName] === null || angular.isUndefined(obj[propName])) {
                  delete obj[propName];
              } else if (angular.isObject(obj[propName])) {
                  deleteNullProperties(obj[propName]);
              } else if (angular.isArray(obj[propName])) {
                  for (var i=0;i<obj[propName].length;i++) {
                      deleteNullProperties(obj[propName][i]);
                  }
              }
          }
      }
  
      function getDefaultDatasourceJson(dataKeySchema) {
          return angular.toJson(getDefaultDatasource(dataKeySchema));
      }
  
      function getDefaultAlarmDataKeys() {
          return angular.copy(defaultAlarmDataKeys);
      }
  
      function isDescriptorSchemaNotEmpty(descriptor) {
          if (descriptor && descriptor.schema && descriptor.schema.properties) {
              for(var prop in descriptor.schema.properties) {
                  if (descriptor.schema.properties.hasOwnProperty(prop)) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      function filterSearchTextEntities(entities, searchTextField, pageLink, deferred) {
          var response = {
              data: [],
              hasNext: false,
              nextPageLink: null
          };
          var limit = pageLink.limit;
          var textSearch = '';
          if (pageLink.textSearch) {
              textSearch = pageLink.textSearch.toLowerCase();
          }
  
          for (var i=0;i<entities.length;i++) {
              var entity = entities[i];
              var text = entity[searchTextField].toLowerCase();
              var createdTime = entity.createdTime;
              if (pageLink.textOffset && pageLink.textOffset.length > 0) {
                  var comparison = text.localeCompare(pageLink.textOffset);
                  if (comparison === 0
                      && createdTime < pageLink.createdTimeOffset) {
                      response.data.push(entity);
                      if (response.data.length === limit) {
                          break;
                      }
                  } else if (comparison > 0 && text.startsWith(textSearch)) {
                      response.data.push(entity);
                      if (response.data.length === limit) {
                          break;
                      }
                  }
              } else if (textSearch.length > 0) {
                  if (text.startsWith(textSearch)) {
                      response.data.push(entity);
                      if (response.data.length === limit) {
                          break;
                      }
                  }
              } else {
                  response.data.push(entity);
                  if (response.data.length === limit) {
                      break;
                  }
              }
          }
          if (response.data.length === limit) {
              var lastEntity = response.data[limit-1];
              response.nextPageLink = {
                  limit: pageLink.limit,
                  textSearch: textSearch,
                  idOffset: lastEntity.id.id,
                  createdTimeOffset: lastEntity.createdTime,
                  textOffset: lastEntity[searchTextField].toLowerCase()
              };
              response.hasNext = true;
          }
          deferred.resolve(response);
      }
  
      function guid() {
          function s4() {
              return Math.floor((1 + Math.random()) * 0x10000)
                  .toString(16)
                  .substring(1);
          }
          return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
              s4() + '-' + s4() + s4() + s4();
      }
  
      function cleanCopy(object) {
          var copy = angular.copy(object);
          for (var prop in copy) {
              if (prop && prop.startsWith('$$')) {
                  delete copy[prop];
              }
          }
          return copy;
      }
  
      function genNextColor(datasources) {
          var index = 0;
          if (datasources) {
              for (var i = 0; i < datasources.length; i++) {
                  var datasource = datasources[i];
                  index += datasource.dataKeys.length;
              }
          }
          return getMaterialColor(index);
      }
  
      function isLocalUrl(url) {
          var parser = document.createElement('a'); //eslint-disable-line
          parser.href = url;
          var host = parser.hostname;
          if (host === "localhost" || host === "127.0.0.1") {
              return true;
          } else {
              return false;
          }
      }
  
      function validateDatasources(datasources) {
          datasources.forEach(function (datasource) {
              if (datasource.type === 'device') {
                  datasource.type = types.datasourceType.entity;
                  datasource.entityType = types.entityType.device;
                  if (datasource.deviceId) {
                      datasource.entityId = datasource.deviceId;
                  } else if (datasource.deviceAliasId) {
                      datasource.entityAliasId = datasource.deviceAliasId;
                  }
                  if (datasource.deviceName) {
                      datasource.entityName = datasource.deviceName;
                  }
              }
              if (datasource.type === types.datasourceType.entity && datasource.entityId) {
                  datasource.name = datasource.entityName;
              }
          });
          return datasources;
      }
  
      function createKey(keyInfo, type, datasources) {
          var label;
          if (type === types.dataKeyType.alarm && !keyInfo.label) {
              var alarmField = types.alarmFields[keyInfo.name];
              if (alarmField) {
                  label = $translate.instant(alarmField.name)+'';
              }
          }
          if (!label) {
              label = keyInfo.label || keyInfo.name;
          }
          var dataKey = {
              name: keyInfo.name,
              type: type,
              label: label,
              funcBody: keyInfo.funcBody,
              settings: {},
              _hash: Math.random()
          }
          if (keyInfo.units) {
              dataKey.units = keyInfo.units;
          }
          if (angular.isDefined(keyInfo.decimals)) {
              dataKey.decimals = keyInfo.decimals;
          }
          if (keyInfo.color) {
              dataKey.color = keyInfo.color;
          } else {
              dataKey.color = genNextColor(datasources);
          }
          if (keyInfo.postFuncBody && keyInfo.postFuncBody.length) {
              dataKey.usePostProcessing = true;
              dataKey.postFuncBody = keyInfo.postFuncBody;
          }
          return dataKey;
      }
  
      function createLabelFromDatasource(datasource, pattern) {
          var label = angular.copy(pattern);
          var match = varsRegex.exec(pattern);
          while (match !== null) {
              var variable = match[0];
              var variableName = match[1];
              if (variableName === 'dsName') {
                  label = label.split(variable).join(datasource.name);
              } else if (variableName === 'entityName') {
                  label = label.split(variable).join(datasource.entityName);
              } else if (variableName === 'deviceName') {
                  label = label.split(variable).join(datasource.entityName);
              } else if (variableName === 'aliasName') {
                  label = label.split(variable).join(datasource.aliasName);
              }
              match = varsRegex.exec(pattern);
          }
          return label;
      }
  
      function insertVariable(pattern, name, value) {
          var result = angular.copy(pattern);
          var match = varsRegex.exec(pattern);
          while (match !== null) {
              var variable = match[0];
              var variableName = match[1];
              if (variableName === name) {
                  result = result.split(variable).join(value);
              }
              match = varsRegex.exec(pattern);
          }
          return result;
      }
  
      function customTranslation(translationValue, defaultValue) {
          var result = '';
          var translationId = types.translate.customTranslationsPrefix + translationValue;
          var translation = $translate.instant(translationId);
          if (translation != translationId) {
              result = translation + '';
          } else {
              result = defaultValue;
          }
          return result;
      }
  
      function objToBase64(obj) {
          var json = angular.toJson(obj);
          var encoded = utf8Encode(json);
          var b64Encoded = base64js.fromByteArray(encoded);
          return b64Encoded;
      }
  
      function base64toObj(b64Encoded) {
          var encoded = base64js.toByteArray(b64Encoded);
          var json = utf8Decode(encoded);
          var obj = angular.fromJson(json);
          return obj;
      }
  
      function loadImageAspect(imageUrl) {
          var deferred = $q.defer();
          if (imageUrl && imageUrl.length) {
              var urlHashCode = hashCode(imageUrl);
              var aspect = imageAspectMap[urlHashCode];
              if (angular.isUndefined(aspect)) {
                  var testImage = document.createElement('img'); // eslint-disable-line
                  testImage.style.position = 'absolute';
                  testImage.style.left = '-99999px';
                  testImage.style.top = '-99999px';
                  testImage.onload = function() {
                      aspect = testImage.width / testImage.height;
                      document.body.removeChild(testImage); //eslint-disable-line
                      imageAspectMap[urlHashCode] = aspect;
                      deferred.resolve(aspect);
                  };
                  testImage.onerror = function() {
                      aspect = 0;
                      imageAspectMap[urlHashCode] = aspect;
                      deferred.resolve(aspect);
                  };
                  document.body.appendChild(testImage); //eslint-disable-line
                  testImage.src = imageUrl;
              } else {
                  deferred.resolve(aspect);
              }
          } else {
              deferred.resolve(0);
          }
          return deferred.promise;
      }
  
  }
  