import inherits from 'inherits';

import {
  find,
  isArray,
  matchPattern,
  some
} from 'min-dash';

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

import {
  add as collectionAdd,
  remove as collectionRemove
} from 'diagram-js/lib/util/Collections';

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

import { isAny } from '../util/ModelingUtil';

import { hasEventDefinition } from '../../../util/DiUtil';

var LOW_PRIORITY = 500;


/**
 * Add referenced root elements (error, escalation, message, signal) if they don't exist.
 * Copy referenced root elements on copy & paste.
 */
export default function RootElementReferenceBehavior(
    bpmnjs, eventBus, injector, moddleCopy, bpmnFactory
) {
  injector.invoke(CommandInterceptor, this);

  function canHaveRootElementReference(element) {
    return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) ||
      hasAnyEventDefinition(element, [
        'bpmn:ErrorEventDefinition',
        'bpmn:EscalationEventDefinition',
        'bpmn:MessageEventDefinition',
        'bpmn:SignalEventDefinition'
      ]);
  }

  function hasRootElement(rootElement) {
    var definitions = bpmnjs.getDefinitions(),
        rootElements = definitions.get('rootElements');

    return !!find(rootElements, matchPattern({ id: rootElement.id }));
  }

  function getRootElementReferencePropertyName(eventDefinition) {
    if (is(eventDefinition, 'bpmn:ErrorEventDefinition')) {
      return 'errorRef';
    } else if (is(eventDefinition, 'bpmn:EscalationEventDefinition')) {
      return 'escalationRef';
    } else if (is(eventDefinition, 'bpmn:MessageEventDefinition')) {
      return 'messageRef';
    } else if (is(eventDefinition, 'bpmn:SignalEventDefinition')) {
      return 'signalRef';
    }
  }

  function getRootElement(businessObject) {
    if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
      return businessObject.get('messageRef');
    }

    var eventDefinitions = businessObject.get('eventDefinitions'),
        eventDefinition = eventDefinitions[ 0 ];

    return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
  }

  function setRootElement(businessObject, rootElement) {
    if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
      return businessObject.set('messageRef', rootElement);
    }

    var eventDefinitions = businessObject.get('eventDefinitions'),
        eventDefinition = eventDefinitions[ 0 ];

    return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement);
  }

  // create shape
  this.executed('shape.create', function(context) {
    var shape = context.shape;

    if (!canHaveRootElementReference(shape)) {
      return;
    }

    var businessObject = getBusinessObject(shape),
        rootElement = getRootElement(businessObject),
        rootElements;

    if (rootElement && !hasRootElement(rootElement)) {
      rootElements = bpmnjs.getDefinitions().get('rootElements');

      // add root element
      collectionAdd(rootElements, rootElement);

      context.addedRootElement = rootElement;
    }
  }, true);

  this.reverted('shape.create', function(context) {
    var addedRootElement = context.addedRootElement;

    if (!addedRootElement) {
      return;
    }

    var rootElements = bpmnjs.getDefinitions().get('rootElements');

    // remove root element
    collectionRemove(rootElements, addedRootElement);
  }, true);

  eventBus.on('copyPaste.copyElement', function(context) {
    var descriptor = context.descriptor,
        element = context.element;

    if (!canHaveRootElementReference(element)) {
      return;
    }

    var businessObject = getBusinessObject(element),
        rootElement = getRootElement(businessObject);

    if (rootElement) {
      descriptor.referencedRootElement = rootElement;
    }
  });

  eventBus.on('copyPaste.pasteElement', LOW_PRIORITY, function(context) {
    var descriptor = context.descriptor,
        businessObject = descriptor.businessObject;

    if (!canHaveRootElementReference(businessObject)) {
      return;
    }

    var referencedRootElement = descriptor.referencedRootElement;

    if (!referencedRootElement) {
      return;
    }

    if (!hasRootElement(referencedRootElement)) {
      referencedRootElement = moddleCopy.copyElement(
        referencedRootElement,
        bpmnFactory.create(referencedRootElement.$type)
      );
    }

    setRootElement(businessObject, referencedRootElement);
  });
}

RootElementReferenceBehavior.$inject = [
  'bpmnjs',
  'eventBus',
  'injector',
  'moddleCopy',
  'bpmnFactory'
];

inherits(RootElementReferenceBehavior, CommandInterceptor);

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

function hasAnyEventDefinition(element, types) {
  if (!isArray(types)) {
    types = [ types ];
  }

  return some(types, function(type) {
    return hasEventDefinition(element, type);
  });
}