function Connect(eventBus, dragging, modeling, rules) {

  // rules

  function canConnect(source, target) {
    return rules.allowed('connection.create', {
      source: source,
      target: target
    });
  }

  function canConnectReverse(source, target) {
    return canConnect(target, source);
  }


  // event handlers

  eventBus.on('connect.hover', function(event) {
    var context = event.context,
        start = context.start,
        hover = event.hover,
        canExecute;

    // cache hover state
    context.hover = hover;

    canExecute = context.canExecute = canConnect(start, hover);

    // ignore hover
    if (isNil(canExecute)) {
      return;
    }

    if (canExecute !== false) {
      context.source = start;
      context.target = hover;

      return;
    }

    canExecute = context.canExecute = canConnectReverse(start, hover);

    // ignore hover
    if (isNil(canExecute)) {
      return;
    }

    if (canExecute !== false) {
      context.source = hover;
      context.target = start;
    }
  });

  eventBus.on([ 'connect.out', 'connect.cleanup' ], function(event) {
    var context = event.context;

    context.hover = null;
    context.source = null;
    context.target = null;

    context.canExecute = false;
  });

  eventBus.on('connect.end', function(event) {
    var context = event.context,
        canExecute = context.canExecute,
        connectionStart = context.connectionStart,
        connectionEnd = {
          x: event.x,
          y: event.y
        },
        source = context.source,
        target = context.target;

    if (!canExecute) {
      return false;
    }

    var attrs = null,
        hints = {
          connectionStart: isReverse(context) ? connectionEnd : connectionStart,
          connectionEnd: isReverse(context) ? connectionStart : connectionEnd
        };

    if (isObject(canExecute)) {
      attrs = canExecute;
    }

    context.connection = modeling.connect(source, target, attrs, hints);
  });


  // API

  /**
   * Start connect operation.
   *
   * @param {MouseEvent|TouchEvent} event
   * @param {Element} start
   * @param {Point} [connectionStart]
   * @param {boolean} [autoActivate=false]
   */
  this.start = function(event, start, connectionStart, autoActivate) {
    if (!isObject(connectionStart)) {
      autoActivate = connectionStart;
      connectionStart = getMid(start);
    }

    dragging.init(event, 'connect', {
      autoActivate: autoActivate,
      data: {
        shape: start,
        context: {
          start: start,
          connectionStart: connectionStart
        }
      }
    });
  };
}
@param {EventBus} eventBus
@param {Dragging} dragging
@param {Modeling} modeling
@param {Rules} rules