importPackage(Packages.de.elo.ix.client);

//@include lib_Class.js
//@include lib_sol.common.Cache.js
//@include lib_sol.common.UserUtils.js
//@include lib_sol.common.IxUtils.js
//@include lib_sol.common.jc.ActionHandler.js

/**
 * This class loads ELO Business Solutions action definitions and creates the ribbon, button groups and buttons.
 *
 * This class is automatically called during client startup.
 *
 * @author ELO Digital Office GmbH
 * @version 1.09.000
 *
 * @requires sol.common.IxUtils
 */
sol.define("sol.common.jc.ActionDefinitionUtils", {
  singleton: true,

  TAB_CONST: {
    HOME: { name: "home" },
    DOCUMENT: { name: "document" },
    ARCHIVE: { name: "archive" },
    VIEW: { name: "view" },
    WORKFLOW: { name: "workflow" },
    INTRAY: { name: "intray" },
    SEARCH: { name: "search" },
    NEW: { name: "new" },
    OUTPUT: { name: "output" },
    ORGANIZE: { name: "organize" }
  },

  BUTTON_MODES: {
    BIG: { name: "big", jcPrio: 1 },
    SMALL: { name: "small", jcPrio: 3 }
  },

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
  },

  /**
   * Initializes ribbon buttons, groups and tabs for ELO Business Solutions.
   *
   * If called with a context, the method will use the old JavaClient way to initialize all the ribbon buttons.
   * If called without a context, the method will use the new dynamic button registration.
   *
   * @param {Object} ctx (optional) The current script scope.
   */
  initializeRibbon: function (ctx) {
    var me = this,
        result, errorMessage;

    me.logJavaInfo();

    try {
      result = sol.common.IxUtils.execute("RF_sol_common_services_ActionDefinitionCollector", {});

      if (result && result.definitions) {
        me.jcVersion20OrLater = sol.common.IxUtils.checkVersion(workspace.getClientVersion(), "20.00.000");
        me.buildRibbonFromDefinition(result.definitions, ctx);
      }
    } catch (ex) {
      errorMessage = String(ex);
      me.logger.error("loading ribbon definition failed.", errorMessage);
    }
  },

  /**
   * @private
   * Logs the Java version and the path of the JRE
   */
  logJavaInfo: function () {
    var me = this,
        urlLoaderClassName = "com.sun.webkit.network.URLLoader",
        javaRuntimeVersion, bitness, javaVersion, urlLoaderClass, urlLoaderClassLocation, urlLoaderJarDate;

    javaRuntimeVersion = String(java.lang.System.getProperty("java.runtime.version"));
    bitness = String(java.lang.System.getProperty("sun.arch.data.model"));
    javaVersion = javaRuntimeVersion + " - " + bitness + " bit";
    me.logger.info("java.version=" + javaVersion);
    me.logger.info("java.home=" + java.lang.System.getProperty("java.home"));
    me.logger.info("java.ext.dirs=" + java.lang.System.getProperty("java.ext.dirs"));
    try {
      urlLoaderClass = java.lang.Class.forName(urlLoaderClassName);
      urlLoaderClassLocation = urlLoaderClass.protectionDomain.codeSource.location;
      urlLoaderJarDate = new Date(new java.io.File(urlLoaderClassLocation.toURI()).lastModified());
      me.logger.info(["Location of the class '{0}': {1} (last modified: {2})", urlLoaderClassName, urlLoaderClassLocation, urlLoaderJarDate]);
    } catch (ignore) {
    }
  },

  /**
   * @private
   * Builds the java client ribbon based on ribbon button / group / tab definitions given by RF_sol_common_services_ActionDefinitionCollector.
   * @param {Object[]} defs action/ ribbon definition
   * @param {Object} ctx
   */
  buildRibbonFromDefinition: function (defs, ctx) {
    var me = this,
        dynRibbon = {
          _tabs: {},
          tabs: [],
          _bands: {},
          bands: [],
          _buttons: {},
          buttons: []
        },
        def, i, j, rib,
        tabIdentifierValid, bandIdentifierValid, buttonIdentifierValid;

    for (i = 0; i < defs.length; i++) {
      def = defs[i];

      // legacy action definition - just one ribbon definition instead of an array
      if (def.ribbon) {
        def.ribbons = [def.ribbon];
      }

      if (def.ribbons) {
        for (j = 0; j < def.ribbons.length; j++) {
          rib = def.ribbons[j];
          tabIdentifierValid = me.isValidId("rib.ribbonTab.name", rib.ribbonTab.name);

          if (rib && rib.button && rib.ribbonTab && rib.buttongroup && tabIdentifierValid) {
            if (!dynRibbon._tabs[rib.ribbonTab.name]) {
              dynRibbon._tabs[rib.ribbonTab.name] = true;
              dynRibbon.tabs.push({
                name: rib.ribbonTab.name,
                text: rib.ribbonTab.text,
                position: rib.ribbonTab.position,
                access: rib.ribbonTab.access
              });
            }

            bandIdentifierValid = me.isValidId("rib.buttongroup.name", rib.buttongroup.name);
            if (!dynRibbon._bands[rib.buttongroup.name] && bandIdentifierValid) {
              dynRibbon._bands[rib.buttongroup.name] = true;
              dynRibbon.bands.push({
                ribbonTab: rib.ribbonTab,
                name: rib.buttongroup.name,
                text: rib.buttongroup.text,
                position: rib.buttongroup.position
              });
            }

            buttonIdentifierValid = me.isValidId("rib.button.name", rib.button.name);
            if (buttonIdentifierValid) {
              if (!dynRibbon._buttons[rib.button.name]) {
                dynRibbon._buttons[rib.button.name] = {
                  type: def.type,
                  action: def.action,
                  button: rib.button,
                  additionalButtonPositions: [],
                  ribbonTab: rib.ribbonTab,
                  buttongroup: rib.buttongroup
                };
                dynRibbon.buttons.push(dynRibbon._buttons[rib.button.name]);
              } else {
                dynRibbon._buttons[rib.button.name].additionalButtonPositions.push({ ribbonTab: rib.ribbonTab, buttongroupName: rib.buttongroup.name });
              }
            }
          }
        }
      }
    }

    me.registerTabs(dynRibbon.tabs, ctx);
    me.registerBands(dynRibbon.bands, ctx);
    me.registerButtons(dynRibbon.buttons, ctx);
  },

  /**
   * @private
   * Registers the tabs.
   * @param {Object[]} tabDefinitions This contains one action definitions per tab which was found
   * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
   */
  registerTabs: function (tabDefinitions, ctx) {
    var me = this,
        tabsArray = [];

    if (ctx) {
      tabDefinitions.forEach(function (def) {
        if (!me.TAB_CONST[def.name]) {
          tabsArray.push(def.position + "," + def.text);
        }
      });

      ctx.getExtraTabs = function () {
        return tabsArray.join(";");
      };
    } else {
      tabDefinitions.forEach(function (def) {
        var tab;
        if (!me.TAB_CONST[def.name]) {
          tab = ribbon.addTab(def.position, null, def.name);
          tab.setTitle(def.text);
          if (def.access && (me.jcVersion20OrLater === true)) {
            tab.setVisibleCallback(function () {
              return me.buttonEnabledHandler(def.access, def.name);
            }, me);
          }
        }
      });
    }
  },

  /**
   * @private
   * Registers the buton groups.
   * @param {Object[]} bandDefinitions This contains one action definitions per button group which was found
   * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
   */
  registerBands: function (bandDefinitions, ctx) {
    var me = this,
        bandsArray = [];

    if (ctx) {
      bandDefinitions.forEach(function (def) {
        var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;
        bandsArray.push(tabDef.name + "," + def.position + "," + def.text);
      });

      ctx.getExtraBands = function () {
        return bandsArray.join(";");
      };
    } else {
      bandDefinitions.forEach(function (def) {
        var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;
        ribbon.addBand(tabDef.name, def.position, def.name).setTitle(def.text);
      });
    }
  },

  /**
   * @private
   * Registers the butons.
   * @param {Object[]} buttonDefinitions This contains one action definitions per button which was found
   * @param {Object} ctx (optional) Whether this is set or not, this method will use the old or the new way to register tabs in the client (see {@link #initializeRibbon})
   */
  registerButtons: function (buttonDefinitions, ctx) {
    var me = this;

    buttonDefinitions.sort(function (def1, def2) {
      return def1.button.position - def2.button.position;
    });

    if (ctx) {
      me.registerButtonsOld(buttonDefinitions, ctx);
    } else {
      me.registerButtonsNew(buttonDefinitions);
    }

  },

  /**
   * @private
   * Registers the butons in the old fashion.
   * @param {Object[]} buttonDefinitions This contains one action definitions per button group which was found
   * @param {Object} ctx
   */
  registerButtonsOld: function (buttonDefinitions, ctx) {
    var me = this,
        buttonPositions = [];
    buttonDefinitions.forEach(function (def) {
      var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab;

      buttonPositions.push(def.button.jc.buttonId + "," + tabDef.name + "," + def.buttongroup.text + "," + me.getPriority(def));

      me.buttonEnabledStatic(def.button.jc.buttonId, def.button.access);

      ctx["eloScriptButton" + def.button.jc.buttonId + "Start"] = function () {
        me.buttonHandler(def);
      };

      ctx["getScriptButton" + def.button.jc.buttonId + "Name"] = function () {
        return def.button.text;
      };

      ctx["getScriptButton" + def.button.jc.buttonId + "Tooltip"] = function () {
        return def.button.tooltipText;
      };

    });

    ctx.getScriptButtonPositions = function () {
      return buttonPositions.join(";");
    };
  },

  /**
   * @private
   * Registers the butons in the new dynamic way.
   * @param {Object[]} buttonDefinitions This contains one action definitions per button group which was found
   */
  registerButtonsNew: function (buttonDefinitions) {
    var me = this,
        abp;

    buttonDefinitions.forEach(function (def) {
      var tabDef = me.TAB_CONST[def.ribbonTab.name] || def.ribbonTab,
          btn = ribbon.addButton(tabDef.name, def.buttongroup.name, def.button.name),
          imageName = me.getIconName(def.button), i;

      if (def.additionalButtonPositions && def.additionalButtonPositions.length > 0) {
        if (typeof btn.addPosition == "function") {
          for (i = 0; i < def.additionalButtonPositions.length; i++) {
            abp = def.additionalButtonPositions[i];
            tabDef = me.TAB_CONST[abp.ribbonTab.name] || abp.ribbonTab;
            btn.addPosition(tabDef.name, abp.buttongroupName);
          }
        } else {
          me.logger.error("ActionDef Button: " + def.button.name + " - additionalButtonPositions found. This feature is not supported by this client. Please update to a newer version.");
        }
      }

      btn.setCallback(function () {
        me.buttonHandler(def);
      }, me);
      btn.setTitle(def.button.text);
      btn.setTooltip(def.button.tooltipText);
      btn.setIconName(imageName);
      btn.asTile(def.button.asTile === true);
      btn.setTileIconName(imageName);
      btn.setPriority(me.getPriority(def));

      if (def.button.access && def.button.access.solTypes) {
        btn.setEnabledCallback(function () {
          return me.buttonEnabledHandler(def.button.access, def.button.name);
        }, me);
      } else {
        me.buttonEnabledStatic(btn.fctNr - 9000, def.button.access);
      }

      if (btn.setDefaultPinState) {
        btn.setDefaultPinState(!!def.button.pinned);
      }
    });
  },

  /**
   * @private
   * @param {String} idType Identifier type
   * @param {String} id Identifier
   * @return {Boolean} is valid
   */
  isValidId: function (idType, id) {
    var me = this,
        valid = true,
        idRegEx;

    idType = (idType || "ID") + "";
    id += "";

    idRegEx = "^[0-9a-zA-Z_-]*$";

    if (!id) {
      me.logger.warn("ID is missing: idType=" + idType);
      valid = false;
    } else if (!id.match(idRegEx)) {
      me.logger.warn("ID contains illegal characters: idType=" + idType + ", id=" + id + ", idRegEx=" + idRegEx);
      valid = false;
    }

    return valid;
  },

  /**
   * @private
   * @param {Object} buttonDef
   * @return {String}
   */
  getIconName: function (buttonDef) {
    var me = this;
    if (buttonDef && buttonDef.iconName && (me.jcVersion20OrLater === true)) {
      return buttonDef.iconName;
    }
    return "ScriptButton" + buttonDef.jc.buttonId;
  },

  /**
   * @private
   * Retrieves the priority from a button definition.
   * @param {Object} btnDef
   * @return {Number}
   */
  getPriority: function (btnDef) {
    var me = this;
    return (btnDef.buttongroup.mode === me.BUTTON_MODES.SMALL.name) ? me.BUTTON_MODES.SMALL.jcPrio : me.BUTTON_MODES.BIG.jcPrio;
  },

  /**
   * @private
   * Checks, when the button should be enabled, and calls the `setScriptButtonEnabled` method accordingly.
   * This is used by the old way of registering buttons and only while the button is initialized.
   * @param {String} btnId A button id
   * @param {Object} access A button access definition
   */
  buttonEnabledStatic: function (btnId, access) {
    if (access && (access.document || access.folder || access.multi)) {
      workspace.setScriptButtonEnabled(
        btnId,
        access.document === true,
        access.folder === true,
        false,
        false,
        false,
        access.multi === true
      );
    }
  },

  /**
   * @private
   * Checks, if the button should be enabled. This is used by the the new way of registering buttons and will be called dynamically.
   * @param {Object} access The access element of a button definition
   * @return {Boolean}
   */
  buttonEnabledHandler: function (access, targetName) {
    var me = this,
        activeView, activeNavBarSelection, checkElement, selectionCount, firstSelected, allSelected, current, i;

    // if there is no access definition (or it has no content) the element should always be enabled
    if (!access || (!access.document && !access.folder && !access.multi && !access.solTypes)) { // nothing set, button will always be enabled
      return true;
    }

    try {
      activeView = workspace.activeView;
    } catch (ex) {
      me.logger.warn("Active view not available: " + ex);
      return false;
    }

    activeNavBarSelection = workspace.workspace.navigationBar && workspace.workspace.navigationBar.active && workspace.workspace.navigationBar.active.selectedIds;

    // function to check a single indexed element
    checkElement = function (e) {
      var solType, fieldValue, conditions, visible = false;

      if (!e) {
        return false;
      }

      if ((access.document !== true || access.folder !== true) && ((access.document === true && !e.isDocument()) || (access.folder === true && !e.isStructure()))) {
        return false;
      }

      if (!access.solTypes || (access.solTypes.length <= 0)) {
        return true;
      }

      try {
        solType = e.getObjKeyValue("SOL_TYPE") + "";

        if (solType != "" && access.solTypes.indexOf(solType) !== -1) {
          conditions = access.conditions;
          if (conditions && Array.isArray(conditions)) {
            // we're checking if one of our filter criterias is not
            // true. This is when our some-function returns true
            // (and we're inverting this for the 'visible' variable)
            visible = !conditions.some(function (condition) {
              if (condition.key && condition.value) {
                try {
                  fieldValue = e.getObjKeyValue(condition.key) + "";
                  return fieldValue != condition.value;
                } catch (ex) {
                  me.logger.warn(["Button/Tab access check failed. Condition Field {0} does not exist in mask of clicked object {1}", condition.key, e.name], targetName);
                  return true;
                }
              } else {
                me.logger.error("Button/Tab access check failed. Bad condition configuration.", targetName);
                return true;
              }
            });
          } else {
            visible = true;
          }
        }
      } catch (ex) {
        // can not get value from the SOL_TYPE field
        visible = false;
      }
      return visible;
    };

    selectionCount = (activeView) ? activeView.selectionCount : ((activeNavBarSelection) ? activeNavBarSelection.length : 0);

    if (selectionCount <= 0) {
      return false; // nothing selected
    }

    // check single access
    if (access.multi !== true) {
      if (selectionCount !== 1) {
        return false;
      }
      firstSelected = (activeView) ? activeView.firstSelected : ((activeNavBarSelection) ? archive.getElement(workspace.workspace.navigationBar.active.selectedIds[0]) : null);
      return checkElement(firstSelected);
    }

    // check multi select
    if (activeView && activeView.allSelected) { // for normal views
      allSelected = activeView.allSelected;
      while (allSelected.hasMoreElements()) {
        current = allSelected.nextElement();
        if (!checkElement(current)) {
          return false;
        }
      }
    } else if (activeNavBarSelection) { // for AppViews, because those do not have an active view in 9.03
      allSelected = workspace.workspace.navigationBar.active.selectedIds; // do not try this at home
      for (i = 0; i < allSelected.length; i++) {
        current = allSelected[i];
        if (!checkElement(archive.getElement(current))) {
          return false;
        }
      }
    } else {
      return false;
    }

    return true;
  },

  /**
   * This handles the button execution. This is used by the old and the new way of registering buttons alike.
   * @param {Object} def A button definition
   */
  buttonHandler: function (def) {
    var me = this,
        objId = me.getSelection(def.button.access);

    switch (def.type) {
      case "ADVANCED_ACTION":
        sol.common.jc.ActionHelper.executeAdvancedAction(objId, def.action);
        break;
      case "SIMPLE_ACTION":
        sol.common.jc.ActionHelper.executeSimpleAction(objId, def.action);
        break;
      default:
        me.logger.error("No handler found for button definition", def.button.name);
    }
  },

  /**
   * @private
   * returns the selected element objIds.
   * @param {Object} access This object determines if this returns just one or many objIds (in case of multiselect).
   * @return {String|String[]}
   */
  getSelection: function (access) {
    var allSelected, selected, objIds;

    if (workspace.activeView && workspace.activeView.allSelectedArchiveElements) {
      allSelected = workspace.activeView.allSelectedArchiveElements;
      objIds = [];
      while (allSelected.hasMoreElements()) {
        selected = allSelected.nextElement();
        if (selected.id) {
          objIds.push(selected.id);
        }
      }
    } else if (workspace.workspace.navigationBar && workspace.workspace.navigationBar.active && workspace.workspace.navigationBar.active.selectedIds) {

      // FIXME as soon as the JC supports the retrival of selected elements from "AppViews" this should use the official API

      allSelected = workspace.workspace.navigationBar.active.selectedIds; // do not try this at home
      objIds = allSelected.map(function (objId) {
        return objId;
      }); // convert to JavaScript array
    }

    if (access && (access.multi === true)) {
      return (objIds && objIds.length > 0) ? objIds : null;
    } else {
      return (objIds && objIds.length > 0) ? objIds[0] : null;
    }
  }

});

// This will be used by ELO10 JavaClients //
function eloExpandRibbon() {
  if (typeof ribbon !== "undefined") {
    sol.common.jc.ActionDefinitionUtils.initializeRibbon();
  }
}

// This is the fallback for older JavaClients //
(function (ctx) {
  if (typeof ribbon === "undefined") {
    sol.common.jc.ActionDefinitionUtils.initializeRibbon(ctx);
  }
}(this));