wysihtml5.quirks.tableCellsSelection = function(editable, editor) {

    var dom = wysihtml5.dom,
        select = {
            table: null,
            start: null,
            end: null,
            cells: null,
            select: selectCells
        },
        selection_class = "wysiwyg-tmp-selected-cell",
        moveHandler = null,
        upHandler = null;

    function init () {

        dom.observe(editable, "mousedown", function(event) {
          var target = wysihtml5.dom.getParentElement(event.target, { nodeName: ["TD", "TH"] });
          if (target) {
              handleSelectionMousedown(target);
          }
        });

        return select;
    }

    function handleSelectionMousedown (target) {
      select.start = target;
      select.end = target;
      select.cells = [target];
      select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });

      if (select.table) {
        removeCellSelections();
        dom.addClass(target, selection_class);
        moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
        upHandler = dom.observe(editable, "mouseup", handleMouseUp);
        editor.fire("tableselectstart").fire("tableselectstart:composer");
      }
    }

    // remove all selection classes
    function removeCellSelections () {
        if (editable) {
            var selectedCells = editable.querySelectorAll('.' + selection_class);
            if (selectedCells.length > 0) {
              for (var i = 0; i < selectedCells.length; i++) {
                  dom.removeClass(selectedCells[i], selection_class);
              }
            }
        }
    }

    function addSelections (cells) {
      for (var i = 0; i < cells.length; i++) {
        dom.addClass(cells[i], selection_class);
      }
    }

    function handleMouseMove (event) {
      var curTable = null,
          cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
          oldEnd;

      if (cell && select.table && select.start) {
        curTable =  dom.getParentElement(cell, { nodeName: ["TABLE"] });
        if (curTable && curTable === select.table) {
          removeCellSelections();
          oldEnd = select.end;
          select.end = cell;
          select.cells = dom.table.getCellsBetween(select.start, cell);
          if (select.cells.length > 1) {
            editor.composer.selection.deselect();
          }
          addSelections(select.cells);
          if (select.end !== oldEnd) {
            editor.fire("tableselectchange").fire("tableselectchange:composer");
          }
        }
      }
    }

    function handleMouseUp (event) {
      moveHandler.stop();
      upHandler.stop();
      editor.fire("tableselect").fire("tableselect:composer");
      setTimeout(function() {
        bindSideclick();
      },0);
    }

    function bindSideclick () {
        var sideClickHandler = dom.observe(editable.ownerDocument, "click", function(event) {
          sideClickHandler.stop();
          if (dom.getParentElement(event.target, { nodeName: ["TABLE"] }) != select.table) {
              removeCellSelections();
              select.table = null;
              select.start = null;
              select.end = null;
              editor.fire("tableunselect").fire("tableunselect:composer");
          }
        });
    }

    function selectCells (start, end) {
        select.start = start;
        select.end = end;
        select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
        selectedCells = dom.table.getCellsBetween(select.start, select.end);
        addSelections(selectedCells);
        bindSideclick();
        editor.fire("tableselect").fire("tableselect:composer");
    }

    return init();

};
