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 PZ, ELO Digital Office GmbH
 * @version 1.03.000
 *
 * @requires sol.common.IxUtils
 */
sol.define("sol.common.jc.ActionDefinitionUtils", {
  singleton: true,

  TAB_CONST: {
    HOME: { text: "home" },
    DOCUMENT: { text: "document" },
    ARCHIVE: { text: "archive" },
    VIEW: { text: "view" },
    WORKFLOW: { text: "workflow" },
    INTRAY: { text: "intray" },
    SEARCH: { text: "search" }
  },

  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.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;

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

      if (def.ribbon && def.ribbon.button && def.ribbon.ribbonTab && def.ribbon.buttongroup) {
        if (!dynRibbon._tabs[def.ribbon.ribbonTab.name]) {
          dynRibbon._tabs[def.ribbon.ribbonTab.name] = def;
          dynRibbon.tabs.push(dynRibbon._tabs[def.ribbon.ribbonTab.name]);
        }

        if (!dynRibbon._bands[def.ribbon.buttongroup.name]) {
          dynRibbon._bands[def.ribbon.buttongroup.name] = def;
          dynRibbon.bands.push(dynRibbon._bands[def.ribbon.buttongroup.name]);
        }

        if (!dynRibbon._buttons[def.ribbon.button.name]) {
          dynRibbon._buttons[def.ribbon.button.name] = def;
          dynRibbon.buttons.push(dynRibbon._buttons[def.ribbon.button.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.ribbon.ribbonTab.name]) {
          tabsArray.push(def.ribbon.ribbonTab.position + "," + def.ribbon.ribbonTab.text);
        }
      });

      ctx.getExtraTabs = function () {
        return tabsArray.join(";");
      };
    } else {
      tabDefinitions.forEach(function (def) {
        if (!me.TAB_CONST[def.ribbon.ribbonTab.name]) {
          ribbon.addTab(def.ribbon.ribbonTab.position, null, def.ribbon.ribbonTab.text).setTitle(def.ribbon.ribbonTab.text);
        }
      });
    }
  },

  /**
   * @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.ribbon.ribbonTab.name] || def.ribbon.ribbonTab;
        bandsArray.push(tabDef.text + "," + def.ribbon.buttongroup.position + "," + def.ribbon.buttongroup.text);
      });

      ctx.getExtraBands = function () {
        return bandsArray.join(";");
      };
    } else {
      bandDefinitions.forEach(function (def) {
        var tabDef = me.TAB_CONST[def.ribbon.ribbonTab.name] || def.ribbon.ribbonTab;
        ribbon.addBand(tabDef.text, def.ribbon.buttongroup.position, def.ribbon.buttongroup.text).setTitle(def.ribbon.buttongroup.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.ribbon.button.position - def2.ribbon.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.ribbon.ribbonTab.name] || def.ribbon.ribbonTab;

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

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

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

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

      ctx["getScriptButton" + def.ribbon.button.jc.buttonId + "Tooltip"] = function () {
        return def.ribbon.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;

    buttonDefinitions.forEach(function (def) {
      var tabDef = me.TAB_CONST[def.ribbon.ribbonTab.name] || def.ribbon.ribbonTab,
          buttonNo = def.ribbon.button.jc.buttonId,
          btn = ribbon.addButton(tabDef.text, def.ribbon.buttongroup.text, buttonNo),
          imageName = "ScriptButton" + buttonNo;
      btn.setCallback(function () {
        me.buttonHandler(def);
      }, me);
      btn.setTitle(def.ribbon.button.text);
      btn.setTooltip(def.ribbon.button.tooltipText);
      btn.setIconName(imageName);
      btn.asTile(def.ribbon.button.asTile === true);
      btn.setTileIconName(imageName);
      btn.setPriority(me.getPriority(def));

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

  /**
   * @private
   * Retrieves the priority from a button definition.
   * @param {Object} btnDef
   * @return {Number}
   */
  getPriority: function (btnDef) {
    var me = this;
    return (btnDef.ribbon.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) {
    var 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;
    }

    activeView = workspace.activeView;
    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;

      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") + "";
      } catch (ex) {
        // no sol type found //
      }
      return (solType && (solType != "")) ? access.solTypes.indexOf(solType) !== -1 : false;
    };

    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.ribbon.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.ribbon.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.allSelected) {
      allSelected = workspace.activeView.allSelected;
      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));