import inherits from 'inherits';

import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';

import { getBusinessObject, is } from '../../../util/ModelUtil';

import { isLabel } from '../../../util/LabelUtil';

import { getBBox } from 'diagram-js/lib/util/Elements';

import {
  assign,
  find
} from 'min-dash';

import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';

var HORIZONTAL_PARTICIPANT_PADDING = 20,
    VERTICAL_PARTICIPANT_PADDING = 20;

export var PARTICIPANT_BORDER_WIDTH = 30;

var HIGH_PRIORITY = 2000;


/**
 * BPMN-specific behavior for creating participants.
 */
export default function CreateParticipantBehavior(canvas, eventBus, modeling) {
  CommandInterceptor.call(this, eventBus);

  // fit participant
  eventBus.on([
    'create.start',
    'shape.move.start'
  ], HIGH_PRIORITY, function(event) {
    var context = event.context,
        shape = context.shape,
        rootElement = canvas.getRootElement();

    if (!is(shape, 'bpmn:Participant') ||
      !is(rootElement, 'bpmn:Process') ||
      !rootElement.children.length) {
      return;
    }

    // ignore connections, groups and labels
    var children = rootElement.children.filter(function(element) {
      return !is(element, 'bpmn:Group') &&
        !isLabel(element) &&
        !isConnection(element);
    });

    // ensure for available children to calculate bounds
    if (!children.length) {
      return;
    }

    var childrenBBox = getBBox(children);

    var participantBounds = getParticipantBounds(shape, childrenBBox);

    // assign width and height
    assign(shape, participantBounds);

    // assign create constraints
    context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
  });

  // force hovering process when creating first participant
  eventBus.on('create.start', HIGH_PRIORITY, function(event) {
    var context = event.context,
        shape = context.shape,
        rootElement = canvas.getRootElement(),
        rootElementGfx = canvas.getGraphics(rootElement);

    function ensureHoveringProcess(event) {
      event.element = rootElement;
      event.gfx = rootElementGfx;
    }

    if (is(shape, 'bpmn:Participant') && is(rootElement, 'bpmn:Process')) {
      eventBus.on('element.hover', HIGH_PRIORITY, ensureHoveringProcess);

      eventBus.once('create.cleanup', function() {
        eventBus.off('element.hover', ensureHoveringProcess);
      });
    }
  });

  // turn process into collaboration when creating first participant
  function getOrCreateCollaboration() {
    var rootElement = canvas.getRootElement();

    if (is(rootElement, 'bpmn:Collaboration')) {
      return rootElement;
    }

    return modeling.makeCollaboration();
  }

  // when creating mutliple elements through `elements.create` parent must be set to collaboration
  // and passed to `shape.create` as hint
  this.preExecute('elements.create', HIGH_PRIORITY, function(context) {
    var elements = context.elements,
        parent = context.parent,
        participant = findParticipant(elements),
        hints;

    if (participant && is(parent, 'bpmn:Process')) {
      context.parent = getOrCreateCollaboration();

      hints = context.hints = context.hints || {};

      hints.participant = participant;
      hints.process = parent;
      hints.processRef = getBusinessObject(participant).get('processRef');
    }
  }, true);

  // when creating single shape through `shape.create` parent must be set to collaboration
  // unless it was already set through `elements.create`
  this.preExecute('shape.create', function(context) {
    var parent = context.parent,
        shape = context.shape;

    if (is(shape, 'bpmn:Participant') && is(parent, 'bpmn:Process')) {
      context.parent = getOrCreateCollaboration();

      context.process = parent;
      context.processRef = getBusinessObject(shape).get('processRef');
    }
  }, true);

  // #execute necessary because #preExecute not called on CommandStack#redo
  this.execute('shape.create', function(context) {
    var hints = context.hints || {},
        process = context.process || hints.process,
        shape = context.shape,
        participant = hints.participant;

    // both shape.create and elements.create must be handled
    if (process && (!participant || shape === participant)) {

      // monkey-patch process ref
      getBusinessObject(shape).set('processRef', getBusinessObject(process));
    }
  }, true);

  this.revert('shape.create', function(context) {
    var hints = context.hints || {},
        process = context.process || hints.process,
        processRef = context.processRef || hints.processRef,
        shape = context.shape,
        participant = hints.participant;

    // both shape.create and elements.create must be handled
    if (process && (!participant || shape === participant)) {

      // monkey-patch process ref
      getBusinessObject(shape).set('processRef', processRef);
    }
  }, true);

  this.postExecute('shape.create', function(context) {
    var hints = context.hints || {},
        process = context.process || context.hints.process,
        shape = context.shape,
        participant = hints.participant;

    if (process) {
      var children = process.children.slice();

      // both shape.create and elements.create must be handled
      if (!participant) {
        modeling.moveElements(children, { x: 0, y: 0 }, shape);
      } else if (shape === participant) {
        modeling.moveElements(children, { x: 0, y: 0 }, participant);
      }
    }
  }, true);
}

CreateParticipantBehavior.$inject = [
  'canvas',
  'eventBus',
  'modeling'
];

inherits(CreateParticipantBehavior, CommandInterceptor);

// helpers //////////

function getParticipantBounds(shape, childrenBBox) {
  childrenBBox = {
    width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
    height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
  };

  var width = Math.max(shape.width, childrenBBox.width),
      height = Math.max(shape.height, childrenBBox.height);

  return {
    x: -width / 2,
    y: -height / 2,
    width: width,
    height: height
  };
}

function getParticipantCreateConstraints(shape, childrenBBox) {
  childrenBBox = asTRBL(childrenBBox);

  return {
    bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
    left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
    top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
    right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
  };
}

function isConnection(element) {
  return !!element.waypoints;
}

function findParticipant(elements) {
  return find(elements, function(element) {
    return is(element, 'bpmn:Participant');
  });
}