angular
  .module("app")
  .directive("propertyGrid", function ($rootScope, $timeout, SQConstants, L) {
    return {
      restrict: "E",
      replace: true,
      scope: {
        instance: "=",
        afterInit: "=",
        onChange: "=",
        noteOnSide: "=",
      },
      templateUrl: "../app/directives/propertyGrid/propertyGrid.html",
      link: function (scope, element, attrs) {
        function printNote(note) {
          if (note && note != "Null") {
            scope.note = note;
          } else {
            scope.note = "";
          }
        }

        scope.instance.printNote = function (note) {
          printNote(note);
        };

        scope.propertyClick = function (property) {
          printNote(property.note);
        };

        scope.toggleCategory = function (category) {
          category.expanded = !category.expanded;
        };

        scope.shouldShowInput = function (property) {
          if (
            property.dataType == scope.dataTypes.int ||
            property.dataType == scope.dataTypes.double ||
            property.dataType == scope.dataTypes.str
          ) {
            return true;
          }
        };

        scope.shouldShowSelect = function (property) {
          if (
            property.dataType == scope.dataTypes.intList ||
            property.dataType == scope.dataTypes.strList
          ) {
            for (var j = 0; j < scope.properties.length; j++) {
              if (scope.properties[j].$$hashKey == property.$$hashKey) {
                scope.properties[j].display = property.value;
                for (var i = 0; i < property.options.length; i++) {
                  if (property.options[i].name == property.value) {
                    scope.properties[j].display = property.options[i].value;
                    break;
                  }
                }
                break;
              }
            }
            return true;
          }
        };

        scope.shouldShowInput = function (property) {
          if (
            property.dataType == scope.dataTypes.int ||
            property.dataType == scope.dataTypes.double ||
            property.dataType == scope.dataTypes.str
          ) {
            return true;
          }
        };

        scope.shouldShowCheckbox = function (property) {
          return property.dataType == scope.dataTypes.boolean;
        };

        scope.shouldShowTimeSpinner = function (property) {
          return property.dataType == scope.dataTypes.time;
        };

        scope.checkDisabled = function (property) {
          if (!property.activator) return false;

          var activatorProperty = getItem(
            scope.properties,
            "key",
            property.activator
          );
          return (
            !activatorProperty ||
            activatorProperty.value === false ||
            activatorProperty.value === "false"
          );
        };

        scope.checkHidden = function (property) {
          return property.hidden === true;
        };

        scope.checkHiddenCategory = function (category) {
          var optionsToDisplay = 0;

          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            if (property.category == category.name) {
              if (!property.hidden) {
                optionsToDisplay++;
              }
            }
          }

          return optionsToDisplay == 0;
        };

        scope.onInputChange = function (property, oldValue) {
          switch (property.dataType) {
            case scope.dataTypes.int:
              if (!checkIntegerValue("" + property.value)) {
                $rootScope.showError(L.tsq("Expected integer value"));
                property.value = parseInt(oldValue);
              }
              break;
            case scope.dataTypes.double:
              if (!checkDoubleValue("" + property.value)) {
                $rootScope.showError(L.tsq("Expected numeric value"));
                property.value = parseFloat(oldValue);
              }
              break;
          }
        };

        scope.getPropertyNumberType = function (property) {
          switch (property.dataType) {
            case scope.dataTypes.int:
              return "int";
            case scope.dataTypes.double:
              return "double";
            default:
              return "text";
          }
        };

        function getValues() {
          var data = [];

          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            var item = {
              key: property.key,
              dataType: property.dataType,
              value: property.value,
            };

            if (property.hasOwnProperty("options")) {
              item.options = property.options;
            }

            data.push(item);
          }

          return data;
        }

        function getType(dataType) {
          switch (parseInt(dataType)) {
            case scope.dataTypes.int:
              return "Integer";
            case scope.dataTypes.double:
              return "Double";
            case scope.dataTypes.boolean:
              return "Boolean";
            case scope.dataTypes.time:
              return "Time";
            default:
              return "String";
          }
        }

        function addCategory(name, position) {
          scope.categories.push({
            name: name,
            position: position,
            expanded: true,
          });
        }

        function addNumericProperty(
          key,
          name,
          dataType,
          category,
          value,
          min,
          max,
          step,
          note,
          className,
          activator
        ) {
          scope.properties.push({
            key: key,
            name: name,
            type: "number",
            dataType: dataType,
            category: category,
            value: value,
            min: min,
            max: max,
            step: step,
            note: note,
            className: className,
            activator: activator,
          });
        }

        function addStringProperty(
          key,
          name,
          dataType,
          category,
          value,
          note,
          className,
          activator
        ) {
          scope.properties.push({
            key: key,
            name: name,
            type: "text",
            dataType: dataType,
            category: category,
            value: value,
            note: note,
            className: className,
            activator: activator,
          });
        }

        function addBooleanProperty(
          key,
          name,
          dataType,
          category,
          value,
          note,
          className,
          activator
        ) {
          scope.properties.push({
            key: key,
            name: name,
            type: "boolean",
            dataType: dataType,
            category: category,
            value: value,
            note: note,
            className: className,
            activator: activator,
          });
        }

        function addTimeProperty(
          key,
          name,
          dataType,
          category,
          value,
          note,
          className,
          activator
        ) {
          scope.properties.push({
            key: key,
            name: name,
            type: "time",
            dataType: dataType,
            category: category,
            value: value,
            note: note,
            className: className,
            activator: activator,
          });
        }

        function addListProperty(
          key,
          name,
          dataType,
          category,
          value,
          options,
          note,
          className,
          activator
        ) {
          scope.properties.push({
            key: key,
            name: name,
            dataType: dataType,
            category: category,
            value: value,
            options: options,
            note: note,
            className: className,
            activator: activator,
          });
        }

        function setValue(key, value) {
          for (var i = 0; i < scope.properties.length; i++) {
            if (scope.properties[i].key == key) {
              var dataType = scope.properties[i].dataType;

              if (
                dataType == scope.dataTypes.int ||
                dataType == scope.dataTypes.time
              ) {
                scope.properties[i].value = parseInt(value);
              } else if (dataType == scope.dataTypes.double) {
                scope.properties[i].value = parseFloat(value);
              } else if (dataType == scope.dataTypes.boolean) {
                if (typeof value === "boolean") {
                  scope.properties[i].value = value;
                } else {
                  scope.properties[i].value = value == "true";
                }
              } else if (
                dataType == scope.dataTypes.strList &&
                key == "Session"
              ) {
                var item = getItem(scope.properties[i].options, "value", value);

                if (item) {
                  scope.properties[i].value = value;
                } else if (scope.properties[i].options.length > 0) {
                  scope.properties[i].value =
                    scope.properties[i].options[0].value;
                }
              } else {
                scope.properties[i].value = value;
              }

              try {
                scope.$digest();
              } catch (err) {}
              return;
            }
          }
          console.error(
            "Cannot set value. Parameter with key '" + key + "' not found"
          );
        }

        //- Event Handlers --------------------------------------------------------------

        var unregisterWatch = scope.$watch(
          "properties",
          function (newValue, oldValue) {
            for (var i = 0; i < newValue.length; i++) {
              if (newValue[i].type == "time" && !("timeValue" in newValue[i])) {
                newValue[i].timeValue = sqnGetTime(newValue[i].value);
              }
            }
            if (loaded) {
              if (oldValue != newValue && scope.onChange) {
                scope.onChange(getValues());
              }
            } else
              $timeout(
                function () {
                  loaded = true;
                },
                0,
                false
              );

            $timeout(
              function () {
                $('.property-grid [data-toggle="tooltip"]').tooltip({
                  trigger: "hover",
                  template:
                    '<div class="tooltip" role="tooltip"><div class="arrow"></div><div class="tooltip-inner smaller-left"></div></div>',
                });
              },
              0,
              false
            );
          },
          true
        );

        //- Directive instance functions ------------------------------------------------

        scope.instance.addNumericProperty = function (
          key,
          name,
          dataType,
          category,
          value,
          min,
          max,
          step,
          note,
          className
        ) {
          addNumericProperty(
            key,
            name,
            dataType,
            category,
            value,
            min,
            max,
            step,
            note,
            className
          );
        };

        scope.instance.addStringProperty = function (
          key,
          name,
          dataType,
          category,
          value,
          note,
          className
        ) {
          addStringProperty(
            key,
            name,
            dataType,
            category,
            value,
            note,
            className
          );
        };

        scope.instance.addBooleanProperty = function (
          key,
          name,
          dataType,
          category,
          value,
          note,
          className
        ) {
          addBooleanProperty(
            key,
            name,
            dataType,
            category,
            value,
            note,
            className
          );
        };

        scope.instance.addTimeProperty = function (
          key,
          name,
          dataType,
          category,
          value,
          note,
          className
        ) {
          addTimeProperty(
            key,
            name,
            dataType,
            category,
            value,
            note,
            className
          );
        };

        scope.instance.addListProperty = function (
          key,
          name,
          dataType,
          category,
          value,
          options,
          note,
          className
        ) {
          addListProperty(
            key,
            name,
            dataType,
            category,
            value,
            options,
            note,
            className
          );
        };

        scope.instance.addCategory = function (name, position) {
          addCategory(name, position);
        };

        scope.instance.setProperties = function (data) {
          console.log("Creating property grid...", data);

          if (data.categories) {
            for (var i = 0; i < data.categories.length; i++) {
              var category = data.categories[i];
              addCategory(category.name, category.position);
            }

            if (data.properties) {
              for (var i = 0; i < data.properties.length; i++) {
                var property = data.properties[i];
                scope.properties.push(property);
              }
            } else {
              $rootScope.showError(
                L.tsq("Property grid error. No properties found")
              );
            }
          } else {
            $rootScope.showError(
              L.tsq("Property grid error. No categories found")
            );
          }
        };

        scope.instance.createFromXML = function (xmlConfig, callback) {
          var xmlObj = xmlToObject(xmlConfig);
          var paramObjs = xmlObj.find("Param");

          loaded = false;
          scope.instance.clearAll();

          if (paramObjs) {
            for (var p = 0; p < paramObjs.length; p++) {
              var paramObj = paramObjs[p];
              var categoryName =
                correctAttrValue(paramObj.attributes["category"]) || "Default";

              if (!getItem(scope.categories, "name", categoryName))
                addCategory(
                  categoryName,
                  categoryName == "Default" ? -1 : scope.categories.length
                );

              var name = correctAttrValue(paramObj.attributes["name"]);
              var key = correctAttrValue(paramObj.attributes["key"]);
              var dataType = parseInt(
                correctAttrValue(paramObj.attributes["dataType"])
              );
              var value = correctAttrValue(paramObj.attributes["value"] || "");
              var note = correctAttrValue(paramObj.attributes["description"]);
              var className = correctAttrValue(
                paramObj.attributes["className"]
              );
              var activator = correctAttrValue(
                paramObj.attributes["activator"]
              );

              switch (dataType) {
                case scope.dataTypes.int:
                case scope.dataTypes.double:
                  var min = correctAttrValue(paramObj.attributes["min"]);
                  var max = correctAttrValue(paramObj.attributes["max"]);
                  var step = correctAttrValue(paramObj.attributes["step"]);
                  
                  addNumericProperty(
                    key,
                    name,
                    dataType,
                    categoryName,
                    parseFloat(value),
                    min,
                    max,
                    step,
                    note,
                    className,
                    activator
                  );
                  break;

                case scope.dataTypes.str:
                  addStringProperty(
                    key,
                    name,
                    dataType,
                    categoryName,
                    value,
                    note,
                    className,
                    activator
                  );
                  break;

                case scope.dataTypes.boolean:
                  addBooleanProperty(
                    key,
                    name,
                    dataType,
                    categoryName,
                    (value && value.toLowerCase() === 'true'),
                    note,
                    className,
                    activator
                  );
                  break;

                case scope.dataTypes.time:
                  addTimeProperty(
                    key,
                    name,
                    dataType,
                    categoryName,
                    value,
                    note,
                    className,
                    activator
                  );
                  break;

                case scope.dataTypes.strList:
                  var options = [];
                  var optionsObj = getChildElements(paramObj, "Options")[0];
                  if (optionsObj) {
                    var optionObjs = getChildElements(optionsObj, "Option");
                    if (optionObjs && optionObjs.length > 0) {
                      for (var o = 0; o < optionObjs.length; o++) {
                        var optionObj = optionObjs[o];
                        if (optionObj.childNodes[0]) {
                          options.push({
                            name: correctAttrValue(
                              optionObj.attributes["name"]
                            ),
                            value: optionObj.childNodes[0].nodeValue,
                          });
                        } else {
                          console.error(
                            "Cannot get property's option from xml"
                          );
                        }
                      }
                    }

                    if (key == "Session" || key == "MarketOpenSession") {
                      options.sort(function (x, y) {
                        return x.value == "No Session"
                          ? -1
                          : y.value == "No Session"
                          ? 1
                          : 0;
                      });
                    }

                    addListProperty(
                      key,
                      name,
                      dataType,
                      categoryName,
                      value,
                      options,
                      note,
                      className,
                      activator
                    );
                  } else {
                    $rootScope.showError(
                      L.tsq(
                        "Properties error. Cannot get options for property '%s'",
                        [name]
                      )
                    );
                  }
                  break;

                default:
                  $rootScope.showError(
                    L.tsq("Properties error. Unknown data type '%s'", [
                      dataType,
                    ])
                  );
              }
            }
          }

          loaded = true;

          try {
            scope.$digest();
          } catch (err) {}

          if (callback) {
            callback();
          }
        };

        scope.instance.setValue = function (key, value) {
          setValue(key, value);
        };

        scope.instance.setValues = function (params, callback) {
          if (!params) {
            scope.onChange(getValues());

            return;
          }

          for (var i = 0; i < params.length; i++) {
            var param = params[i];

            setValue(param.key, param.value);
          }
        };

        scope.instance.setPropertyByKey = function (key, data) {
          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            if (property.key == key) {
              scope.properties[i] = data;
              return;
            }
          }
        };

        scope.instance.clearAll = function () {
          scope.categories = [];
          scope.properties = [];
        };

        scope.instance.getValues = function () {
          return getValues();
        };

        scope.instance.getValuesXml = function (xmlDoc, methodObj) {
          removeAllChildren(methodObj);

          var paramsObj = createChild(methodObj, "Params", xmlDoc);

          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            var type = getType(property.dataType);
            if (!type) {
              $rootScope.showError(
                L.tsq(
                  "Error saving settings. Cannot get parameter type of dataType '%s'",
                  [property.dataType]
                )
              );
              return;
            }

            var paramObj = createChild(paramsObj, "Param", xmlDoc, true);
            paramObj.setAttribute("key", property.key);
            if (property.className) {
              paramObj.setAttribute("className", property.className);
            }
            paramObj.appendChild(xmlDoc.createTextNode(property.value));
          }
        };

        scope.instance.getPropertyByKey = function (key) {
          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            if (property.key == key) {
              return property;
            }
          }
        };

        scope.instance.getPropertyValueByKey = function (key) {
          for (var i = 0; i < scope.properties.length; i++) {
            var property = scope.properties[i];

            if (property.key == key) {
              return property.value;
            }
          }
        };

        scope.instance.getProperties = function () {
          return scope.properties;
        };

        scope.instance.printValueByConfig = function (xmlConfig, key, value) {
          var xmlObj = xmlToObject(xmlConfig);
          var paramObjs = xmlObj.find("Param");

          if (!paramObjs) {
            return value;
          }

          for (var p = 0; p < paramObjs.length; p++) {
            var paramObj = paramObjs[p];

            var paramKey = correctAttrValue(paramObj.attributes["key"]);
            var dataType = parseInt(
              correctAttrValue(paramObj.attributes["dataType"])
            );

            if (paramKey != key) {
              continue;
            }

            switch (dataType) {
              case scope.dataTypes.int:
                return parseInt(value);

              case scope.dataTypes.double:
                return parseFloat(value);

              case scope.dataTypes.str:
                return value;

              case scope.dataTypes.strList:
                var optionsObj = getChildElements(paramObj, "Options")[0];
                if (optionsObj) {
                  var optionObjs = getChildElements(optionsObj, "Option");
                  if (optionObjs && optionObjs.length > 0) {
                    for (var o = 0; o < optionObjs.length; o++) {
                      var optionObj = optionObjs[o];
                      if (optionObj.childNodes[0]) {
                        var optionName = correctAttrValue(
                          optionObj.attributes["name"]
                        );
                        var optionValue = optionObj.childNodes[0].nodeValue;

                        if (optionName == value) {
                          return optionValue;
                        }
                      }
                    }
                  }
                }

                break;
            }
          }

          return value;
        };

        //this method is used by SettingsTable where we need to get the xml with values without initializing the property grid
        scope.instance.getValuesXmlByConfig = function (
          xmlConfig,
          values,
          xmlDoc,
          methodObj
        ) {
          var xmlObj = xmlToObject(xmlConfig);
          var paramObjs = xmlObj.find("Param");

          if (!paramObjs) {
            return value;
          }

          var paramsElem = createChild(methodObj, "Params", xmlDoc);

          for (var p = 0; p < paramObjs.length; p++) {
            var paramObj = paramObjs[p];
            var key = correctAttrValue(paramObj.attributes["key"]);
            var dataType = parseInt(
              correctAttrValue(paramObj.attributes["dataType"])
            );
            var value = correctAttrValue(paramObj.attributes["value"]);

            var parameterElem = createChild(paramsElem, "Param", xmlDoc, true);
            var type = getType(dataType);

            for (var v = 0; v < values.length; v++) {
              if (values[v].key == key) {
                value = values[v].value;

                break;
              }
            }

            parameterElem.setAttribute("key", key);
            parameterElem.setAttribute("type", type);
            setNodeValue(parameterElem, value, xmlDoc);
          }
        };

        scope.enabled = true;
        scope.instance.setEnabled = function (enabled) {
          scope.enabled = enabled;
        };

        //- Initialization --------------------------------------------------------------

        var loaded = false;

        scope.dataTypes = SQConstants.getConstants().propGridTypes;
        scope.categories = [];
        scope.properties = [];

        if (scope.afterInit) {
          $timeout(scope.afterInit, 0, false);
        }
      },
    };
  });
