(function(wysihtml5) {
  var dom                     = wysihtml5.dom,
      // Following elements are grouped
      // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
      // instead of creating a H4 within a H1 which would result in semantically invalid html
      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "DIV"];

  /**
   * Remove similiar classes (based on classRegExp)
   * and add the desired class name
   */
  function _addClass(element, className, classRegExp) {
    if (element.className) {
      _removeClass(element, classRegExp);
      element.className = wysihtml5.lang.string(element.className + " " + className).trim();
    } else {
      element.className = className;
    }
  }

  function _addStyle(element, cssStyle, styleRegExp) {
    _removeStyle(element, styleRegExp);
    if (element.getAttribute('style')) {
      element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
    } else {
      element.setAttribute('style', cssStyle);
    }
  }

  function _removeClass(element, classRegExp) {
    var ret = classRegExp.test(element.className);
    element.className = element.className.replace(classRegExp, "");
    if (wysihtml5.lang.string(element.className).trim() == '') {
        element.removeAttribute('class');
    }
    return ret;
  }

  function _removeStyle(element, styleRegExp) {
    var ret = styleRegExp.test(element.getAttribute('style'));
    element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
    if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
      element.removeAttribute('style');
    }
    return ret;
  }

  function _removeLastChildIfLineBreak(node) {
    var lastChild = node.lastChild;
    if (lastChild && _isLineBreak(lastChild)) {
      lastChild.parentNode.removeChild(lastChild);
    }
  }

  function _isLineBreak(node) {
    return node.nodeName === "BR";
  }

  /**
   * Execute native query command
   * and if necessary modify the inserted node's className
   */
  function _execCommand(doc, composer, command, nodeName, className) {
    var ranges = composer.selection.getOwnRanges();
    for (var i = ranges.length; i--;){
      composer.selection.getSelection().removeAllRanges();
      composer.selection.setSelection(ranges[i]);
      if (className) {
        var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
          var target = event.target,
              displayStyle;
          if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
            return;
          }
          displayStyle = dom.getStyle("display").from(target);
          if (displayStyle.substr(0, 6) !== "inline") {
            // Make sure that only block elements receive the given class
            target.className += " " + className;
          }
        });
      }
      doc.execCommand(command, false, nodeName);

      if (eventListener) {
        eventListener.stop();
      }
    }
  }

  function _selectionWrap(composer, options) {
    if (composer.selection.isCollapsed()) {
        composer.selection.selectLine();
    }

    var surroundedNodes = composer.selection.surround(options);
    for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
      wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
      _removeLastChildIfLineBreak(surroundedNodes[i]);
    }

    // rethink restoring selection
    // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
  }

  function _hasClasses(element) {
    return !!wysihtml5.lang.string(element.className).trim();
  }

  function _hasStyles(element) {
    return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
  }

  wysihtml5.commands.formatBlock = {
    exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
      var doc             = composer.doc,
          blockElements    = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
          useLineBreaks   = composer.config.useLineBreaks,
          defaultNodeName = useLineBreaks ? "DIV" : "P",
          selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;

      if (blockElements.length) {
        composer.selection.executeAndRestoreRangy(function() {
          for (var b = blockElements.length; b--;) {
            if (classRegExp) {
              classRemoveAction = _removeClass(blockElements[b], classRegExp);
            }
            if (styleRegExp) {
              styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
            }

            if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
              // dont rename or remove element when just setting block formating class or style
              return;
            }

            var hasClasses = _hasClasses(blockElements[b]),
                hasStyles = _hasStyles(blockElements[b]);

            if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
              // Insert a line break afterwards and beforewards when there are siblings
              // that are not of type line break or block element
              wysihtml5.dom.lineBreaks(blockElements[b]).add();
              dom.replaceWithChildNodes(blockElements[b]);
            } else {
              // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
              dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
            }
          }
        });

        return;
      }

      // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
      if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
        selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
        composer.selection.executeAndRestoreRangy(function() {
          for (var n = selectedNodes.length; n--;) {
            blockElement = dom.getParentElement(selectedNodes[n], {
              nodeName: BLOCK_ELEMENTS_GROUP
            });
            if (blockElement == composer.element) {
              blockElement = null;
            }
            if (blockElement) {
                // Rename current block element to new block element and add class
                if (nodeName) {
                  blockElement = dom.renameElement(blockElement, nodeName);
                }
                if (className) {
                  _addClass(blockElement, className, classRegExp);
                }
                if (cssStyle) {
                  _addStyle(blockElement, cssStyle, styleRegExp);
                }
              blockRenameFound = true;
            }
          }

        });

        if (blockRenameFound) {
          return;
        }
      }

      _selectionWrap(composer, {
        "nodeName": (nodeName || defaultNodeName),
        "className": className || null,
        "cssStyle": cssStyle || null
      });
    },

    state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
      var nodes = composer.selection.getSelectedOwnNodes(),
          parents = [],
          parent;

      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;

      //var selectedNode = composer.selection.getSelectedNode();
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
        parent = dom.getParentElement(nodes[i], {
          nodeName:     nodeName,
          className:    className,
          classRegExp:  classRegExp,
          cssStyle:     cssStyle,
          styleRegExp:  styleRegExp
        });
        if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
          parents.push(parent);
        }
      }
      if (parents.length == 0) {
        return false;
      }
      return parents;
    }


  };
})(wysihtml5);
