
(function($, Archibald) {

// Prepare a global singleton, which will be an instance of
// ArchibaldCurrMapping.
var archibaldCurrMapping;

var ArchibaldCurrMapping = function(settings) {
  // Store a local reference to the Drupal settings, for easier access.
  this.settings = settings;
};

ArchibaldCurrMapping.prototype = {
  /**
   * All apps that can be the target of suggestions are registered here.
   */
  apps: {},

  /**
   * Register an app as a target for suggestions.
   *
   * @param {ArchibaldCurriculum.Core} app
   */
  registerApp: function(app) {
    if (typeof app.curriculum !== 'undefined') {
      if (typeof this.apps[app.curriculum] === 'undefined') {
        this.apps[app.curriculum] = [];
      }
      this.apps[app.curriculum].push(app);
    }
  },

  /**
   * Can this curriculum receive suggestions, and if so, from whom?
   *
   * @param {String} curriculum
   *    The curriculum to check.
   *
   * @returns {Array}
   *    The list of curricula this curriculum can receive suggestions from.
   */
  canReceiveSuggestionsFrom: function(curriculum) {
    var from = [];
    if (
      typeof this.settings.activateSuggestions[curriculum] !== 'undefined' &&
      typeof this.settings.activateSuggestions[curriculum].providesFor !== 'undefined'
    ) {
      for (var source in this.settings.activateSuggestions[curriculum].providesFor) {
        from.push({
          name: source,
          label: this.settings.activateSuggestions[curriculum].providesFor[source]
        });
      }
    }
    return from.length ? from : false;
  },

  /**
   * Can this curriculum provide suggestions, and if so, for whom?
   *
   * @param {String} curriculum
   *    The curriculum to check.
   *
   * @returns {Array}
   *    The list of curricula this curriculum can provide suggestions for.
   */
  canProvideSuggestionsFor: function(curriculum) {
    var providesFor = [];
    for (var receiver in this.settings.activateSuggestions) {
      if (
        typeof this.settings.activateSuggestions[receiver].providesFor[curriculum] !== 'undefined'
      ) {
        providesFor.push({
          name: receiver,
          label: this.settings.activateSuggestions[receiver].label
        });
      }
    }
    return providesFor.length ? providesFor : false;
  },

  /**
   * Helper method to build the suggestion URL.
   *
   * In order to fetch suggestions, we need to craft a specific URL. This method
   * helps with this.
   *
   * @param {ArchibaldCurriculum.Core} app
   *    The app that will provide the suggestions.
   *
   * @returns {String}
   *    An URL for an ajax request to fetch suggestions.
   */
  getSuggestionURL: function(app) {
    return this.settings.suggestionURL + '/' +
      app.curriculum + '/' +
      _.map(app.providesFor, function(item) {
        return item.name;
      }).join(',');
  },

  /**
   * Add the suggestion to the concerned apps.
   *
   * @param {String} curriculum
   *    The curriculum that the suggestion was for. Target all apps that are for
   *    this curriculum.
   * @param {String} itemId
   *    The item to activate in all concerned apps.
   */
  addSuggestion: function(curriculum, itemId) {
    var app, model;
    if (typeof this.apps[curriculum] !== 'undefined') {
      for (var i = 0, len = this.apps[curriculum].length; i < len; i++) {
        app = this.apps[curriculum][i];
        model = app.getItemDatabase().findWhere({
          id: itemId
        });

        if (model && !model.get('active')) {
          model.set('active', true);

          // Because this was not done through the UI, the app won't trigger
          // the recursive checking logic. Trigger it here.
          app.recursiveCheck(model);
        }
      }
    }
  },

  /**
   * Remove the suggestion to the concerned apps.
   *
   * @param {String} curriculum
   *    The curriculum that the suggestion was for. Target all apps that are for
   *    this curriculum.
   * @param {String} curriculumLabel
   *    The name of the curriculum.
   * @param {String} itemId
   *    The item to de-activate in all concerned apps.
   */
  removeSuggestion: function(curriculum, curriculumLabel, itemId) {
    var app, model;
    if (typeof this.apps[curriculum] !== 'undefined') {
      for (var i = 0, len = this.apps[curriculum].length; i < len; i++) {
        app = this.apps[curriculum][i];
        model = app.getItemDatabase().findWhere({
          id: itemId
        });

        if (model && model.get('active')) {
          model.set('active', false);

          // Because this was not done through the UI, the app won't trigger
          // the recursive checking logic. Trigger it here.
          app.recursiveCheck(model);

          // Now, we also need to check if we need to recursively disable parent
          // items. When unchecking, the app only disables child items. But
          // here, if the parent has no children anymore, we probably want to
          // remove it as well. Start by fetching the parents, and checking of
          // they all only have a single active child. If so, we could remove
          // this branch.
          var firstModel = true,
              parent = model,
              parents = [],
              // Add a limit, to prevent infinite loops. This is a failsafe.
              limit = 10,
              children;
          while (
            limit &&
            (parent = app.getItemDatabase().findWhere({
              id: parent.get('parentId')
            }))
          ) {
            // Does the parent have any children? Because we just removed at
            // least one child, our first iteration must take that into account,
            // and check if there are no children at all.
            children = app.getItemDatabase().where({
              parentId: parent.get('id'),
              active: true
            });

            // This means, the first iteration would return exactly 1, if the
            // model we just de-activated was the only active child. If it
            // returns more than 1, than some its siblings are active.
            if (children.length + (firstModel ? 1 : 0) > 1) {
              // There are several active children. We can abort now.
              break;
            }

            parents.push(parent);
            limit--;
            firstModel = false;
          }

          if (parents.length) {
            parent = parents[ parents.length - 1 ];
            // The last parent is the top-most parent. Recurse down.
            parent.set('active', false);
            app.recursiveCheck(parent);
          }
        }
      }
    }
  }
};

Drupal.behaviors.archibaldCurricula.hooks.appRegistered.push(function(app) {
  // Use this opportunity to make sure we have a global ArchibaldCurrMapping
  // object.
  if (typeof archibaldCurrMapping === 'undefined') {
    archibaldCurrMapping = new ArchibaldCurrMapping(
      Drupal.settings.archibaldCurrMapping
    );
  }

  var from, providesFor;
  if (typeof app.curriculum !== 'undefined') {
    from = archibaldCurrMapping.canReceiveSuggestionsFrom(app.curriculum);
    providesFor = archibaldCurrMapping.canProvideSuggestionsFor(app.curriculum);
  }

  // Is this an app that can "receive" suggestions?
  if (from) {
    archibaldCurrMapping.registerApp(app);
  }

  // Is this an app with which we can provide suggestions?
  if (providesFor) {
    app.providesFor = providesFor;

    // Activate the suggestions for this editor.
    app.on('app:render', function() {
      // Add a wrapper to contain the suggestions.
      app.getWrapper().find('.archibald-curriculum-ui__editor').after(
        _.template(Archibald.templates.currMappingSuggestionsWrapper)()
      );

      // Store the suggestion wrapper "globally" by attaching it to the app
      // object, and provide shortcuts for the child elements as well.
      app.$suggestionWrapper = app.getWrapper().find('.archibald-curricula-field__editor__suggestions');
      app.$suggestionWrapper.$content = app.$suggestionWrapper.find('.archibald-curricula-field__editor__suggestions__content');
      // Hide it.
      app.$suggestionWrapper.slideUp();

      // Do we need to integrate the help tooltip?
      if (
        typeof Drupal.settings.archibaldCurrMapping.helpTooltip !== 'undefined'
      ) {
        var $tooltip = $(Drupal.settings.archibaldCurrMapping.helpTooltip)
        $tooltip.insertBefore(app.$suggestionWrapper.$content);
        Drupal.attachBehaviors();
      }
    });
  }
});

Drupal.behaviors.archibaldCurricula.hooks.appReady.push(function(app) {
  // Is this an app for which we can provide suggestions?
  if (
    typeof app.providesFor !== 'undefined' &&
    app.providesFor
  ) {

    // Hide the suggestion box, on any event.
    app.on('all', function() {
      // Hide the current suggestion box, if it is visible.
      app.$suggestionWrapper.slideUp();
    });

    // Show suggestions when an item is activated.
    app.on('items:change:active', function(models, collection) {
      if (models.where({ active: true }).length) {
        // Fetch suggestions. Make sure we only fetch one payload at a time.
        // if there was already an ajax request being made, cancel it.
        if (
          typeof app.suggestionRequest !== 'undefined'
        ) {
          app.suggestionRequest.abort();
        }

        // Hide the current suggestion box, if it is visible.
        app.$suggestionWrapper.slideUp();

        app.suggestionRequest = $.ajax({
          url: archibaldCurrMapping.getSuggestionURL(app),
          dataType: 'json',
          type: 'post',
          data: { item_ids: models.map(function(model) {
            return model.get('id');
          }) },
          success: function(json) {
            // We consider this request as completed.
            app.suggestionRequest = undefined;
            var hasSuggestions = false;

            _.map(app.providesFor, function(item) {
              if (typeof json[app.curriculum][item.name] !== 'undefined') {
                // Are there any suggestions?
                if (
                  !hasSuggestions &&
                  json[app.curriculum][item.name].suggestions !== 'undefined' &&
                  json[app.curriculum][item.name].suggestions.length
                ) {
                  hasSuggestions = true;
                }

                // Add the labels to the json data, so we don't have to look
                // them up in the template.
                json[app.curriculum][item.name].label = item.label;
              }
            });

            // Are there any suggestions? If not, return now.
            if (!hasSuggestions) {
              return;
            }

            // Automatically add them.
            for (var curriculum in json[app.curriculum]) {
              for (var suggestion in json[app.curriculum][curriculum].suggestions) {
                archibaldCurrMapping.addSuggestion(
                  curriculum,
                  json[app.curriculum][curriculum].suggestions[suggestion].id
                );
              }
            }

            // Render them.
            app.$suggestionWrapper.$content.html(
              _.template(Archibald.templates.currMappingSuggestions)({
                items: json[app.curriculum]
              })
            );

            // Handle the manual removing (or re-enabling) of these items.
            app.$suggestionWrapper.$content
              .find('.archibald-curricula-field__editor__suggestions__content__checkbox')
              .click(function() {
                var $this = $(this),
                    curriculum = $this.attr('data-curriculum-id'),
                    curriculumLabel = $this.attr('data-curriculum-label'),
                    itemId = $this.attr('data-item-id');
                if (this.checked) {
                  // Re-enable the suggestion.
                  archibaldCurrMapping.addSuggestion(
                    curriculum,
                    itemId
                  );

                  // Flash an "Added" message.
                  $this.siblings('.archibald-curricula-field__editor__suggestions__content__confirm-message')
                    .html(Drupal.t("Added again"), {}, { context: "archibald_curr_mapping:view" })
                    .fadeIn('fast').delay(2000).fadeOut('slow');
                }
                else {
                  // Disable the suggestion.
                  archibaldCurrMapping.removeSuggestion(
                    curriculum,
                    curriculumLabel,
                    itemId
                  );

                  // Flash a "Removed" message.
                  $this.siblings('.archibald-curricula-field__editor__suggestions__content__confirm-message')
                    .html(Drupal.t("Removed"), {}, { context: "archibald_curr_mapping:view" })
                    .fadeIn('fast').delay(2000).fadeOut('slow');
                }
              });

            // Show the suggestions.
            app.$suggestionWrapper.slideDown();
          }
        });
      }
    });
  }
});

})(jQuery, window.ArchibaldCurriculum || (window.ArchibaldCurriculum = new Object()));
