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

import {
  forEach,
  isArray,
  isUndefined,
  omit,
  reduce
} from 'min-dash';

function copyProperties(source, target, properties) {
  if (!isArray(properties)) {
    properties = [ properties ];
  }

  forEach(properties, function(property) {
    if (!isUndefined(source[property])) {
      target[property] = source[property];
    }
  });
}

var LOW_PRIORITY = 750;


export default function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {

  function copy(bo, clone) {
    var targetBo = bpmnFactory.create(bo.$type);

    return moddleCopy.copyElement(bo, targetBo, null, clone);
  }

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

    // do not copy business object + di for labels;
    // will be pulled from the referenced label target
    if (isLabel(element)) {
      return descriptor;
    }

    var businessObjectCopy = descriptor.businessObject = copy(businessObject, true);
    var diCopy = descriptor.di = copy(getDi(element), true);
    diCopy.bpmnElement = businessObjectCopy;

    copyProperties(businessObjectCopy, descriptor, 'name');
    copyProperties(diCopy, descriptor, 'isExpanded');

    // default sequence flow
    if (businessObject.default) {
      descriptor.default = businessObject.default.id;
    }
  });

  var referencesKey = '-bpmn-js-refs';

  function getReferences(cache) {
    return (cache[referencesKey] = cache[referencesKey] || {});
  }

  function setReferences(cache, references) {
    cache[referencesKey] = references;
  }

  function resolveReferences(descriptor, cache, references) {
    var businessObject = getBusinessObject(descriptor);

    // default sequence flows
    if (descriptor.default) {

      // relationship cannot be resolved immediately
      references[ descriptor.default ] = {
        element: businessObject,
        property: 'default'
      };
    }

    // boundary events
    if (descriptor.host) {

      // relationship can be resolved immediately
      getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]);
    }

    return omit(references, reduce(references, function(array, reference, key) {
      var element = reference.element,
          property = reference.property;

      if (key === descriptor.id) {
        element[ property ] = businessObject;

        array.push(descriptor.id);
      }

      return array;
    }, []));
  }

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

    // wire existing di + businessObject for external label
    if (isLabel(descriptor)) {
      descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
      descriptor.di = getDi(cache[ descriptor.labelTarget ]);

      return;
    }

    businessObject = descriptor.businessObject = copy(businessObject);

    di = descriptor.di = copy(di);
    di.bpmnElement = businessObject;

    copyProperties(descriptor, businessObject, [
      'isExpanded',
      'name'
    ]);

    descriptor.type = businessObject.$type;
  });

  // copy + paste processRef with participant

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

    if (!is(element, 'bpmn:Participant')) {
      return;
    }

    var participantBo = getBusinessObject(element);

    if (participantBo.processRef) {
      descriptor.processRef = copy(participantBo.processRef, true);
    }
  });

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

    if (processRef) {
      descriptor.processRef = copy(processRef);
    }
  });

  // resolve references

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

    // resolve references e.g. default sequence flow
    setReferences(
      cache,
      resolveReferences(descriptor, cache, getReferences(cache))
    );
  });

}


BpmnCopyPaste.$inject = [
  'bpmnFactory',
  'eventBus',
  'moddleCopy'
];

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

function isLabel(element) {
  return !!element.labelTarget;
}
