/**
 * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
 *
 *   #1 caret in unformatted text:
 *      abcdefg|
 *   output:
 *      abcdefg<b>|</b>
 *
 *   #2 unformatted text selected:
 *      abc|deg|h
 *   output:
 *      abc<b>|deg|</b>h
 *
 *   #3 unformatted text selected across boundaries:
 *      ab|c <span>defg|h</span>
 *   output:
 *      ab<b>|c </b><span><b>defg</b>|h</span>
 *
 *   #4 formatted text entirely selected
 *      <b>|abc|</b>
 *   output:
 *      |abc|
 *
 *   #5 formatted text partially selected
 *      <b>ab|c|</b>
 *   output:
 *      <b>ab</b>|c|
 *
 *   #6 formatted text selected across boundaries
 *      <span>ab|c</span> <b>de|fgh</b>
 *   output:
 *      <span>ab|c</span> de|<b>fgh</b>
 */
(function(wysihtml5) {
  var // Treat <b> as <strong> and vice versa
      ALIAS_MAPPING = {
        "strong": "b",
        "em":     "i",
        "b":      "strong",
        "i":      "em"
      },
      htmlApplier = {};

  function _getTagNames(tagName) {
    var alias = ALIAS_MAPPING[tagName];
    return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
  }

  function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
    var identifier = tagName;
    
    if (className) {
      identifier += ":" + className;
    }
    if (cssStyle) {
      identifier += ":" + cssStyle;
    }

    if (!htmlApplier[identifier]) {
      htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
    }

    return htmlApplier[identifier];
  }

  wysihtml5.commands.formatInline = {
    exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
      var range = composer.selection.createRange(),
          ownRanges = composer.selection.getOwnRanges();

      if (!ownRanges || ownRanges.length == 0) {
        return false;
      }
      composer.selection.getSelection().removeAllRanges();

      _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);

      if (!dontRestoreSelect) {
        range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
        range.setEnd(
          ownRanges[ownRanges.length - 1].endContainer,
          ownRanges[ownRanges.length - 1].endOffset
        );
        composer.selection.setSelection(range);
        composer.selection.executeAndRestore(function() {
          if (!noCleanup) {
            composer.cleanUp();
          }
        }, true, true);
      } else if (!noCleanup) {
        composer.cleanUp();
      }
    },

    // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
    // It is achieved by selecting the entire state element before executing.
    // This works on built in contenteditable inline format commands
    execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
      var that = this;

      if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
        composer.selection.isCollapsed() &&
        !composer.selection.caretIsLastInSelection() &&
        !composer.selection.caretIsFirstInSelection()
      ) {
        var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
        composer.selection.executeAndRestoreRangy(function() {
          var parent = state_element.parentNode;
          composer.selection.selectNode(state_element, true);
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
        });
      } else {
        if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
          composer.selection.executeAndRestoreRangy(function() {
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
          });
        } else {
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
        }
      }
    },

    state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
      var doc           = composer.doc,
          aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
          ownRanges, isApplied;

      // Check whether the document contains a node with the desired tagName
      if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
          !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
        return false;
      }

       // Check whether the document contains a node with the desired className
      if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
         return false;
      }

      ownRanges = composer.selection.getOwnRanges();

      if (!ownRanges || ownRanges.length === 0) {
        return false;
      }

      isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);

      return (isApplied && isApplied.elements) ? isApplied.elements : false;
    }
  };
})(wysihtml5);
