/**
 * @file
 * js for taxonomy ui tweaks
 */

(function ($, Drupal, window, document, undefined) {

  function TaxonomyWranglerUID() {
    var prefix = 'taxonomy-wrangler-';
    var randomId = prefix + this.count;
    while($('#' + randomId).length) {
      this.count++;
      randomId = prefix + this.count;
    }
    return randomId;
  }
  TaxonomyWranglerUID.count = 0;

  function TaxonomyWranglerAccordion(table) {
    this.table = table;
    this.rows = $('tbody tr', table);
    this.init();
  }

  TaxonomyWranglerAccordion.instances = {};

  var ctrlDown = false;

  $(window).keydown(function(e) {
    if (e.keyCode == 17) {
      ctrlDown = true;
    }
  }).keyup(function(e) {
    if (e.keyCode == 17) {
      ctrlDown = false;
    }
  });

  TaxonomyWranglerAccordion.prototype.init = function() {
    var accordion = this;
    $('tbody tr').not(':eq(0)').each(function(){
      var $row = $(this);
      accordion.collapseTree($row);
      accordion.attachAccordionEvents($row);
    });
  };

  TaxonomyWranglerAccordion.prototype.attachAccordionEvents = function($row) {
    var accordion = this;
    $row.find('.taxonomy-wrangler-accordion-item').bind('click.tweaksAccordion', function(e){
      e.preventDefault();
      if (ctrlDown) {
        accordion.toggleTree($row);
      }
      else {
        accordion.toggleRow($row);
      }
      return false;
    });
  }

  TaxonomyWranglerAccordion.prototype.refreshRows = function(){
    var accordion = this;
    this.rows.not(':eq(0)').each(function(){
      var $this = $(this);
      var hasChildren = accordion.rows.filter('[data-parent="'+ $this.data('tid') +'"]').length > 0 ? true : false;
      var $collapseLink = $this.find('.taxonomy-wrangler-accordion-item--parent');
      //create a new parent
      if (hasChildren && !$collapseLink.length) {
        $this.find('.tabledrag-handle').after('<a href="#" class="taxonomy-wrangler-accordion-item taxonomy-wrangler-accordion-item--parent"><span></span></a>');
        accordion.attachAccordionEvents($this);
      }
      else if (!hasChildren && $collapseLink.length) {
        $collapseLink.remove();
      }
    });
  };

  /**
   * This is a bit different than a typical accordion in that the
   * data structure is hierarchical, but its representation in the DOM is flat
   */
  TaxonomyWranglerAccordion.prototype.showRow = function($row) {
    $row.css('display', 'table-row').removeClass('tabledrag-hidden');
  }

  TaxonomyWranglerAccordion.prototype.hideRow = function($row) {
    $row.css('display', 'none').addClass('tabledrag-hidden');
  }

  TaxonomyWranglerAccordion.prototype.setCollapsed = function($row, collapsed, recurse) {
    var accordion = this;
    var $children = this.rows.filter('[data-parent=' + $row.data('tid') + ']');
    if (collapsed && $children.length) {
      $row.data('is-collapsed', true);
      $row.find('.taxonomy-wrangler-accordion-item').addClass('is-collapsed');
      if ($children.length) {
        $row.addClass('tabledrag-leaf');
      }
    }
    else {
      $row.data('is-collapsed', false).removeClass('tabledrag-leaf');
      $row.find('.taxonomy-wrangler-accordion-item').removeClass('is-collapsed');
    }
    if (recurse) {
      accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
        accordion.setCollapsed($(this), collapsed, true);
      });
    }
  }

  /*
   * Hide all children
   */
  TaxonomyWranglerAccordion.prototype.hideChildren = function($row) {
    var $item = $row.find('.taxonomy-wrangler-accordion-item');
    var accordion = this;
    this.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
      accordion.hideRow($(this));
      accordion.hideChildren($(this));
    });
  };

  /**
   * Show children if they are not collapsed
   */
  TaxonomyWranglerAccordion.prototype.showChildren = function($row) {
    var accordion = this;
    accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
      accordion.showRow($(this));
      if (!$(this).data('is-collapsed')) {
        accordion.showChildren($(this));
      }
    });
  };

  TaxonomyWranglerAccordion.prototype.toggleRow = function($row) {
    if ($row.data('is-collapsed')) {
      this.setCollapsed($row, false);
      this.showChildren($row);
    }
    else {
      this.setCollapsed($row, true);
      this.hideChildren($row);
    }
  };

  TaxonomyWranglerAccordion.prototype.expandTree = function($row) {
    this.setCollapsed($row, false, true);
    this.showChildren($row);
  };

  TaxonomyWranglerAccordion.prototype.collapseTree = function($row) {
    this.setCollapsed($row, true, true);
    this.hideChildren($row);
  };

  TaxonomyWranglerAccordion.prototype.toggleTree = function($row) {
    if ($row.data('is-collapsed')) {
      this.expandTree($row);
    }
    else {
      this.collapseTree($row);
    }
  }

  Drupal.behaviors.taxonomyWranglerAccordion = {
    attach: function(context, settings) {
      idCount = 0;

      $('.taxonomy-wrangler-accordion', context)
        .not('.taxonomy-wrangler-accordion-processed')
        .addClass('taxonomy-wrangler-accordion-processed')
        .each(function(){
          var id = $(this).attr('id')
          if (!!!id || id == '') {
            $(this).attr('id', TaxonomyWranglerUID());
          }
          TaxonomyWranglerAccordion.instances[id] = new TaxonomyWranglerAccordion(this);
        });
    }
  };

  function TaxonomyWranglerCollection() {
    this.updatedTerms = {};
  };

  TaxonomyWranglerCollection.prototype.updateTerm = function(row) {
    var $row = $(row);
    var tid = $row.data('tid');
    var now = Date.now();
    this.updatedTerms[tid] = {
      "tid": $row.data('tid'),
      "parent": $row.data('parent'),
      "depth": $row.data('depth'),
      "weight": $row.data('weight'),
      "updated": now
    }
    //el.attr('data-{key}') & el.data('key') are not the same!
    $row.attr('data-parent', $row.data('parent'))
      .attr('data-depth', $row.data('depth'))
      .attr('data-weight', $row.data('weight'))
      .data('updated', now);
  };

  /**
   * New TableDrag "class" that inherits Drupal.tableDrag
   */
  function TaxonomyWranglerDrag(table, tableSettings) {
      Drupal.tableDrag.call(this, table, tableSettings);
  }

  TaxonomyWranglerDrag.prototype = new Drupal.tableDrag();

  TaxonomyWranglerDrag.instances = {};

  /**
   * Find the target used within a particular row and group.
   * This isn't strictly necessary in this case because the data
   * is stored on the row element itself.
   * Copied and modified from misc/tabledrag.js
   */
  TaxonomyWranglerDrag.prototype.rowSettings = function (group, row) {
    for (var delta in this.tableSettings[group]) {
      // Return a copy of the row settings.
      var rowSettings = {};
      for (var n in this.tableSettings[group][delta]) {
        rowSettings[n] = this.tableSettings[group][delta][n];
      }
      return rowSettings;
    }
  };

  /**
   * After the row is dropped, update the table fields according to the settings
   * set for this table.
   *
   * @param changedRow
   *   DOM object for the row that was just dropped.
   */
  TaxonomyWranglerDrag.prototype.updateFields = function (changedRow) {
    var $c = $(changedRow);
    for (var group in this.tableSettings) {
      // Each group may have a different setting for relationship, so we find
      // the source rows for each separately.
      this.updateField(changedRow, group);
    }
  };

  /**
   * After the row is dropped, update a single table field according to specific
   * settings. This code is cribbed from tabledrag.js and modified to
   * use the data properties on the row object rather than input fields in the row.
   *
   * @param changedRow
   *   DOM object for the row that was just dropped.
   * @param group
   *   The settings group on which field updates will occur.
   */
  TaxonomyWranglerDrag.prototype.updateField = function (changedRow, group) {
    var rowSettings = this.rowSettings(group, changedRow);
    // Set the row as its own target.
    if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
      var sourceRow = changedRow;
    }
    // Siblings are easy, check previous and next rows.
    else if (rowSettings.relationship == 'sibling') {
      var previousRow = $(changedRow).prev('tr').get(0);
      var nextRow = $(changedRow).next('tr').get(0);
      var sourceRow = changedRow;
      if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
        if (this.indentEnabled) {
          if ($('.indentations', previousRow).length == $('.indentations', changedRow)) {
            sourceRow = previousRow;
          }
        }
        else {
          sourceRow = previousRow;
        }
      }
      else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
        if (this.indentEnabled) {
          if ($('.indentations', nextRow).length == $('.indentations', changedRow)) {
            sourceRow = nextRow;
          }
        }
        else {
          sourceRow = nextRow;
        }
      }
    }
    // Parents, look up the tree until we find a field not in this group.
    // Go up as many parents as indentations in the changed row.
    else if (rowSettings.relationship == 'parent') {
      var previousRow = $(changedRow).prev('tr');
      while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
        previousRow = previousRow.prev('tr');
      }
      // If we found a row.
      if (previousRow.length) {
        sourceRow = previousRow[0];
      }
      // Otherwise we went all the way to the left of the table without finding
      // a parent, meaning this item has been placed at the root level.
      else {
        // Use the first row in the table as source, because it's guaranteed to
        // be at the root level. Find the first item, then compare this row
        // against it as a sibling.
        sourceRow = $(this.table).find('tr.draggable:first').get(0);
        if (sourceRow == this.rowObject.element) {
          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
        }
        var useSibling = true;
      }
    }

    // Because we may have moved the row from one category to another,
    // take a look at our sibling and borrow its sources and targets.
    this.copyDragClasses(sourceRow, changedRow, group);
    rowSettings = this.rowSettings(group, changedRow);

    // In the case that we're looking for a parent, but the row is at the top
    // of the tree, copy our sibling's values.
    if (useSibling) {
      rowSettings.relationship = 'sibling';
      rowSettings.source = rowSettings.target;
    }
    var $changedRow = $(changedRow);
    // Check if data exists in this row.
    if (typeof $(sourceRow).data(rowSettings.target) !== 'undefined') {
      switch (rowSettings.action) {
        case 'depth':
          // Get the depth of the target row.
          $changedRow.data(rowSettings.target, $('.indentation', $(sourceRow)).length);
          break;
        case 'match':
          // Update the value.
          $changedRow.data(rowSettings.target, $(sourceRow).data(rowSettings.source));
          break;
        case 'order':
          var siblings = this.rowObject.findSiblings(rowSettings);
          // We're not using fields for this so we just assume
          //  weight starting from 0
          var weight = 0;
          $(siblings).each(function () {
            $(this).data('weight', weight);
            weight++;
            $(this).trigger('taxonomyWranglerTermDrag.rowDataChange', [this]);
          });
          break;
      }
    }
    $changedRow.trigger('taxonomyWranglerTermDrag.rowDataChange', [changedRow]);
  };


  TaxonomyWranglerDrag.prototype.dropRow = function (event, self) {
    // Drop row functionality shared between mouseup and blur events.
    if (self.rowObject != null) {
      var droppedRow = self.rowObject.element;
      // The row is already in the right place so we just release it.
      if (self.rowObject.changed == true) {
        // Update the fields in the dropped row.
        self.updateFields(droppedRow);

        // If a setting exists for affecting the entire group, update all the
        // fields in the entire dragged group.
        for (var group in self.tableSettings) {
          var rowSettings = self.rowSettings(group, droppedRow);
          if (rowSettings.relationship == 'group') {
            for (var n in self.rowObject.children) {
              self.updateField(self.rowObject.children[n], group);
            }
          }
        }

        self.rowObject.markChanged();
        if (self.changed == false) {
          self.changed = true;
        }
      }

      if (self.indentEnabled) {
        self.rowObject.removeIndentClasses();
      }
      if (self.oldRowElement) {
        $(self.oldRowElement).removeClass('drag-previous');
      }
      $(droppedRow).removeClass('drag').addClass('drag-previous');
      self.oldRowElement = droppedRow;
      self.onDrop();
      self.rowObject = null;
    }

    // Functionality specific only to mouseup event.
    if (self.dragObject != null) {
      $('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover');

      self.dragObject = null;
      $('body').removeClass('drag');
      clearInterval(self.scrollInterval);

      // Hack for IE6 that flickers uncontrollably if select lists are moved.
      if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
        $('select', this.table).css('display', 'block');
      }
    }
  };

  /**
   * Indent a row within the legal bounds of the table.
   *
   * @param indentDiff
   *   The number of additional indentations proposed for the row (can be
   *   positive or negative). This number will be adjusted to nearest valid
   *   indentation level for the row.
   */
  TaxonomyWranglerDrag.prototype.row.prototype.indent = function (indentDiff) {
    // Determine the valid indentations interval if not available yet.
    if (!this.interval) {
      var prevRow = $(this.element).prevAll('tr:not(.tabledrag-hidden)').get(0);
      var nextRow = $(this.group).filter(':last').nextAll('tr:not(.tabledrag-hidden)').get(0);
      this.interval = this.validIndentInterval(prevRow, nextRow);
    }

    // Adjust to the nearest valid indentation.
    var indent = this.indents + indentDiff;
    indent = Math.max(indent, this.interval.min);
    indent = Math.min(indent, this.interval.max);
    indentDiff = indent - this.indents;

    for (var n = 1; n <= Math.abs(indentDiff); n++) {
      // Add or remove indentations.
      if (indentDiff < 0) {
        $('.indentation:first', this.group).remove();
        this.indents--;
      }
      else {
        $('td:first', this.group).prepend(Drupal.theme('tableDragIndentation'));
        this.indents++;
      }
    }
    if (indentDiff) {
      // Update indentation for this row.
      this.changed = true;
      this.groupDepth += indentDiff;
      this.onIndent();
    }

    return indentDiff;
  };

  /**
   * Perform the swap between two rows. Take into account the collapsed
   * accordion rows when swapping after.
   *
   * @param position
   *   Whether the swap will occur 'before' or 'after' the given row.
   * @param row
   *   DOM element what will be swapped with the row group.
   */
  TaxonomyWranglerDrag.prototype.row.prototype.swap = function (position, row) {
    var swapRow = row;
    if (position == 'after') {
      var $row = $(row);
      if ($row.data('is-collapsed')) {
        var $target = $row.nextUntil(":visible");
        if ($target.length) {
          swapRow = $target.get($target.length - 1);
        }
        else {
          // If there are no other rows visible, we must be at the bottom
          // of the table, get the last row.
          swapRow = $row.nextAll(":last").get(0);
        }
      }
    }

    Drupal.detachBehaviors(this.group, Drupal.settings, 'move');
    $(swapRow)[position](this.group);
    Drupal.attachBehaviors(this.group, Drupal.settings);
    this.changed = true;
    this.onSwap(row);
  };

  /**
   * Ensure that two rows are allowed to be swapped.
   *
   * @param row
   *   DOM object for the row being considered for swapping.
   */
  TaxonomyWranglerDrag.prototype.row.prototype.isValidSwap = function (row) {
    if (this.indentEnabled) {
      var prevRow, nextRow;
      if (this.direction == 'down') {
        prevRow = row;
        nextRow = $(row).nextAll('tr:not(:hidden)').get(0);
      }
      else {
        prevRow = $(row).prevAll('tr:not(:hidden)').get(0);
        nextRow = row;
      }
      this.interval = this.validIndentInterval(prevRow, nextRow);

      // We have an invalid swap if the valid indentations interval is empty.
      if (this.interval.min > this.interval.max) {
        return false;
      }
    }

    // Do not let an un-draggable first row have anything put before it.
    if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) {
      return false;
    }

    return true;
  };

/**
 * Move a block in the blocks table from one region to another via select list.
 *
 * This behavior is dependent on the tableDrag behavior, since it uses the
 * objects initialized in that behavior to update the row.
 */
Drupal.behaviors.taxonomyWranglerTermDrag = {
  attach: function (context, settings) {
    for (var base in settings.taxonomyWrangler.tables) {
      if (typeof TaxonomyWranglerDrag.instances[base] == 'undefined') {
        $('#' + base, context).once('tabledrag', function () {
          // Create the new tableDrag instance. Save in the Drupal variable
          // to allow other scripts access to the object.
          TaxonomyWranglerDrag.instances[base] = new TaxonomyWranglerDrag(this, settings.taxonomyWrangler.tables[base]);
        });
      }
    }
    var table = $('#taxonomy-wrangler', context);
    var tableDrag = TaxonomyWranglerDrag.instances['taxonomy-wrangler']; // Get the blocks tableDrag object.
    var $messages = $('#taxonomy-wrangler-messages-js', context);

    // Compare server timestamp with javascript changed stamp and remove changed classes.
    if (settings.taxonomyWrangler && settings.taxonomyWrangler.updatedTimestamps) {
      $.each(settings.taxonomyWrangler.updatedTimestamps, function (tid, stamp) {
        var $row = table.find('tr[data-tid="'+ tid +'"]');
        if ($row.data('updated') == stamp) {
          $row.removeClass('drag-previous').find('span.tabledrag-changed').remove();
        }
      });
    }

    if (tableDrag.tm === null) {
      $messages.removeClass('is-busy').empty();
    }

    if (!table.hasClass('taxonomy-wrangler-processed')) {
      table.addClass('taxonomy-wrangler-processed');
      var rows = $('tr', table).length;
      var $termData = $('input[name="term_data"]', context);
      var $submit = $('input[name="op"][value="Save"]', context)
      var $rows = $('tr', table);
      tableDrag.collection = new TaxonomyWranglerCollection();
      var updatedTerms = {};
      $submit.attr('disabled', 'disabled');
      var accordion = !!TaxonomyWranglerAccordion.instances[table.attr('id')] ? TaxonomyWranglerAccordion.instances[table.attr('id')] : null;

      $rows.bind('taxonomyWranglerTermDrag.rowDataChange', function(e, row){
        tableDrag.collection.updateTerm(row);
        var termsArray = [];
        var tids = [];
        accordion.refreshRows();
        $.each(tableDrag.collection.updatedTerms, function(i, val) {
          termsArray.push(val);
          tids.push(val.tid + ':' + val.weight);
        });
        $termData.val(JSON.stringify({ "termData": termsArray }));
        if (tableDrag.tm) {
          clearTimeout(tableDrag.tm);
          tableDrag.tm = null;
        }
       tableDrag.tm = setTimeout(function(){
          $submit.removeAttr('disabled').trigger('mousedown').attr('disabled', 'disabled');
          tableDrag.tm = null;
        }, 1000);
        if (!$messages.hasClass('is-busy')) {
          $messages.addClass('is-busy').append('<div class="ajax-progress ajax-progress-throbber ajax-progress-throbber--taxonomy-wrangler"><div class="throbber">&nbsp;</div> Updating terms</div>');
        }
      });
    }
  }
};

})(jQuery, Drupal, this, this.document);
