
// Ensure the $ alias is owned by jQuery.
(function($) {

Drupal.settings.Panels = Drupal.settings.Panels || {};

Drupal.PanelsIPE = {
  editors: {},
  bindClickDelete: function(context) {
    $('a.pane-delete:not(.pane-delete-processed)', context)
      .addClass('pane-delete-processed')
      .click(function() {
        if (confirm(Drupal.t('Remove this pane?'))) {
          $(this).parents('div.panels-ipe-portlet-wrapper').fadeOut('medium', function() {
            var $sortable = $(this).closest('.ui-sortable');
            $(this).empty().remove();
            $sortable.trigger('sortremove');
          });
          $(this).parents('div.panels-ipe-display-container').addClass('changed');
        }
        return false;
      });
  }
}


Drupal.behaviors.PanelsIPE = {
  attach: function(context) {
    // Remove any old editors.
    for (var i in Drupal.PanelsIPE.editors) {
      if (Drupal.settings.PanelsIPECacheKeys.indexOf(i) === -1) {
        // Clean-up a little bit and remove it.
        Drupal.PanelsIPE.editors[i].editing = false;
        Drupal.PanelsIPE.editors[i].changed = false;
        delete Drupal.PanelsIPE.editors[i];
      }
    }

    // Initialize new editors.
    for (var i in Drupal.settings.PanelsIPECacheKeys) {
      var key = Drupal.settings.PanelsIPECacheKeys[i];
      $('div#panels-ipe-display-' + key + ':not(.panels-ipe-processed)')
        .addClass('panels-ipe-processed')
        .each(function() {
          // If we're replacing an old IPE, clean it up a little.
          if (Drupal.PanelsIPE.editors[key]) {
            Drupal.PanelsIPE.editors[key].editing = false;
          }
          Drupal.PanelsIPE.editors[key] = new DrupalPanelsIPE(key);
          Drupal.PanelsIPE.editors[key].showContainer();
        });
    }
    $('.panels-ipe-hide-bar').once('panels-ipe-hide-bar').click(function() {
      Drupal.PanelsIPE.editors[key].hideContainer();
    });
    Drupal.PanelsIPE.bindClickDelete(context);
  }
};

/**
 * Base object (class) definition for the Panels In-Place Editor.
 *
 * A new instance of this object is instanciated for every unique IPE on a given
 * page.
 *
 * Note that this form is provisional, and we hope to replace it with a more
 * flexible, loosely-coupled model that utilizes separate controllers for the
 * discrete IPE elements. This will result in greater IPE flexibility.
 */
function DrupalPanelsIPE(cache_key, cfg) {
  cfg = cfg || {};
  var ipe = this;
  this.key = cache_key;
  this.lockPath = null;
  this.state = {};
  this.container = $('#panels-ipe-control-container');
  this.control = $('div#panels-ipe-control-' + cache_key);
  this.initButton = $('div.panels-ipe-startedit', this.control);
  this.cfg = cfg;
  this.changed = false;
  this.sortableOptions = $.extend({
    opacity: 0.75, // opacity of sortable while sorting
    items: 'div.panels-ipe-portlet-wrapper',
    handle: 'div.panels-ipe-draghandle',
    cancel: '.panels-ipe-nodrag',
    tolerance: 'pointer',
    dropOnEmpty: true
  }, cfg.sortableOptions || {});

  this.regions = [];
  this.sortables = {};

  $(document).bind('CToolsDetachBehaviors', function() {
    // If the IPE is off and the container is not visible, then we need
    // to reshow the container on modal close.
    if (!$('.panels-ipe-form-container', ipe.control).html() && !ipe.container.is(':visible')) {
      ipe.showContainer();
      ipe.cancelLock();
    }

    // If the IPE is on and we've hidden the bar for a modal, we need to
    // re-display it.
    if (ipe.topParent && ipe.topParent.hasClass('panels-ipe-editing') && ipe.container.is(':not(visible)')) {
      ipe.showContainer();
    }
  });


  // If a user navigates away from a locked IPE, cancel the lock in the background.
  $(window).bind('beforeunload', function() {
    if (!ipe.editing) {
      return;
    }

    if (ipe.topParent && ipe.topParent.hasClass('changed')) {
      ipe.changed = true;
    }

    if (ipe.changed) {
      return Drupal.t('This will discard all unsaved changes. Are you sure?');
    }
  });

  // If a user navigates away from a locked IPE, cancel the lock in the background.
  $(window).bind('unload', function() {
    ipe.cancelLock(true);
  });

  /**
   * If something caused us to abort what we were doing, send a background
   * cancel lock request to the server so that we do not leave stale locks
   * hanging around.
   */
  this.cancelLock = function(sync) {
    // If there's a lockpath and an ajax available, inform server to clear lock.
    // We borrow the ajax options from the customize this page link.
    if (ipe.lockPath && Drupal.ajax['panels-ipe-customize-page']) {
      var ajaxOptions = {
        type: 'POST',
        url: ipe.lockPath
      }

      if (sync) {
        ajaxOptions.async = false;
      }

      // Make sure we don't somehow get another one:
      ipe.lockPath = null;

      // Send the request. This is synchronous to prevent being cancelled.
      $.ajax(ajaxOptions);
    }
  }

  this.activateSortable = function(event, ui) {
    if (!Drupal.settings.Panels || !Drupal.settings.Panels.RegionLock) {
      // don't bother if there are no region locks in play.
      return;
    }

    var region = event.data.region;
    var paneId = ui.item.attr('id').replace('panels-ipe-paneid-', '');

    var disabledRegions = false;

    // Determined if this pane is locked out of this region.
    if (!Drupal.settings.Panels.RegionLock[paneId] || Drupal.settings.Panels.RegionLock[paneId][region]) {
      ipe.sortables[region].sortable('enable');
      ipe.sortables[region].sortable('refresh');
    }
    else {
      disabledRegions = true;
      ipe.sortables[region].sortable('disable');
      ipe.sortables[region].sortable('refresh');
    }

    // If we disabled regions, we need to
    if (disabledRegions) {
      $(event.srcElement).bind('dragstop', function(event, ui) {
        // Go through
      });
    }
  };

  // When dragging is stopped, we need to ensure all sortable regions are enabled.
  this.enableRegions = function(event, ui) {
    for (var i in ipe.regions) {
      ipe.sortables[ipe.regions[i]].sortable('enable');
      ipe.sortables[ipe.regions[i]].sortable('refresh');
    }
  }

  this.initSorting = function() {
    var $region = $(this).parents('.panels-ipe-region');
    var region = $region.attr('id').replace('panels-ipe-regionid-', '');
    ipe.sortables[region] = $(this).sortable(ipe.sortableOptions);
    ipe.regions.push(region);
    $(this).bind('sortactivate', {region: region}, ipe.activateSortable);
  };

  this.initEditing = function(formdata) {
    ipe.editing = true;
    ipe.topParent = $('div#panels-ipe-display-' + cache_key);
    ipe.backup = this.topParent.clone();

    // See http://jqueryui.com/demos/sortable/ for details on the configuration
    // parameters used here.
    ipe.changed = false;

    $('div.panels-ipe-sort-container', ipe.topParent).each(ipe.initSorting);

    // Since the connectWith option only does a one-way hookup, iterate over
    // all sortable regions to connect them with one another.
    $('div.panels-ipe-sort-container', ipe.topParent)
      .sortable('option', 'connectWith', ['div.panels-ipe-sort-container']);

    $('div.panels-ipe-sort-container', ipe.topParent).bind('sortupdate', function() {
      ipe.changed = true;
    });

    $('div.panels-ipe-sort-container', ipe.topParent).bind('sortstop', this.enableRegions);

    // Refresh the control jQuery object.
    ipe.control = $(ipe.control.selector);
    $('.panels-ipe-form-container', ipe.control).append(formdata);

    $('input:submit:not(.ajax-processed), button:not(.ajax-processed)', ipe.control).addClass('ajax-processed').each(function() {
      var element_settings = {};

      element_settings.url = $(this.form).attr('action');
      element_settings.setClick = true;
      element_settings.event = 'click';
      element_settings.progress = { 'type': 'throbber' };
      element_settings.ipe_cache_key = cache_key;

      var base = $(this).attr('id');
      Drupal.ajax[ipe.base] = new Drupal.ajax(base, this, element_settings);
    });

    // Perform visual effects in a particular sequence.
    // .show() + .hide() cannot have speeds associated with them, otherwise
    // it clears out inline styles.
    $('.panels-ipe-on').show();
    ipe.showForm();
    $('body').add(ipe.topParent).addClass('panels-ipe-editing');

  };

  this.hideContainer = function() {
    ipe.container.slideUp('fast');
  };

  this.showContainer = function() {
    ipe.container.slideDown('normal');
  };

  this.showButtons = function() {
    $('.panels-ipe-form-container').hide();
    $('.panels-ipe-button-container').show();
    ipe.showContainer();
  };

  this.showForm = function() {
    $('.panels-ipe-button-container').hide();
    $('.panels-ipe-form-container').show();
    ipe.showContainer();
  };

  this.endEditing = function() {
    ipe.editing = false;
    ipe.lockPath = null;
    $('.panels-ipe-form-container').empty();
    // Re-show all the IPE non-editing meta-elements
    $('div.panels-ipe-off').show('fast');

    // Refresh the container and control jQuery objects.
    ipe.container = $(ipe.container.selector);
    ipe.control = $(ipe.control.selector);

    ipe.showButtons();
    // Re-hide all the IPE meta-elements
    $('div.panels-ipe-on').hide();

    $('.panels-ipe-editing').removeClass('panels-ipe-editing');
    $('div.panels-ipe-sort-container.ui-sortable', ipe.topParent).sortable("destroy");
  };

  this.saveEditing = function() {
    $('div.panels-ipe-region', ipe.topParent).each(function() {
      var val = '';
      var region = $(this).attr('id').split('panels-ipe-regionid-')[1];
      $(this).find('div.panels-ipe-portlet-wrapper').each(function() {
        var id = $(this).attr('id').split('panels-ipe-paneid-')[1];
        if (id) {
          if (val) {
            val += ',';
          }
          val += id;
        }
      });
      $('[name="panel[pane][' +  region + ']"]', ipe.control).val(val);
    });
  }

  this.cancelIPE = function() {
    ipe.hideContainer();
    ipe.topParent.fadeOut('medium', function() {
      ipe.topParent.replaceWith(ipe.backup.clone());
      ipe.topParent = $('div#panels-ipe-display-' + ipe.key);

      // Processing of these things got lost in the cloning, but the classes remained behind.
      // @todo this isn't ideal but I can't seem to figure out how to keep an unprocessed backup
      // that will later get processed.
      $('.ctools-use-modal-processed', ipe.topParent).removeClass('ctools-use-modal-processed');
      $('.panels-ipe-hide-bar-processed', ipe.topParent).removeClass('panels-ipe-hide-bar-processed');
      $('.pane-delete-processed', ipe.topParent).removeClass('pane-delete-processed');
      ipe.topParent.fadeIn('medium');
      Drupal.attachBehaviors();
    });
  };

  this.cancelEditing = function() {
    if (ipe.topParent.hasClass('changed')) {
      ipe.changed = true;
    }

    if (!ipe.changed || confirm(Drupal.t('This will discard all unsaved changes. Are you sure?'))) {
      this.cancelIPE();
      return true;
    }
    else {
      // Cancel the submission.
      return false;
    }
  };

  this.createSortContainers = function() {
    $('div.panels-ipe-region', this.topParent).each(function() {
      $(this).children('div.panels-ipe-portlet-marker').parent()
        .wrapInner('<div class="panels-ipe-sort-container" />');

      // Move our gadgets outside of the sort container so that sortables
      // cannot be placed after them.
      $('div.panels-ipe-portlet-static', this).each(function() {
        $(this).prependTo($(this).parent().parent());
      });
    });
  }

  this.createSortContainers();

};

$(function() {
  Drupal.ajax.prototype.commands.initIPE = function(ajax, data, status) {
    if (Drupal.PanelsIPE.editors[data.key]) {
      Drupal.PanelsIPE.editors[data.key].initEditing(data.data);
      Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
    }
    Drupal.attachBehaviors();

  };

  Drupal.ajax.prototype.commands.IPEsetLockState = function(ajax, data, status) {
    if (Drupal.PanelsIPE.editors[data.key]) {
      Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
    }
  };

  Drupal.ajax.prototype.commands.addNewPane = function(ajax, data, status) {
    if (Drupal.PanelsIPE.editors[data.key]) {
      Drupal.PanelsIPE.editors[data.key].changed = true;
    }
  };

  Drupal.ajax.prototype.commands.cancelIPE = function(ajax, data, status) {
    if (Drupal.PanelsIPE.editors[data.key]) {
      Drupal.PanelsIPE.editors[data.key].cancelIPE();
      Drupal.PanelsIPE.editors[data.key].endEditing();
    }
  };

  Drupal.ajax.prototype.commands.unlockIPE = function(ajax, data, status) {
    if (confirm(data.message)) {
      var ajaxOptions = ajax.options;
      ajaxOptions.url = data.break_path;
      $.ajax(ajaxOptions);
    }
    else {
      Drupal.PanelsIPE.editors[data.key].endEditing();
    }
  };

  Drupal.ajax.prototype.commands.endIPE = function(ajax, data, status) {
    if (Drupal.PanelsIPE.editors[data.key]) {
      Drupal.PanelsIPE.editors[data.key].endEditing();
    }
  };

  Drupal.ajax.prototype.commands.insertNewPane = function(ajax, data, status) {
    IPEContainerSelector = '#panels-ipe-regionid-' + data.regionId + ' div.panels-ipe-sort-container';
    firstPaneSelector = IPEContainerSelector + ' div.panels-ipe-portlet-wrapper:first';
    // Insert the new pane before the first existing pane in the region, if
    // any.
    if ($(firstPaneSelector).length) {
      insertData = {
        'method': 'before',
        'selector': firstPaneSelector,
        'data': data.renderedPane,
        'settings': null
      }
      Drupal.ajax.prototype.commands.insert(ajax, insertData, status);
    }
    // Else, insert it as a first child of the container. Doing so might fall
    // outside of the wrapping markup for the style, but it's the best we can
    // do.
    else {
      insertData = {
        'method': 'prepend',
        'selector': IPEContainerSelector,
        'data': data.renderedPane,
        'settings': null
      }
      Drupal.ajax.prototype.commands.insert(ajax, insertData, status);
    }
  };

  /**
   * Override the eventResponse on ajax.js so we can add a little extra
   * behavior.
   */
  Drupal.ajax.prototype.ipeReplacedEventResponse = Drupal.ajax.prototype.eventResponse;
  Drupal.ajax.prototype.eventResponse = function (element, event) {
    if (element.ipeCancelThis) {
      element.ipeCancelThis = null;
      return false;
    }

    if ($(this.element).attr('id') == 'panels-ipe-cancel') {
      if (!Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].cancelEditing()) {
        return false;
      }
    }

    var retval = this.ipeReplacedEventResponse(element, event);
    if (this.ajaxing && this.element_settings.ipe_cache_key) {
      // Move the throbber so that it appears outside our container.
      if (this.progress.element) {
        $(this.progress.element).addClass('ipe-throbber').appendTo($('body'));
      }
      Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].hideContainer();
    }
    // @TODO $('#panels-ipe-throbber-backdrop').remove();
    return retval;
  };

  /**
   * Override the eventResponse on ajax.js so we can add a little extra
   * behavior.
   */
  Drupal.ajax.prototype.ipeReplacedError = Drupal.ajax.prototype.error;
  Drupal.ajax.prototype.error = function (response, uri) {
    var retval = this.ipeReplacedError(response, uri);
    if (this.element_settings.ipe_cache_key) {
      Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].showContainer();
    }
  };

  Drupal.ajax.prototype.ipeReplacedBeforeSerialize = Drupal.ajax.prototype.beforeSerialize;
  Drupal.ajax.prototype.beforeSerialize = function (element_settings, options) {
    if ($(this.element).hasClass('panels-ipe-save')) {
      Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].saveEditing();
    };
    return this.ipeReplacedBeforeSerialize(element_settings, options);
  };

});

/**
 * Apply margin to bottom of the page.
 *
 * Note that directly applying marginBottom does not work in IE. To prevent
 * flickering/jumping page content with client-side caching, this is a regular
 * Drupal behavior.
 *
 * @see admin_menu.js via https://drupal.org/project/admin_menu
 */
Drupal.behaviors.panelsIpeMarginBottom = {
  attach: function () {
    $('body:not(.panels-ipe)').addClass('panels-ipe');
  }
};

})(jQuery);
