"""A module for converting services to BPMN."""
from collections import OrderedDict
import xml.dom.minidom
from lxml import etree
from lxml.etree import QName

_DIGITAL_STR = 'digital'
_TITLE_STR = 'title'
_IMPLEMENTATION_STR = 'implementation'
_CHILD_STR = 'child'
_SEMANTIC_STR = 'semantic'
_DEFINITIONS_STR = 'definitions'
_TIMER_STR = 'Timer'
_MIN_STR = 'min'
_MAX_STR = 'max'
_NUM_ID_SUFFIX = 'num_id'
_PREV_CHILD_SUFFIX = f'previous_{_CHILD_STR}'

_MAX_DURATION_TEMPLATE = {
    'Λεπτά': 'PT{}M',
    'Ώρες': 'PT{}Η',
    'Ημέρες': 'P{}DS',
    'Εβδομάδες': 'P{}WS',
    'Μήνες': 'P{}MS'
}

def bracketize(string):
    return f'{{{string}}}'

def getMaxDurationAsString(timer):
    duration_type = timer[2]
    try:
        template = _MAX_DURATION_TEMPLATE[duration_type]
    except KeyError:
        return ''
    max_duration = timer[1]
    min_duration = timer[0]
    out = max(float(max_duration), float(min_duration))
    return template.format(out)

class BPMNNamespaces:
    def __init__(self, semantic, bpmndi, di, dc, xsi, NSMAP, di_NAMESPACE,
                 xsi_NAMESPACE):
        self.semantic = semantic
        self.bpmndi = bpmndi
        self.di = di
        self.dc = dc
        self.xsi = xsi
        self.NSMAP = NSMAP
        self.di_NAMESPACE = di_NAMESPACE
        self.xsi_NAMESPACE = xsi_NAMESPACE

class BPMN:
    # Keys
    _SEMANTIC_KEY = _SEMANTIC_STR
    _DEFINITIONS_KEY = _DEFINITIONS_STR
    _BPMNDI_KEY = 'bpmndi'
    _DI_KEY = 'di'
    _DC_KEY = 'dc'
    _XSI_KEY = 'xsi'
    _XML_TARGET_KEY = 'xml_target'
    _PROCESS_EVIDENCE_DESCR_KEY = 'process_evidence_description'
    _NAME_KEY = 'name'
    _DOCUMENTATION_KEY = 'documentation'
    _RESOLUTION_KEY = 'resolution'
    _TYPE_KEY = 'type'
    _FIELDS_KEY = 'fields'
    _WIDTH_KEY = 'width'
    _HEIGHT_KEY = 'height'
    # Prefixes
    _SEQUENCE_FLOW_PREFIX = 'SequenceFlow_'
    _START_EVENT_PREFIX = 'StartEvent_'
    _END_EVENT_PREFIX = 'EndEvent_'
    _PR_PREFIX = 'pr_'
    _TASK_PREFIX = 'Task_'
    _SUBTASK_PREFIX = 'Subtask_'
    _PARTICIPANT_PREFIX = 'participant_'
    _EXCLUSIVE_GATEWAY_PREFIX = 'ExclusiveGateway_'
    _C_PREFIX = 'C'
    _TIMER_PREFIX = f'{_TIMER_STR}_'
    # Suffixes
    _WAYPOINT_SUFFIX = 'waypoint'
    _START_EVENT_SUFFIX = 'startEvent'
    _OUTGOING_SUFFIX = 'outgoing'
    _SEQUENCE_FLOW_SUFFIX = 'sequenceFlow'
    _END_EVENT_SUFFIX = 'endEvent'
    _DATA_OBJECT_SUFFIX = 'dataObject'
    _DATA_OBJECT_REFERENCE_SUFFIX = 'dataObjectReference'
    _DATA_OBJECT_PREFIX = 'DataObject_'
    _DATA_OBJECT_REFERENCE_PREFIX = 'DataObjectReference_'
    _INCOMING_SUFFIX = 'incoming'
    _TASK_SUFFIX = 'task'
    _MANUAL_TASK_SUFFIX = 'manualTask'
    _SERVICE_TASK_SUFFIX = 'serviceTask'
    _COLLABORATION_SUFFIX = 'collaboration'
    _PARTICIPANT_SUFFIX = 'participant'
    _EXCLUSIVE_GATEWAY_SUFFIX = 'exclusiveGateway'
    _1_SUFFIX = '_1'
    _LAST_SUFFIX = '_last'
    _DI_SUFFIX = '_di'
    _1_DI_SUFFIX = '_1_di'
    _LAST_DI_SUFFIX = '_last_di'
    _BOUNDS_SUFFIX = 'Bounds'
    _SHAPE_SUFFIX = 'BPMNShape'
    _LABEL_SUFFIX = 'BPMNLabel'
    _EDGE_SUFFIX = 'BPMNEdge'
    _DIAGRAM_SUFFIX = 'BPMNDiagram'
    _PLANE_SUFFIX = 'BPMNPlane'
    _BOUNDARY_EVENT_SUFFIX = 'boundaryEvent'
    _TIMER_EVENT_DEF_SUFFIX = 'timerEventDefinition'
    _TIME_DURATION_SUFFIX = 'timeDuration'
    # Process evidences
    _PROCESS_EVIDENCES_STR = 'Process evidences'
    _PROCESS_EVIDENCE_PREFIX = 'process_evidence'
    # Process steps
    _PROCESS_STEPS_STR = 'Process steps'
    _PROCESS_STEPS_DIGITAL_STR = 'Process steps digital'
    _PROCESS_STEP_PREFIX = 'process_step'
    _PROCESS_STEP_TITLE = None
    _PROCESS_STEP_IMPLEMENTATION = None
    _PROCESS_STEP_CHILD = None
    _PROCESS_STEP_PREVIOUS_CHILD = None

    # Process steps duration
    _PROCESS_STEP_DURATION_MAX = 'process_step_duration_max'
    _PROCESS_STEP_DURATION_MIN = 'process_step_duration_min'
    _PROCESS_STEP_DURATION_TYPE = 'process_step_duration_type'
    _PROCESS_STEP_DIGITAL_DURATION_MAX = 'process_step_digital_duration_max'
    _PROCESS_STEP_DIGITAL_DURATION_MIN = 'process_step_digital_duration_min'
    _PROCESS_STEP_DIGITAL_DURATION_TYPE = 'process_step_digital_duration_type'
    _TFORMALEXPRESSION = 'tFormalExpression'

    _MAX_TEXT_LEN = 120

    # X
    _X_SHAPE = 100
    # Y
    _Y_SHAPE = 218
    _Y_SHAPE_VARIANT = 221
    _Y_SHAPE_VARIANT_2 = 257
    _Y_SHAPE_VARIANT_3 = 111
    _Y_LABEL = 230
    _Y_LABEL_VARIANT = 190
    _Y_START = 240
    # Widths
    _WIDTH_TASK = 100
    _WIDTH_ARROW = 50
    _WIDTH_RHOMBUS = 50
    _WIDTH_START_EVENT = 36
    _WIDTH_LABEL = 90
    _WIDTH_LABEL_VARIANT = 30
    _WIDTH_LABEL_VARIANT_2 = 190
    _WIDTH_DOC_ICON = 36
    # Heights
    _HEIGHT_LABEL = 40
    _HEIGHT_LABEL_VARIANT = _WIDTH_LABEL_VARIANT
    _HEIGHT_RHOMBUS = _WIDTH_RHOMBUS
    _HEIGHT_DOC_ICON = 50
    # Offsets
    _OFFSET_BOUNDS = 15
    _OFFSET_X_LABEL = 19
    _OFFSET_Y_LABEL = 10
    _OFFSET_HANDLE_PLAIN_NODE_SHAPES = 50
    _OFFSET_X_APPEND_SHAPES_AND_EDGES = 150
    _OFFSET_DOC_TAB = 200
    _OFFSET_X_DOC = 100
    _OFFSET_Y_DOC = 150
    _OFFSET_EVIDENCES_Y_LABEL = 60
    _OFFSET_BOUNDS_WIDTH = 50
    # Multipliers
    _MULTIPLIER_BOX_HEIGHT = 120
    _MULTIPLIER_TASK_TEXT_HEIGHT = 27
    _MULTIPLIER_EVIDENCES_TEXT_HEIGHT = 10
    # Divisors
    _DIVISOR_BOX_HEIGHT = 25
    _DIVISOR_TASK_ROWS = 9
    _DIVISOR_EVIDENCES_ROWS = 6.5
    # Iraklis stuff
    # Widths
    _TASK_WIDTH = 100
    _ARROW_WIDTH = 50
    _RHOMBUS_WIDTH = 50
    _START_EVENT_WIDTH = 36
    # HEIGHTS
    _CHAINS_y_OFFSET = 120
    # Namespaces
    _NAMESPACE = {
        _SEMANTIC_KEY: 'http://www.omg.org/spec/BPMN/20100524/MODEL',
        _BPMNDI_KEY: 'http://www.omg.org/spec/BPMN/20100524/DI',
        _DI_KEY: 'http://www.omg.org/spec/DD/20100524/DI',
        _DC_KEY: 'http://www.omg.org/spec/DD/20100524/DC',
        _XSI_KEY: 'http://www.w3.org/2001/XMLSchema-instance',
        _XML_TARGET_KEY: 'http://www.trisotech.com/definitions/_1276276944297'
    }
    # Attributes
    _NAME_ATTR_PREFIX = 'Collaboration Diagram for '
    _DOCUMENTATION_ATTR = ''
    _RESOLUTION_ATTR = '96.00000267028808'
    _TYPE_ATTR = 'dc:Point'
    # Other
    _SEQUENCE_FLOW_0 = 'SequenceFlow_0'
    _START_EVENT_0 = 'StartEvent_0'
    _START_EVENT_NAME = 'Έναρξη'
    _END_EVENT_NAME = 'Λήξη'
    _SELECTION_NAME = 'Επιλογή'
    _MANUAL_TASK = 'Χειροκίνητη ενέργεια'
    _SERVICE_TASK = 'Ενέργεια μέσω λογισμικού'
    _TASK_1_NAME = 'Task_1'
    _PROCESS_STR = 'process'
    _LANE_SET_STR = 'laneSet'
    _IS_EXECUTABLE_FALSE = 'false'
    _IS_MARKER_VISIBLE_TRUE = 'true'
    _IS_HORIZONTAL_TRUE = 'true'
    _PROCESS_STEP_CHILD_YES = 'Ναι'
    _TRISOTECH_ID = 'Trisotech.Visio_'
    _TRISOTECH_ID_6 = 'Trisotech.Visio-_6'
    _PLANE_FIND_ALL_QUERY = './bpmndi:BPMNShape[@isHorizontal]/dc:Bounds'

    def __init__(self, digital_steps=False):
        if digital_steps:
            self._PROCESS_STEPS_STR += f' {_DIGITAL_STR}'
            process_step_prefix = f'{self._PROCESS_STEP_PREFIX}_{_DIGITAL_STR}'
        else:
            process_step_prefix = self._PROCESS_STEP_PREFIX
        self._PROCESS_STEP_TITLE = f'{process_step_prefix}_{_TITLE_STR}'
        self._PROCESS_STEP_IMPLEMENTATION = \
            f'{process_step_prefix}_{_IMPLEMENTATION_STR}'
        self._PROCESS_STEP_NUM_ID = f'{process_step_prefix}_{_NUM_ID_SUFFIX}'
        self._PROCESS_STEP_CHILD = f'{process_step_prefix}_{_CHILD_STR}'
        self._PROCESS_STEP_PREVIOUS_CHILD = f'{process_step_prefix}_'\
            f'{_PREV_CHILD_SUFFIX}'

        self._ns = BPMNNamespaces(
            semantic=bracketize(self._NAMESPACE[self._SEMANTIC_KEY]),
            bpmndi=bracketize(self._NAMESPACE[self._BPMNDI_KEY]),
            di=bracketize(self._NAMESPACE[self._DI_KEY]),
            dc=bracketize(self._NAMESPACE[self._DC_KEY]),
            xsi=bracketize(self._NAMESPACE[self._XSI_KEY]),
            NSMAP=self._NAMESPACE,
            di_NAMESPACE=self._NAMESPACE[self._DI_KEY],
            xsi_NAMESPACE=self._NAMESPACE[self._XSI_KEY])
        self._process_steps = None

    def group_options(self, opts):
        chains = {}
        for n in opts:
            curr = n.get(self._PROCESS_STEP_NUM_ID)
            prev = n.get(self._PROCESS_STEP_PREVIOUS_CHILD)
            if chains.get(prev) is not None:
                arr = chains.get(prev)
                arr.append(n)
                chains.pop(prev, None)
                chains[curr] = arr
            else:
                arr = []
                arr.append(n)
                chains[curr] = arr
        maxchainlength = 0
        for k in chains:
            if len(chains.get(k)) > maxchainlength:
                maxchainlength = len(chains.get(k))
        return chains, maxchainlength

    def _create_waypoint(self, BPMNEdge, x, y):
        waypoint = etree.SubElement(
            BPMNEdge,
            self._ns.di + self._WAYPOINT_SUFFIX,
            x=str(x),
            y=str(y),
            nsmap=self._ns.NSMAP)
        return waypoint

    def _appendStartEventTree(self, process, allnodes):
        """Append the xml elements of the StartEvent"""
        task = etree.SubElement(
            process, self._ns.semantic + self._START_EVENT_SUFFIX,
            id=self._START_EVENT_0,
            name=self._START_EVENT_NAME, nsmap=self._ns.NSMAP)
        outgoing = etree.SubElement(
            task, self._ns.semantic + self._OUTGOING_SUFFIX,
            nsmap=self._ns.NSMAP)
        outgoing.text = self._SEQUENCE_FLOW_0
        allnodes.append(self._START_EVENT_0)

    def _appendDataObjects(self, definition, process, data):
        stepcount = 0
        # Parse the Evidences and add a dataObject and a dataObjectReference
        # for each
        for evidence_num, evidence in self._process_evidences.items():
            stepcount += 1
            evid_name = evidence[self._PROCESS_EVIDENCE_DESCR_KEY]
            if evid_name is not None:
                if len(evid_name) > self._MAX_TEXT_LEN:
                    evid_name = evid_name[:self._MAX_TEXT_LEN] + '...'
                etree.SubElement(
                    process,
                    self._ns.semantic + self._DATA_OBJECT_SUFFIX,
                    id=self._DATA_OBJECT_PREFIX + str(stepcount))
                etree.SubElement(
                    process,
                    self._ns.semantic + self._DATA_OBJECT_REFERENCE_SUFFIX,
                    id=self._DATA_OBJECT_REFERENCE_PREFIX + str(stepcount),
                    name=evid_name,
                    dataObjectRef=self._DATA_OBJECT_PREFIX + str(stepcount))

    def _appendEndNodes(self, data, process, allnodes, options, stepcount):
        """Append the xml elements of the EndEvent"""
        stepname = self._END_EVENT_NAME
        task = etree.SubElement(process,
                                self._ns.semantic + self._END_EVENT_SUFFIX,
                                id=self._END_EVENT_PREFIX + str(stepcount + 1),
                                name=stepname, nsmap=self._ns.NSMAP)
        incoming = etree.SubElement(
            task, self._ns.semantic + self._INCOMING_SUFFIX,
            nsmap=self._ns.NSMAP)
        incoming.text = self._SEQUENCE_FLOW_PREFIX + str(stepcount)
        allnodes.append(self._END_EVENT_PREFIX + str(stepcount + 1))
        task = etree.SubElement(
            process, self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
            id=(self._SEQUENCE_FLOW_PREFIX + str(stepcount)),
            sourceRef=allnodes[stepcount],
            targetRef=allnodes[stepcount + 1],
            nsmap=self._ns.NSMAP)

    def _appendProcessTree(self, definition, process_name, data):
        process = etree.SubElement(definition,
                                   self._ns.semantic + self._PROCESS_STR,
                                   id=self._PR_PREFIX + str(
                                       hash(process_name)),
                                   isExecutable=self._IS_EXECUTABLE_FALSE,
                                   nsmap=self._ns.NSMAP)
        etree.SubElement(process, self._ns.semantic + self._LANE_SET_STR,
                         nsmap=self._ns.NSMAP)
        stepcount = 0
        allnodes = []
        options = []
        branched = False
        # Add a default startEvent
        self._appendStartEventTree(process, allnodes)
        # Parse the process steps and either add plainEventNodes, or
        # put the branchNodes in a list and handle them when the first
        # plainEventNode appears
        for step_num, step in self._process_steps.items():
            stepcount += 1
            if step.get(self._PROCESS_STEP_TITLE) is not None:
                if step.get(self._PROCESS_STEP_CHILD) == \
                        self._PROCESS_STEP_CHILD_YES:
                    options.append(step)
                    allnodes.append(self._SUBTASK_PREFIX + str(stepcount))
                    branched = True
                else:
                    self._handlePlainNodes(data, process, allnodes, options,
                                           stepcount)
                    allnodes.append(self._TASK_PREFIX + str(stepcount))
                    options = []
                    if not branched:
                        etree.SubElement(
                            process,
                            self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
                            id=self._SEQUENCE_FLOW_PREFIX + str(stepcount - 1),
                            sourceRef=allnodes[stepcount - 1],
                            targetRef=allnodes[stepcount],
                            nsmap=self._ns.NSMAP)
                    branched = False
        # Add a default endEvent
        self._appendEndNodes(data, process, allnodes, options, stepcount)
        return process

    def _handlePlainNodes(self, data, process, allnodes, options, stepcount):
        process_steps = self._process_steps
        stepname = process_steps[stepcount].get(self._PROCESS_STEP_TITLE)
        stepimpl = process_steps[stepcount].get(
            self._PROCESS_STEP_IMPLEMENTATION)
        if stepimpl == self._MANUAL_TASK:
            steptype = self._MANUAL_TASK_SUFFIX
        elif stepimpl == self._SERVICE_TASK:
            steptype = self._SERVICE_TASK_SUFFIX
        else:
            steptype = self._TASK_SUFFIX
        step = process_steps[stepcount]
        if self._PROCESS_STEPS_STR == self._PROCESS_STEPS_DIGITAL_STR:
            timer = (step.get(self._PROCESS_STEP_DIGITAL_DURATION_MIN),
                     step.get(self._PROCESS_STEP_DIGITAL_DURATION_MAX),
                     step.get(self._PROCESS_STEP_DIGITAL_DURATION_TYPE))
        else:
            timer = (step.get(self._PROCESS_STEP_DURATION_MIN),
                     step.get(self._PROCESS_STEP_DURATION_MAX),
                     step.get(self._PROCESS_STEP_DURATION_TYPE))

        # If no BranchNodes are found
        if len(options) == 0:
            task = etree.SubElement(process,
                                    self._ns.semantic + steptype,
                                    id=self._TASK_PREFIX + str(stepcount),
                                    name=stepname, nsmap=self._ns.NSMAP)
            name = f'{_MIN_STR}:{timer[0]}, {_MAX_STR}:{timer[1]} {timer[2]}'
            boundaryEvent = etree.SubElement(
                process, self._ns.semantic + self._BOUNDARY_EVENT_SUFFIX,
                id=self._TIMER_PREFIX + str(stepcount),
                # name=self._TASK_PREFIX + str(stepcount)+"_Duration",
                name=name,
                attachedToRef=self._TASK_PREFIX + str(stepcount),
                nsmap=self._ns.NSMAP)
            timerEvent = etree.SubElement(
                boundaryEvent,
                self._ns.semantic + self._TIMER_EVENT_DEF_SUFFIX)
            timeDuration = etree.SubElement(
                timerEvent, self._ns.semantic + self._TIME_DURATION_SUFFIX)
            timeDuration.attrib[
                QName(self._ns.xsi_NAMESPACE, self._TYPE_KEY)] = \
                _SEMANTIC_STR + ':' + self._TFORMALEXPRESSION
            timeDuration.text = getMaxDurationAsString(timer)
            incoming = etree.SubElement(
                task, self._ns.semantic + self._INCOMING_SUFFIX,
                nsmap=self._ns.NSMAP)
            incoming.text = self._SEQUENCE_FLOW_PREFIX + str(stepcount - 1)
            outgoing = etree.SubElement(
                task, self._ns.semantic + self._OUTGOING_SUFFIX,
                nsmap=self._ns.NSMAP)
            outgoing.text = self._SEQUENCE_FLOW_PREFIX + str(stepcount)
        # If there BranchNodes are found from the previous event
        else:
            chains, maxchainlength = self.group_options(options)
            self._addBranchNodes(options, chains, process, allnodes, stepcount)
            self._addMergeNodes(options, chains, process,
                                allnodes, stepcount, stepname,
                                steptype, timer, False)
            options = []

    def _appendCollaboration(self, definition, process_name):
        collaboration = etree.SubElement(
            definition,
            self._ns.semantic + self._COLLABORATION_SUFFIX,
            id=self._C_PREFIX + str(hash(process_name)),
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            collaboration, self._ns.semantic + self._PARTICIPANT_SUFFIX,
            id=self._PARTICIPANT_PREFIX + str(hash(process_name)),
            name=process_name,
            processRef=self._PR_PREFIX + str(hash(process_name)),
            nsmap=self._ns.NSMAP)

    def _addBranchNodes(self, options, chains, process, allnodes, stepcount):
        # Add an exclusiveGateway node, with an incoming edge
        exclusiveGateway = etree.SubElement(
            process, self._ns.semantic + self._EXCLUSIVE_GATEWAY_SUFFIX,
            id=self._EXCLUSIVE_GATEWAY_PREFIX + str(stepcount - len(options)),
            name=self._SELECTION_NAME, nsmap=self._ns.NSMAP)
        incoming = etree.SubElement(
            exclusiveGateway,
            self._ns.semantic + self._INCOMING_SUFFIX,
            nsmap=self._ns.NSMAP)
        incoming.text = self._SEQUENCE_FLOW_PREFIX + \
            str(stepcount - len(options) - 1)
        # Add a sequenceFlow for the exclusiveGateway's incoming edge
        etree.SubElement(
            process, self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
            id=self._SEQUENCE_FLOW_PREFIX + str(stepcount - len(options) - 1),
            sourceRef=allnodes[stepcount - len(options) - 1],
            targetRef=self._EXCLUSIVE_GATEWAY_PREFIX + str(
                stepcount - len(options)),
            nsmap=self._ns.NSMAP)
        # Add:
        # i) the outgoing edges
        # ii) the sequenceFlows for the outgoing edges,
        # iii) the subtasks,
        # iv) their incoming and outgoing edges,
        # v) the respective SequenceFlows for these edges
        subchaincount = 0
        for key in chains:
            chain = chains.get(key)
            subchaincount += 1
            #  Handle the 1st node of each chain
            step = chain[0]

            stepimpl = step.get(self._PROCESS_STEP_IMPLEMENTATION)
            if stepimpl == self._MANUAL_TASK:
                steptype = self._MANUAL_TASK_SUFFIX
            elif stepimpl == self._SERVICE_TASK:
                steptype = self._SERVICE_TASK_SUFFIX
            else:
                steptype = self._TASK_SUFFIX

            if self._PROCESS_STEPS_STR == self._PROCESS_STEPS_DIGITAL_STR:
                timer = (step.get(self._PROCESS_STEP_DIGITAL_DURATION_MIN),
                         step.get(self._PROCESS_STEP_DIGITAL_DURATION_MAX),
                         step.get(self._PROCESS_STEP_DIGITAL_DURATION_TYPE))
            else:
                timer = (step.get(self._PROCESS_STEP_DURATION_MIN),
                         step.get(self._PROCESS_STEP_DURATION_MAX),
                         step.get(self._PROCESS_STEP_DURATION_TYPE))

            # i)
            outgoing = etree.SubElement(
                exclusiveGateway,
                self._ns.semantic + self._OUTGOING_SUFFIX,
                nsmap=self._ns.NSMAP)
            # ii)  e.g. SequenceFlow_3_1_1, SequenceFlow_3_2_1  etc.
            outgoing.text = self._SEQUENCE_FLOW_PREFIX + \
                str(stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX
            # iii) e.g. Subtask_3_1_1, Subtask_3_2_1  etc.
            subTask = etree.SubElement(
                process, self._ns.semantic + steptype,
                id=self._SUBTASK_PREFIX + str(
                    stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX,
                name=step.get(self._PROCESS_STEP_TITLE),
                nsmap=self._ns.NSMAP)
            name = f'{_MIN_STR}:{timer[0]}, {_MAX_STR}:{timer[1]} {timer[2]}'
            boundaryEvent = etree.SubElement(
                process, self._ns.semantic + self._BOUNDARY_EVENT_SUFFIX,
                id=self._TIMER_PREFIX + str(
                    stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX,
                # name=self._TASK_PREFIX + str(stepcount)+"_Duration",
                name=name,
                attachedToRef=self._SUBTASK_PREFIX + str(
                    stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX,
                nsmap=self._ns.NSMAP)
            timerEvent = etree.SubElement(
                boundaryEvent,
                self._ns.semantic + self._TIMER_EVENT_DEF_SUFFIX)
            timeDuration = etree.SubElement(
                timerEvent, self._ns.semantic + self._TIME_DURATION_SUFFIX)
            timeDuration.attrib[
                QName(self._ns.xsi_NAMESPACE, self._TYPE_KEY)] = \
                _SEMANTIC_STR + ':' + self._TFORMALEXPRESSION
            timeDuration.text = getMaxDurationAsString(timer)
            # Set the current task as the last task of the chain
            lastTask = subTask
            # iv)
            incoming = etree.SubElement(
                subTask, self._ns.semantic + self._INCOMING_SUFFIX,
                nsmap=self._ns.NSMAP)
            incoming.text = self._SEQUENCE_FLOW_PREFIX + \
                str(stepcount - len(options)) + '_' + \
                str(subchaincount) + '_' + str(1)
            etree.SubElement(
                process,
                self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
                id=self._SEQUENCE_FLOW_PREFIX + str(
                    stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX,
                sourceRef=self._EXCLUSIVE_GATEWAY_PREFIX + str(
                    stepcount - len(options)),
                targetRef=self._SUBTASK_PREFIX + str(
                    stepcount - len(options)) + '_' + str(
                    subchaincount) + self._1_SUFFIX, nsmap=self._ns.NSMAP)
            if (len(chain) > 1):
                lastTask = self._add_chain_nodes(
                    options, chain, process, stepcount, subchaincount,
                    lastTask)
            # In all cases
            outgoing = etree.SubElement(
                lastTask, self._ns.semantic + self._OUTGOING_SUFFIX,
                nsmap=self._ns.NSMAP)
            outgoing.text = self._SEQUENCE_FLOW_PREFIX + \
                str(stepcount - len(options) + 1) + \
                '_' + str(subchaincount) + self._LAST_SUFFIX

    def _add_chain_nodes(self, options, chain, process, stepcount,
                         subchaincount, lastTask):
        """This method adds the remaining nodes of a chain.
        If the case has more than one step
        """
        substepcount = 1
        for step in chain[1:]:
            stepimpl = step.get(self._PROCESS_STEP_IMPLEMENTATION)
            if stepimpl == self._MANUAL_TASK:
                steptype = self._MANUAL_TASK_SUFFIX
            elif stepimpl == self._SERVICE_TASK:
                steptype = self._SERVICE_TASK_SUFFIX
            else:
                steptype = self._TASK_SUFFIX
            timer = (step.get(self._PROCESS_STEP_DURATION_MIN),
                     step.get(self._PROCESS_STEP_DURATION_MAX),
                     step.get(self._PROCESS_STEP_DURATION_TYPE))
            substepcount += 1
            curr_id = str(stepcount - len(options)) + '_' + \
                str(subchaincount) + '_' + str(substepcount)
            prev_id = str(stepcount - len(options)) + '_' + \
                str(subchaincount) + '_' + str(substepcount - 1)
            subTask = etree.SubElement(
                process, self._ns.semantic + steptype,
                id=self._SUBTASK_PREFIX + curr_id,
                name=step.get(self._PROCESS_STEP_TITLE),
                nsmap=self._ns.NSMAP)
            outgoing = etree.SubElement(
                lastTask, self._ns.semantic + self._OUTGOING_SUFFIX,
                nsmap=self._ns.NSMAP)
            outgoing.text = self._SEQUENCE_FLOW_PREFIX + curr_id
            incoming = etree.SubElement(
                subTask, self._ns.semantic + self._INCOMING_SUFFIX,
                nsmap=self._ns.NSMAP)
            incoming.text = self._SEQUENCE_FLOW_PREFIX + curr_id
            etree.SubElement(process,
                             self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
                             id=self._SEQUENCE_FLOW_PREFIX + curr_id,
                             sourceRef=self._SUBTASK_PREFIX + prev_id,
                             targetRef=self._SUBTASK_PREFIX + curr_id,
                             nsmap=self._ns.NSMAP)
            name = f'{_MIN_STR}:{timer[0]}, {_MAX_STR}:{timer[1]} {timer[2]}'
            boundaryEvent = etree.SubElement(
                process, self._ns.semantic + self._BOUNDARY_EVENT_SUFFIX,
                id=self._TIMER_PREFIX + curr_id,
                # name=self._TASK_PREFIX + str(stepcount)+"_Duration",
                name=name,
                attachedToRef=self._SUBTASK_PREFIX + curr_id,
                nsmap=self._ns.NSMAP)
            timerEvent = etree.SubElement(
                boundaryEvent,
                self._ns.semantic + self._TIMER_EVENT_DEF_SUFFIX)
            timeDuration = etree.SubElement(
                timerEvent, self._ns.semantic + self._TIME_DURATION_SUFFIX)
            timeDuration.attrib[
                QName(self._ns.xsi_NAMESPACE, self._TYPE_KEY)] = \
                _SEMANTIC_STR + ':' + self._TFORMALEXPRESSION
            timeDuration.text = getMaxDurationAsString(timer)
            # Set this task as the lastTask so that it can used in the next
            # iteration as the previous node
            lastTask = subTask
        return lastTask

    def _addMergeNodes(self, options, chains, process, allnodes, stepcount,
                       stepname, steptype, timer, isEndNode):
        task = etree.SubElement(process, self._ns.semantic + steptype,
                                id=self._TASK_PREFIX + str(stepcount),
                                name=stepname, nsmap=self._ns.NSMAP)
        subchaincount = 0
        outgoing = etree.SubElement(
            task, self._ns.semantic + self._OUTGOING_SUFFIX,
            nsmap=self._ns.NSMAP)
        outgoing.text = self._SEQUENCE_FLOW_PREFIX + str(stepcount)
        name = f'{_MIN_STR}:{timer[0]}, {_MAX_STR}:{timer[1]} {timer[2]}'
        boundaryEvent = etree.SubElement(
            process, self._ns.semantic + self._BOUNDARY_EVENT_SUFFIX,
            id=self._TIMER_PREFIX + str(stepcount),
            # name=self._TASK_PREFIX + str(stepcount)+"_Duration",
            name=name,
            attachedToRef=self._TASK_PREFIX + str(stepcount),
            nsmap=self._ns.NSMAP)
        timerEvent = etree.SubElement(
            boundaryEvent, self._ns.semantic + self._TIMER_EVENT_DEF_SUFFIX)
        timeDuration = etree.SubElement(
            timerEvent, self._ns.semantic + self._TIME_DURATION_SUFFIX)
        timeDuration.attrib[
            QName(self._ns.xsi_NAMESPACE, self._TYPE_KEY)] = \
            _SEMANTIC_STR + ':' + self._TFORMALEXPRESSION
        timeDuration.text = getMaxDurationAsString(timer)
        for k in chains:
            chain = chains.get(k)
            subchaincount += 1
            incoming = etree.SubElement(
                task, self._ns.semantic + self._INCOMING_SUFFIX,
                nsmap=self._ns.NSMAP)
            incoming.text = self._SEQUENCE_FLOW_PREFIX + \
                str(stepcount - len(options) + 1) + \
                '_' + str(subchaincount) + self._LAST_SUFFIX
            # Append the SequenceFlows for the merge
            # (but maybe it is better to move it to the merge)
            etree.SubElement(process,
                             self._ns.semantic + self._SEQUENCE_FLOW_SUFFIX,
                             id=self._SEQUENCE_FLOW_PREFIX + str(
                                 stepcount - len(options) + 1) + '_' + str(
                                 subchaincount) + self._LAST_SUFFIX,
                             sourceRef=self._SUBTASK_PREFIX + str(
                                 stepcount - len(options)) + '_' + str(
                                 subchaincount) + '_' + str(len(chain)),
                             targetRef=self._TASK_PREFIX + str(stepcount),
                             nsmap=self._ns.NSMAP)

    def _getChainHeights(self, options, chains, stepcount, offset):
        subchaincount = 0
        chainHeights = []
        for k in chains:
            steps = chains.get(k)
            step = steps[0]
            maxChainHeight = 0
            stepname = step.get(self._PROCESS_STEP_TITLE)
            rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
            rounded_height = int(self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)
            subchaincount += 1
            if len(steps) > 1:
                substepcount = 1
                for step in steps[1:]:
                    substepcount += 1
                    stepname = step.get(self._PROCESS_STEP_TITLE)
                    rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
                    rounded_height = int(
                        self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)
                    if rounded_height > maxChainHeight:
                        maxChainHeight = rounded_height
            else:
                stepname = step.get(self._PROCESS_STEP_TITLE)
                rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
                rounded_height = int(self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)
                maxChainHeight = rounded_height
            chainHeights.append(maxChainHeight)
        return chainHeights

    def _addBranchNodeShapes(self, options, chains, BPMNPlane, stepcount,
                             offset, xcurr):
        chainHeights = self._getChainHeights(
            options, chains, stepcount, offset)
        # Add the ExclusiveGateway shape
        mainflownodes = stepcount - len(options)
        BPMNShape = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
            id=self._EXCLUSIVE_GATEWAY_PREFIX + str(
                mainflownodes) + self._DI_SUFFIX,
            bpmnElement=self._EXCLUSIVE_GATEWAY_PREFIX + str(mainflownodes),
            isMarkerVisible=self._IS_MARKER_VISIBLE_TRUE, nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(xcurr + self._WIDTH_ARROW),
            y=str(self._Y_SHAPE),
            width=str(self._WIDTH_RHOMBUS),
            height=str(self._HEIGHT_RHOMBUS),
            nsmap=self._ns.NSMAP)
        BPMNLabel = etree.SubElement(
            BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                         x=str(xcurr + self._OFFSET_BOUNDS),
                         y=str(self._Y_LABEL_VARIANT),
                         width=str(self._WIDTH_LABEL_VARIANT),
                         height=str(self._HEIGHT_LABEL_VARIANT),
                         nsmap=self._ns.NSMAP)
        # Check if the previous flow node is the StartEvent or a simple Task
        head = self._TASK_PREFIX
        if stepcount == 2:
            head = self._START_EVENT_PREFIX
        # Draw an edge from the previous node to the ExclusiveGateway node
        BPMNEdge = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
            id=self._SEQUENCE_FLOW_PREFIX + str(
                mainflownodes - 1) + self._DI_SUFFIX,
            bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(mainflownodes - 1),
            sourceElement=head + str(stepcount - 1 - len(options)),
            targetElement=self._EXCLUSIVE_GATEWAY_PREFIX + str(mainflownodes),
            nsmap=self._ns.NSMAP)
        self._create_waypoint(
            BPMNEdge, xcurr, self._Y_START)
        self._create_waypoint(
            BPMNEdge, xcurr + self._WIDTH_ARROW, self._Y_START)

        BPMNLabel = etree.SubElement(
            BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(round((xcurr + self._WIDTH_ARROW) / 2)),
            y=str(self._Y_LABEL),
            width=str(self._WIDTH_LABEL),
            height=str(self._HEIGHT_LABEL),
            nsmap=self._ns.NSMAP)
        subchaincount = 0
        ymove = 0
        xstart = xcurr + self._WIDTH_ARROW + self._WIDTH_RHOMBUS
        xend = xstart + self._WIDTH_ARROW
        maxChainLength = 1
        gap = self._HEIGHT_LABEL

        for k in chains:
            steps = chains.get(k)
            step = steps[0]
            subchaincount += 1
            stepname = step.get(self._PROCESS_STEP_TITLE)
            rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
            rounded_height = int(self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)
            BPMNShape = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                id=self._SUBTASK_PREFIX + str(
                    mainflownodes) + '_' + str(
                    subchaincount) + self._1_DI_SUFFIX,
                bpmnElement=self._SUBTASK_PREFIX + str(
                    mainflownodes) + '_' + str(subchaincount) + self._1_SUFFIX,
                nsmap=self._ns.NSMAP)
            etree.SubElement(
                BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                x=str(xend),
                y=str(self._Y_SHAPE + ymove),
                width=str(self._WIDTH_TASK),
                # height=str(round(
                #   self._MULTIPLIER_BOX_HEIGHT * boxHeightFactor)),
                height=str(rounded_height),
                nsmap=self._ns.NSMAP)
            if subchaincount == 1:
                BPMNEdge = etree.SubElement(
                    BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
                    id=self._SEQUENCE_FLOW_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_DI_SUFFIX,
                    bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_SUFFIX,
                    sourceElement=self._EXCLUSIVE_GATEWAY_PREFIX + str(
                        mainflownodes),
                    targetElement=self._SUBTASK_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_SUFFIX,
                    nsmap=self._ns.NSMAP)
                self._create_waypoint(BPMNEdge, xstart, self._Y_START)
                self._create_waypoint(BPMNEdge, xend, self._Y_START)
                BPMNLabel = etree.SubElement(
                    BPMNEdge,
                    self._ns.bpmndi + self._LABEL_SUFFIX,
                    nsmap=self._ns.NSMAP)
                etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                                 x=str(round(xend + xstart / 2)),
                                 y=str(self._Y_LABEL),
                                 width=str(self._WIDTH_LABEL),
                                 height=str(self._HEIGHT_LABEL),
                                 nsmap=self._ns.NSMAP)

                # add timer icon in first branched node first line
                id_ = f'{self._SUBTASK_PREFIX}{mainflownodes}_' \
                      f'{subchaincount}_1_Timer_{mainflownodes}_' \
                      f'{subchaincount}_1'
                BPMNShape = etree.SubElement(
                    BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                    id=id_,
                    bpmnElement=self._TIMER_PREFIX + str(
                        mainflownodes) + '_' + str(subchaincount) + '_1',
                    nsmap=self._ns.NSMAP)
                etree.SubElement(
                    BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(xend + self._TASK_WIDTH - 15),
                    y=str(self._Y_LABEL_VARIANT + rounded_height + 10),
                    width=str(32),
                    height=str(32),
                    nsmap=self._ns.NSMAP)
                BPMNLabel = etree.SubElement(
                    BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                    nsmap=self._ns.NSMAP)
                y = str(self._Y_LABEL_VARIANT + rounded_height + 32)
                etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                                 x=str(xend + 30),
                                 y=y,
                                 width=str(self._WIDTH_LABEL_VARIANT),
                                 height=str(self._HEIGHT_LABEL_VARIANT),
                                 nsmap=self._ns.NSMAP)
            else:
                BPMNEdge = etree.SubElement(
                    BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
                    id=self._SEQUENCE_FLOW_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_DI_SUFFIX,
                    bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_SUFFIX,
                    sourceElement=self._EXCLUSIVE_GATEWAY_PREFIX + str(
                        mainflownodes),
                    targetElement=self._SUBTASK_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + self._1_SUFFIX,
                    nsmap=self._ns.NSMAP)
                self._create_waypoint(BPMNEdge, xstart, self._Y_START)
                self._create_waypoint(
                    BPMNEdge, xstart, self._Y_START + ymove)
                self._create_waypoint(
                    BPMNEdge, xend, self._Y_START + ymove)
                BPMNLabel = etree.SubElement(
                    BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
                    nsmap=self._ns.NSMAP)
                etree.SubElement(
                    BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(round(xend + xstart / 2)),
                    y=str(self._Y_LABEL + ymove),
                    width=str(self._WIDTH_LABEL),
                    height=str(self._HEIGHT_LABEL),
                    nsmap=self._ns.NSMAP)

                # add timer icon in first branched node all other lines
                BPMNShape = etree.SubElement(
                    BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                    id=self._SUBTASK_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + '_1' + '_Timer_' + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + '_1',
                    bpmnElement=self._TIMER_PREFIX + str(
                        mainflownodes) + '_' + str(
                        subchaincount) + '_1',
                    nsmap=self._ns.NSMAP)
                etree.SubElement(
                    BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(xend + self._TASK_WIDTH - 15),
                    y=str(ymove + self._Y_LABEL_VARIANT + rounded_height + 10),
                    width=str(32),
                    height=str(32),
                    nsmap=self._ns.NSMAP)
                BPMNLabel = etree.SubElement(
                    BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                    nsmap=self._ns.NSMAP)
                y = str(ymove + self._Y_LABEL_VARIANT + rounded_height + 32)
                etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                                 x=str(xend + 30),
                                 y=y,
                                 width=str(self._WIDTH_LABEL_VARIANT),
                                 height=str(self._HEIGHT_LABEL_VARIANT),
                                 nsmap=self._ns.NSMAP)

            if len(steps) > 1:
                if len(steps) > maxChainLength:
                    maxChainLength = len(steps)
                substepcount = 1
                for step in steps[1:]:
                    substepcount += 1

                    stepname = step.get(self._PROCESS_STEP_TITLE)
                    rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
                    rounded_height = int(
                        self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)

                    curr_subtask_id = str(mainflownodes) + '_' + \
                        str(subchaincount) + '_' + str(substepcount)
                    prev_subtask_id = str(mainflownodes) + '_' + \
                        str(subchaincount) + '_' + str(substepcount - 1)
                    bpmn_shape_id = self._SUBTASK_PREFIX + curr_subtask_id + \
                        self._DI_SUFFIX
                    BPMNShape = etree.SubElement(
                        BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                        id=bpmn_shape_id,
                        bpmnElement=self._SUBTASK_PREFIX + curr_subtask_id,
                        nsmap=self._ns.NSMAP)
                    xnew = xstart + (self._WIDTH_ARROW + self._WIDTH_TASK) * \
                        (substepcount - 1)
                    xcurr = xnew + (self._WIDTH_ARROW + self._WIDTH_TASK)

                    etree.SubElement(
                        BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                        x=str(xnew + self._WIDTH_ARROW),
                        y=str(self._Y_SHAPE + ymove),
                        width=str(self._WIDTH_TASK),
                        height=str(rounded_height),
                        nsmap=self._ns.NSMAP)
                    bpmn_edge_id = self._SEQUENCE_FLOW_PREFIX + \
                        curr_subtask_id + self._DI_SUFFIX
                    bpmn_element = self._SEQUENCE_FLOW_PREFIX + curr_subtask_id
                    BPMNEdge = etree.SubElement(
                        BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
                        id=bpmn_edge_id,
                        bpmnElement=bpmn_element,
                        sourceElement=self._SUBTASK_PREFIX + prev_subtask_id,
                        targetElement=self._SUBTASK_PREFIX + curr_subtask_id,
                        nsmap=self._ns.NSMAP)
                    self._create_waypoint(
                        BPMNEdge, xnew, self._Y_START + ymove)
                    self._create_waypoint(
                        BPMNEdge, xnew + self._WIDTH_ARROW,
                        self._Y_START + ymove)
                    BPMNLabel = etree.SubElement(
                        BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
                        nsmap=self._ns.NSMAP)
                    etree.SubElement(
                        BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                        x=str(round(xnew + self._WIDTH_ARROW / 2)),
                        y=str(self._Y_LABEL),
                        width=str(self._WIDTH_LABEL),
                        height=str(self._HEIGHT_LABEL),
                        nsmap=self._ns.NSMAP)
                    # Add timer icon in branched nodes
                    id_ = f'{self._SUBTASK_PREFIX}{curr_subtask_id}_Timer_' \
                          f'{curr_subtask_id}'
                    BPMNShape = etree.SubElement(
                        BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                        id=id_,
                        bpmnElement=self._TIMER_PREFIX + curr_subtask_id,
                        nsmap=self._ns.NSMAP)
                    y = str(
                        ymove + self._Y_LABEL_VARIANT + rounded_height + 10)
                    etree.SubElement(
                        BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                        x=str(xcurr - 15),
                        y=y,
                        width=str(32),
                        height=str(32),
                        nsmap=self._ns.NSMAP)
                    BPMNLabel = etree.SubElement(
                        BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                        nsmap=self._ns.NSMAP)
                    y = str(
                        ymove + self._Y_LABEL_VARIANT + rounded_height + 32)
                    etree.SubElement(BPMNLabel,
                                     self._ns.dc + self._BOUNDS_SUFFIX,
                                     x=str(xcurr - self._TASK_WIDTH + 30),
                                     y=y,
                                     width=str(self._WIDTH_LABEL_VARIANT),
                                     height=str(self._HEIGHT_LABEL_VARIANT),
                                     nsmap=self._ns.NSMAP)

            ymove += gap + chainHeights[subchaincount - 1]
        return ymove, chainHeights

    def _addMergeNodeShapes(self, options, chains, BPMNPlane, stepcount,
                            offset, chainHeights, maxBranchLength, xcurr,
                            rounded_height):
        gap = self._HEIGHT_LABEL
        head = self._TASK_PREFIX
        xstart = xcurr + self._WIDTH_ARROW + \
            self._WIDTH_RHOMBUS + (self._WIDTH_ARROW + self._WIDTH_TASK)
        mainflownodes = stepcount - len(options)
        ymove = 0
        subchaincount = 0
        for k in chains:
            chain = chains.get(k)
            subchaincount += 1
            currstart = xstart + (len(chain) - 1) * \
                (self._WIDTH_ARROW + self._WIDTH_TASK)
            xend = xstart + (maxBranchLength - 1) * \
                (self._WIDTH_ARROW + self._WIDTH_TASK) + 2 * self._WIDTH_ARROW
            xmid = round((currstart + xend) / 2)
            BPMNEdge = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
                id=self._SEQUENCE_FLOW_PREFIX + str(
                    mainflownodes + 1) + '_' + str(
                    subchaincount) + self._LAST_DI_SUFFIX,
                bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(
                    mainflownodes + 1) + '_' + str(
                    subchaincount) + self._LAST_SUFFIX,
                sourceElement=self._SUBTASK_PREFIX + str(
                    mainflownodes) + '_' + str(
                    subchaincount) + '_' + str(len(chain)),
                targetElement=head + str(stepcount),
                nsmap=self._ns.NSMAP)
            BPMNLabel = etree.SubElement(BPMNEdge,
                                         self._ns.bpmndi + self._LABEL_SUFFIX,
                                         nsmap=self._ns.NSMAP)
            if subchaincount == 1:
                self._create_waypoint(BPMNEdge, currstart, self._Y_START)
                self._create_waypoint(BPMNEdge, xend, self._Y_START)
                etree.SubElement(
                    BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(xmid),
                    y=str(self._Y_LABEL),
                    width=str(self._WIDTH_LABEL),
                    height=str(self._HEIGHT_LABEL),
                    nsmap=self._ns.NSMAP)
            else:
                self._create_waypoint(
                    BPMNEdge, currstart, self._Y_START + ymove)
                self._create_waypoint(
                    BPMNEdge, xmid, self._Y_START + ymove)
                self._create_waypoint(BPMNEdge, xmid, self._Y_START)
                self._create_waypoint(BPMNEdge, xend, self._Y_START)
                etree.SubElement(
                    BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(xmid),
                    y=str(self._Y_START + ymove - self._OFFSET_Y_LABEL),
                    width=str(self._WIDTH_LABEL),
                    height=str(self._HEIGHT_LABEL),
                    nsmap=self._ns.NSMAP)
            ymove += gap + chainHeights[subchaincount - 1]
        BPMNShape = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
            id=head + str(stepcount) + self._DI_SUFFIX,
            bpmnElement=head + str(stepcount),
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(xend),
            y=str(self._Y_START - self._OFFSET_BOUNDS),
            width=str(self._WIDTH_TASK),
            height=str(rounded_height),
            nsmap=self._ns.NSMAP)

    def _handlePlainNodeShapes(self, data, options, BPMNPlane, stepcount,
                               offset, planeHeight, xcurr, rounded_height):
        if len(options) == 0:
            BPMNShape = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                id=self._TASK_PREFIX + str(stepcount) + self._DI_SUFFIX,
                bpmnElement=self._TASK_PREFIX + str(stepcount),
                nsmap=self._ns.NSMAP)
            etree.SubElement(
                BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                x=str(xcurr + self._WIDTH_ARROW),
                y=str(self._Y_SHAPE_VARIANT),
                width=str(self._WIDTH_TASK),
                height=str(rounded_height),
                nsmap=self._ns.NSMAP)
            new_plane_height = rounded_height
            if planeHeight < new_plane_height:
                planeHeight = new_plane_height
            head = self._TASK_PREFIX
            BPMNEdge = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
                id=self._SEQUENCE_FLOW_PREFIX + str(
                    stepcount - 1) + self._DI_SUFFIX,
                bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(stepcount - 1),
                sourceElement=head + str(stepcount - 1),
                targetElement=self._TASK_PREFIX + str(stepcount),
                nsmap=self._ns.NSMAP)
            self._create_waypoint(
                BPMNEdge, xcurr, self._Y_START)
            self._create_waypoint(
                BPMNEdge, xcurr + self._WIDTH_ARROW, self._Y_START)
            BPMNLabel = etree.SubElement(
                BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
                nsmap=self._ns.NSMAP)
            etree.SubElement(
                BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                x=str(round((xcurr + self._WIDTH_ARROW) / 2)),
                y=str(self._Y_LABEL),
                width=str(self._WIDTH_LABEL),
                height=str(self._HEIGHT_LABEL),
                nsmap=self._ns.NSMAP)
            maxchainlength = 0
            xcurr += self._WIDTH_ARROW + self._WIDTH_TASK

            # add timer icon main flow
            BPMNShape = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                id=f'{self._TASK_PREFIX}{stepcount}_Timer_{stepcount}',
                bpmnElement=self._TIMER_PREFIX + str(stepcount),
                nsmap=self._ns.NSMAP)
            etree.SubElement(
                BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                x=str(xcurr - 15),
                y=str(self._Y_LABEL_VARIANT + rounded_height + 10),
                width=str(32),
                height=str(32),
                nsmap=self._ns.NSMAP)
            BPMNLabel = etree.SubElement(
                BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                nsmap=self._ns.NSMAP)
            y = str(self._Y_LABEL_VARIANT + rounded_height + 32)
            etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                             x=str(xcurr - self._TASK_WIDTH + 30),
                             y=y,
                             width=str(self._WIDTH_LABEL_VARIANT),
                             height=str(self._HEIGHT_LABEL_VARIANT),
                             nsmap=self._ns.NSMAP)

        else:
            chains, maxchainlength = self.group_options(options)
            branchHeight, chainHeights = self._addBranchNodeShapes(
                options, chains, BPMNPlane, stepcount, offset, xcurr)
            self._addMergeNodeShapes(options, chains, BPMNPlane, stepcount,
                                     offset, chainHeights,
                                     maxchainlength, xcurr, rounded_height)
            xcurr += (maxchainlength + 1) * self._WIDTH_TASK + \
                (maxchainlength + 3) * self._WIDTH_ARROW + self._WIDTH_RHOMBUS
            if planeHeight < branchHeight:
                planeHeight = branchHeight

            # Add timer icon main flow after branch
            BPMNShape = etree.SubElement(
                BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                id=f'{self._TASK_PREFIX}{stepcount}_Timer_{stepcount}',
                bpmnElement=self._TIMER_PREFIX + str(stepcount),
                nsmap=self._ns.NSMAP)
            etree.SubElement(
                BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                x=str(xcurr - 15),
                y=str(self._Y_LABEL_VARIANT + rounded_height + 10),
                width=str(32),
                height=str(32),
                nsmap=self._ns.NSMAP)
            BPMNLabel = etree.SubElement(
                BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                nsmap=self._ns.NSMAP)
            y = str(self._Y_LABEL_VARIANT + rounded_height + 32)
            etree.SubElement(BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                             x=str(xcurr - self._TASK_WIDTH + 30),
                             y=y,
                             width=str(self._WIDTH_LABEL_VARIANT),
                             height=str(self._HEIGHT_LABEL_VARIANT),
                             nsmap=self._ns.NSMAP)

        return planeHeight, maxchainlength, xcurr

    def _appendShapesAndEdges(self, BPMNPlane, data):
        stepcount = 0
        planeHeight = 0
        totalchainlength = 0
        options = []
        allnodes = []
        xcurr = self._OFFSET_X_APPEND_SHAPES_AND_EDGES
        BPMNShape = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
            id=self._START_EVENT_0 + self._DI_SUFFIX,
            bpmnElement=self._START_EVENT_0, nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(xcurr),
            y=str(self._Y_SHAPE_VARIANT),
            width=str(self._WIDTH_START_EVENT),
            height=str(self._WIDTH_START_EVENT),
            nsmap=self._ns.NSMAP)
        BPMNLabel = etree.SubElement(
            BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(xcurr - self._OFFSET_X_LABEL),
            y=str(self._Y_SHAPE_VARIANT_2),
            width=str(self._WIDTH_LABEL),
            height=str(self._HEIGHT_LABEL),
            nsmap=self._ns.NSMAP)
        BPMNEdge = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
            id=self._SEQUENCE_FLOW_0 + self._DI_SUFFIX,
            bpmnElement=self._SEQUENCE_FLOW_0,
            sourceElement=self._START_EVENT_0,
            targetElement=self._TASK_1_NAME,
            nsmap=self._ns.NSMAP)
        self._create_waypoint(
            BPMNEdge, xcurr + self._WIDTH_START_EVENT, self._Y_START)
        self._create_waypoint(
            BPMNEdge, xcurr + self._WIDTH_START_EVENT + self._WIDTH_ARROW,
            self._Y_START)
        BPMNLabel = etree.SubElement(
            BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(stepcount * self._WIDTH_TASK),
            y=str(self._Y_LABEL),
            width=str(self._WIDTH_LABEL),
            height=str(self._HEIGHT_LABEL),
            nsmap=self._ns.NSMAP)
        xcurr += self._WIDTH_START_EVENT + self._WIDTH_ARROW
        rounded_heights = []
        timers = []
        for step_num, step in self._process_steps.items():
            stepcount += 1
            if step.get(self._PROCESS_STEP_TITLE) is not None:
                if self._PROCESS_STEPS_STR == self._PROCESS_STEPS_DIGITAL_STR:
                    timer = (
                        step.get(self._PROCESS_STEP_DIGITAL_DURATION_MIN),
                        step.get(self._PROCESS_STEP_DIGITAL_DURATION_MAX),
                        step.get(self._PROCESS_STEP_DIGITAL_DURATION_TYPE))
                else:
                    timer = (step.get(self._PROCESS_STEP_DURATION_MIN),
                             step.get(self._PROCESS_STEP_DURATION_MAX),
                             step.get(self._PROCESS_STEP_DURATION_TYPE))

                timers.append(timer)
                stepname = step.get(self._PROCESS_STEP_TITLE)
                rows = int(len(stepname) / self._DIVISOR_TASK_ROWS)
                rounded_height = int(self._MULTIPLIER_TASK_TEXT_HEIGHT * rows)
                rounded_heights.append(rounded_height)

                if stepcount == 1:
                    BPMNShape = etree.SubElement(
                        BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                        id=self._TASK_PREFIX + str(
                            stepcount) + self._DI_SUFFIX,
                        bpmnElement=self._TASK_PREFIX + str(stepcount),
                        nsmap=self._ns.NSMAP)
                    etree.SubElement(
                        BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                        x=str(xcurr),
                        y=str(self._Y_SHAPE_VARIANT),
                        width=str(self._WIDTH_TASK),
                        height=str(rounded_height),
                        nsmap=self._ns.NSMAP)
                    allnodes.append(self._TASK_PREFIX + str(stepcount))
                    xcurr += self._WIDTH_TASK

                    # add timer icon
                    id_ = f'{self._TASK_PREFIX}{stepcount}_Timer_{stepcount}'
                    BPMNShape = etree.SubElement(
                        BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                        id=id_,
                        bpmnElement=self._TIMER_PREFIX + str(stepcount),
                        nsmap=self._ns.NSMAP)
                    etree.SubElement(
                        BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                        x=str(xcurr - 15),
                        y=str(self._Y_LABEL_VARIANT + rounded_height + 10),
                        width=str(32),
                        height=str(32),
                        nsmap=self._ns.NSMAP)
                    BPMNLabel = etree.SubElement(
                        BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                        nsmap=self._ns.NSMAP)
                    y = str(
                        self._Y_LABEL_VARIANT + rounded_height + 32)
                    etree.SubElement(BPMNLabel,
                                     self._ns.dc + self._BOUNDS_SUFFIX,
                                     x=str(xcurr - self._TASK_WIDTH + 30),
                                     y=y,
                                     width=str(self._WIDTH_LABEL_VARIANT),
                                     height=str(self._HEIGHT_LABEL_VARIANT),
                                     nsmap=self._ns.NSMAP)

                else:
                    if step.get(self._PROCESS_STEP_CHILD) == \
                            self._PROCESS_STEP_CHILD_YES:
                        options.append(step)
                    else:
                        planeHeight, maxBranchLength, xcurr = \
                            self._handlePlainNodeShapes(
                                data, options, BPMNPlane, stepcount,
                                self._OFFSET_HANDLE_PLAIN_NODE_SHAPES,
                                planeHeight, xcurr, rounded_height)
                        options = []
                        totalchainlength += maxBranchLength
                        allnodes.append(self._TASK_PREFIX + str(stepcount))
        BPMNShape = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
            id=self._END_EVENT_PREFIX + str(stepcount + 1) + self._DI_SUFFIX,
            bpmnElement=self._END_EVENT_PREFIX + str(stepcount + 1),
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(xcurr + self._WIDTH_ARROW),
            y=str(self._Y_SHAPE_VARIANT), width=str(self._WIDTH_START_EVENT),
            height=str(self._WIDTH_START_EVENT), nsmap=self._ns.NSMAP)
        BPMNLabel = etree.SubElement(
            BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(round(xcurr + self._WIDTH_ARROW / 2)),
            y=str(self._Y_SHAPE_VARIANT_2),
            width=str(self._WIDTH_LABEL),
            height=str(self._HEIGHT_LABEL),
            nsmap=self._ns.NSMAP)
        BPMNEdge = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._EDGE_SUFFIX,
            id=self._SEQUENCE_FLOW_PREFIX + str(stepcount) + self._DI_SUFFIX,
            bpmnElement=self._SEQUENCE_FLOW_PREFIX + str(stepcount),
            sourceElement=self._TASK_PREFIX + str(stepcount),
            targetElement=self._END_EVENT_PREFIX + str(stepcount + 1),
            nsmap=self._ns.NSMAP)
        self._create_waypoint(
            BPMNEdge, xcurr, self._Y_START)
        self._create_waypoint(
            BPMNEdge, xcurr + self._WIDTH_ARROW, self._Y_START)
        BPMNLabel = etree.SubElement(
            BPMNEdge, self._ns.bpmndi + self._LABEL_SUFFIX,
            nsmap=self._ns.NSMAP)
        etree.SubElement(
            BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(round(xcurr + self._WIDTH_ARROW / 2)),
            y=str(self._Y_LABEL),
            width=str(self._WIDTH_LABEL),
            height=str(self._HEIGHT_LABEL),
            nsmap=self._ns.NSMAP)
        return planeHeight + self._OFFSET_X_APPEND_SHAPES_AND_EDGES, xcurr

    def _appendDataObjectShapes(self, BPMNPlane, process_name, planeHeight,
                                boundsWidth):
        # Now add the shapes for the Evidences
        stepcount = 0
        maxtextheight = 0
        xcurr = self._OFFSET_X_DOC
        doc_y = planeHeight + self._OFFSET_Y_DOC
        evidenceRowsStarts = [doc_y]
        maxEvidencesPerRow = int(int(boundsWidth) / self._OFFSET_DOC_TAB)
        for evidence_num, evidence in self._process_evidences.items():
            stepcount += 1
            evid_name = evidence[self._PROCESS_EVIDENCE_DESCR_KEY]
            if evid_name is not None:
                # Calculate rows of the description
                chars = len(evid_name)
                divisor = int(
                    self._WIDTH_LABEL_VARIANT_2 / self._DIVISOR_EVIDENCES_ROWS)
                rows = int(chars / divisor)
                textheight = int(rows * self._MULTIPLIER_EVIDENCES_TEXT_HEIGHT)
                if textheight > maxtextheight:
                    maxtextheight = textheight
                BPMNShape = etree.SubElement(
                    BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
                    id=self._DATA_OBJECT_REFERENCE_PREFIX + str(
                        stepcount) + self._DI_SUFFIX,
                    bpmnElement=self._DATA_OBJECT_REFERENCE_PREFIX + str(
                        stepcount),
                    nsmap=self._ns.NSMAP)
                etree.SubElement(
                    BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(xcurr + self._OFFSET_DOC_TAB),
                    y=str(evidenceRowsStarts[-1]),
                    width=str(self._WIDTH_DOC_ICON),
                    height=str(self._HEIGHT_DOC_ICON), nsmap=self._ns.NSMAP)
                BPMNLabel = etree.SubElement(
                    BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,
                    nsmap=self._ns.NSMAP)
                y = str(
                    evidenceRowsStarts[-1] + self._OFFSET_EVIDENCES_Y_LABEL)
                etree.SubElement(
                    BPMNLabel, self._ns.dc + self._BOUNDS_SUFFIX,
                    x=str(round(xcurr + self._OFFSET_DOC_TAB / 2)),
                    y=y,
                    width=str(self._WIDTH_LABEL_VARIANT_2),
                    height=str(self._HEIGHT_LABEL),
                    nsmap=self._ns.NSMAP)
                xcurr += self._OFFSET_DOC_TAB
            # row is full no so continue to next row
            if stepcount % maxEvidencesPerRow == 0:
                evidence_rows_start = evidenceRowsStarts[-1] + \
                    maxtextheight + 2 * self._HEIGHT_DOC_ICON + \
                    self._OFFSET_X_DOC
                evidenceRowsStarts.append(evidence_rows_start)
                maxtextheight = 0
                xcurr = self._OFFSET_X_DOC
        return xcurr, evidenceRowsStarts[-1] + maxtextheight

    def _appendFlow(self, definition, process_name, data):
        # Add the generic shapes (the flow etc.)
        BPMNDiagram = etree.SubElement(
            definition, self._ns.bpmndi + self._DIAGRAM_SUFFIX,
            id=self._TRISOTECH_ID_6,
            nsmap=self._ns.NSMAP)
        BPMNDiagram.attrib[QName(self._ns.di_NAMESPACE, self._NAME_KEY)
                           ] = self._NAME_ATTR_PREFIX + process_name
        BPMNDiagram.attrib[QName(
            self._ns.di_NAMESPACE,
            self._DOCUMENTATION_KEY)] = self._DOCUMENTATION_ATTR
        BPMNDiagram.attrib[QName(self._ns.di_NAMESPACE, self._RESOLUTION_KEY)
                           ] = self._RESOLUTION_ATTR
        BPMNDiagram.attrib[QName(
            self._ns.xsi_NAMESPACE, self._TYPE_KEY)] = self._TYPE_ATTR
        BPMNPlane = etree.SubElement(
            BPMNDiagram, self._ns.bpmndi + self._PLANE_SUFFIX,
            bpmnElement=self._C_PREFIX + str(hash(process_name)),
            nsmap=self._ns.NSMAP)
        BPMNShape = etree.SubElement(
            BPMNPlane, self._ns.bpmndi + self._SHAPE_SUFFIX,
            id=self._TRISOTECH_ID + self._PARTICIPANT_PREFIX + str(
                hash(process_name)),
            bpmnElement=self._PARTICIPANT_PREFIX + str(hash(process_name)),
            isHorizontal=self._IS_HORIZONTAL_TRUE,
            nsmap=self._ns.NSMAP)
        # Now add the shapes for the main flow
        planeHeight, xcurr = self._appendShapesAndEdges(BPMNPlane, data)
        bounds_width = xcurr + self._WIDTH_START_EVENT + self._WIDTH_ARROW + \
            self._OFFSET_BOUNDS_WIDTH
        etree.SubElement(
            BPMNShape, self._ns.dc + self._BOUNDS_SUFFIX,
            x=str(self._X_SHAPE),
            y=str(self._Y_SHAPE_VARIANT_3),
            width=str(bounds_width),
            height=str(planeHeight),
            nsmap=self._ns.NSMAP)
        etree.SubElement(BPMNShape, self._ns.bpmndi + self._LABEL_SUFFIX,)
        return BPMNPlane, planeHeight, bounds_width

    def xml(self, data):
        """Return the bpmn XML of a service.

        Args:
            data (dict): The data of a service, as returned by
                diavlos.src.service.Service._service_dict().

        Returns:
            str: The bpmn XML representation of a service.
        """
        root = etree.Element(self._DEFINITIONS_KEY)
        root.append(etree.Element(self._PROCESS_STR))
        semanticEl = etree.Element(
            f'{{{self._NAMESPACE[self._SEMANTIC_KEY]}}}{_SEMANTIC_STR}')
        etree.SubElement(
            semanticEl,
            f'{{{self._NAMESPACE[self._SEMANTIC_KEY]}}}{_DEFINITIONS_STR}')
        process_name = data.get(self._NAME_KEY)
        definition = etree.Element(
            self._ns.semantic + self._DEFINITIONS_KEY,
            id='_' + str(hash(process_name)),
            targetNamespace=self._NAMESPACE[self._XML_TARGET_KEY],
            nsmap=self._ns.NSMAP)
        # Read steps in order
        self._process_steps = {}
        stringsorted = data.get(self._FIELDS_KEY).get(self._PROCESS_STEPS_STR)
        if stringsorted is not None:
            integerkeyed = {int(k): v for k, v in stringsorted.items()}
            self._process_steps = OrderedDict(
                sorted(integerkeyed.items(), key=lambda t: t[0]))
        # Read evidences in order
        self._process_evidences = {}
        ev_stringsorted = data.get(self._FIELDS_KEY).get(
            self._PROCESS_EVIDENCES_STR)
        if ev_stringsorted is not None:
            ev_integerkeyed = {int(k): v for k, v in ev_stringsorted.items()}
            self._process_evidences = OrderedDict(
                sorted(ev_integerkeyed.items(), key=lambda t: t[0]))
        if self._process_steps is None:
            xml_string = ''
        else:
            process = self._appendProcessTree(definition, process_name, data)
            self._appendDataObjects(definition, process, data)
            self._appendCollaboration(definition, process_name)
            BPMNPlane, planeHeight, boundsWidth = self._appendFlow(
                definition, process_name, data)
            xdocmax, ydocmax = self._appendDataObjectShapes(
                BPMNPlane, process_name, planeHeight, boundsWidth)
            bs = BPMNPlane.findall(
                self._PLANE_FIND_ALL_QUERY, self._NAMESPACE)
            for b in bs:
                b.attrib[self._WIDTH_KEY] = str(
                    max(xdocmax, int(b.attrib[self._WIDTH_KEY])))
                b.attrib[self._HEIGHT_KEY] = str(ydocmax)
            dom = xml.dom.minidom.parseString(etree.tostring(
                definition, encoding='UTF-8', xml_declaration=True))
            xml_string = dom.toprettyxml()
        return xml_string


import numpy as np
import cv2
import copy
try: # python 2
    import tkFileDialog
except ImportError: # python 3
    pass
import os.path
import sys
# step one: load fly video
# step two: choose area that contains fly arena
# step three: select arena width
# step four: align microphone 1
# step three: choose area that shows blinky light
# select center position
# select initial flies (M/F)
# choose mating frame

def updateImage(x, y):
    global arenaX, arenaY, rectRadius, rectCenterX, rectCenterY, showFrame

    xdelta = abs(arenaX - x)
    ydelta = abs(arenaY - y)
    delta = max(xdelta, ydelta)

    rectRadius = delta / 2
    rectCenterX = arenaX
    rectCenterY = arenaY

    img = copy.copy(showFrame)
    cv2.circle(img, (arenaX, arenaY), delta, (0, 255, 0), 3)
    cv2.imshow('display', img)

def areaCallback(event, x, y, flags, param):
    global currentlyDrawing, arenaX, arenaY

    if event == cv2.EVENT_LBUTTONDOWN and currentlyDrawing == False:
        arenaX = x
        arenaY = y

    if event == cv2.EVENT_LBUTTONDOWN:
        currentlyDrawing = True
    elif event == cv2.EVENT_LBUTTONUP:
        currentlyDrawing = False

    if currentlyDrawing:
        updateImage(x, y)

def updateCenterImage(x, y):
    global arenaX, arenaY, showFrame

    img = copy.copy(showFrame)
    cv2.circle(img, (x, y), 5, (0, 255, 0), 3)
    cv2.imshow('display', img)

def centerCallback(event, x, y, flags, param):
    global currentlyDrawing, arenaX, arenaY

    if event == cv2.EVENT_LBUTTONDOWN and currentlyDrawing == False:
        arenaX = x
        arenaY = y

    if event == cv2.EVENT_LBUTTONDOWN:
        currentlyDrawing = True
        arenaX = x
        arenaY = y
    if event == cv2.EVENT_LBUTTONUP:
        currentlyDrawing = False

    if currentlyDrawing:
        updateCenterImage(x, y)

def findFliesCallback(event, x, y, flags, param):
    global nFlies, fly1CenterX, fly1CenterY, fly2CenterX, fly2CenterY

    if event == cv2.EVENT_LBUTTONUP:
        nFlies = nFlies + 1
        print('nFlies = ' + str(nFlies))
        updateCenterImage(x, y)
        flyCenters[nFlies - 1, 0] = x
        flyCenters[nFlies - 1, 1] = y

def trackerCallback(position):
    global angle

    M = cv2.getRotationMatrix2D((width / 4, height / 4), position - 180, 1)
    angle = position
    img = copy.copy(showFrame)
    img = cv2.warpAffine(img, M, (int(width / 2), int(height / 2)))
    cv2.imshow('display', img)

def doNothing(event, x, y, flags, param):
    pass

def getStartTime(dirName):
    trackingFile = os.path.join(dirName, 'startTrackingFrame.txt')
    if (os.path.isfile(trackingFile)):
        f = open(trackingFile)
        startFrame = int(f.readline())
        print('starting frame is ' + str(startFrame))
        f.close()
        return startFrame
    else:
        return 1

# these are the global variables that we want to assign (make these cmd args?)
doDefineArena = True
doAlignMike = True
doBlinky = True
currentlyDrawing = False
arenaX = 0
arenaY = 0
delta = 0
rectCenterX = 0
rectCenterY = 0
rectRadius = 0
angle = 0
nFlies = 0
flyCenters = np.zeros((100, 2))
fly1CenterX = 0
fly1CenterY = 0
fly2CenterX = 0
fly2CenterY = 0

# do the damage
if len(sys.argv) > 1:
    movieName = sys.argv[1]
else:
    movieName = tkFileDialog.askopenfilename()
print(movieName)
# startFrame = getStartTime(movieName[0:(movieName.rindex('/')+1)])
startFrame = getStartTime(os.path.dirname(movieName))

vr = cv2.VideoCapture(movieName)
vr.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'))
NumberOfFrames = int(vr.get(cv2.CAP_PROP_FRAME_COUNT))
fps = vr.get(cv2.CAP_PROP_FPS)
width = int(vr.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(vr.get(cv2.CAP_PROP_FRAME_HEIGHT))

if (startFrame != 1):
    vr.set(cv2.CAP_PROP_POS_FRAMES, startFrame + 1)
ret, frame = vr.read()

# fixed issue with reading early frames on windows
if np.max(frame)==0:
    vr.set(cv2.CAP_PROP_POS_FRAMES, np.min((100, NumberOfFrames)))
    vr.read()
    vr.set(cv2.CAP_PROP_POS_FRAMES, startFrame + 1)
    ret, frame = vr.read()

if not ret:
    print("error reading frame")

print((width, height))
cv2.namedWindow('display')
cv2.resizeWindow('display', width, height)

# choose fly arena
#frame2 = copy.copy(frame)
#cv2.putText(frame2, 'Choose arena', (50,50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
#showFrame = cv2.resize(frame2,(int(width/2),int(height/2)))
#cv2.imshow('display',showFrame)
#cv2.setMouseCallback('display',areaCallback)
#cv2.waitKey(0)

if doDefineArena:
    # choose arena center
    print((width, height))
    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Choose arena (press enter when done)', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
    cv2.imshow('display', showFrame)
    cv2.setMouseCallback('display', centerCallback)
    cv2.waitKey(0)
    centerX, centerY = arenaX, arenaY

    # choose arena width
    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Select LEFT boundary', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    showFrame = cv2.resize(frame2,(int(width / 2), int(height / 2)))
    cv2.imshow('display', showFrame)
    cv2.setMouseCallback('display',centerCallback)  #boundary callback
    cv2.waitKey(0)
    leftX, leftY = arenaX, arenaY

    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Select RIGHT boundary', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
    cv2.imshow('display', showFrame)
    cv2.setMouseCallback('display', centerCallback)  #boundary callback
    cv2.waitKey(0)
    rightX, rightY = arenaX, arenaY

    radius = np.sqrt((leftX - rightX) ** 2 + (leftY - rightY) ** 2) / 2
    # show detected circle
    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Here is the arena...I hope that is correct!', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
    cv2.circle(showFrame, (centerX, centerY), int(radius),(0, 255, 0), 3)
    cv2.imshow('display', showFrame)
    cv2.waitKey(0)

else:
    centerX, centerY = width / 4, height / 4 # order may be wrong...
    # rectCenterX, rectCenterY = width/2, height/2 # order may be wrong...
    leftX, leftY = 10 / 3, 10 / 3
    rightX, rightY = (width - 10) / 3, (height - 10) / 3
    radius = np.sqrt((leftX - rightX) ** 2 + (leftY - rightY) ** 2) / 2

# select flies
frame2 = copy.copy(frame)
cv2.putText(frame2, 'Select ALL flies', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
cv2.imshow('display', showFrame)
cv2.setMouseCallback('display', findFliesCallback)  #boundary callback
cv2.waitKey(0)
leftX, leftY = arenaX, arenaY

flyCenters = flyCenters[0:nFlies, :]

if doAlignMike:
    # align microphone 1
    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Align microphone 1', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    cv2.createTrackbar('imageRotation', 'display', 180, 360, trackerCallback)
    showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
    cv2.setMouseCallback('display', doNothing)
    cv2.imshow('display', showFrame)
    cv2.waitKey(0)

# we want to show the estimated position of the microphones to make sure we have them all...
# maybe this should be shown in the 'align microphone' phase

if doBlinky:
    # choose blinky light area
    frame2 = copy.copy(frame)
    cv2.putText(frame2, 'Select blinky light', (50, 50), cv2.FONT_HERSHEY_PLAIN, 2, 255)
    showFrame = cv2.resize(frame2, (int(width / 2), int(height / 2)))
    cv2.imshow('display', showFrame)
    cv2.setMouseCallback('display', areaCallback)  # boundary callback
    cv2.waitKey(0)

vr.release()
cv2.destroyAllWindows()

# save the data
# f=open(movieName[0:len(movieName)-4] + '_annotated.dat','w')
f = open(os.path.splitext(movieName)[0] + '_annotated.dat', 'w')
f.write(str(width) + '\n')
print('width = ' + str(width))
f.write(str(height) + '\n')
print('height = ' + str(height))
f.write(str(centerX * 2) + '\n')
print('centerX = ' + str(centerX * 2))
f.write(str(centerY * 2) + '\n')
print('centerY = ' + str(centerY * 2))
f.write(str(radius * 2) + '\n')
print('radius = ' + str(radius * 2))
f.write(str(rectCenterX * 2) + '\n')
print('rectCenterX = ' + str(rectCenterX * 2))
f.write(str(rectCenterY * 2) + '\n')
print('rectCenterY = ' + str(rectCenterY * 2))
f.write(str(rectRadius * 2) + '\n')
print('rectRadius = ' + str(rectRadius * 2))
f.write(str(angle) + '\n')
print('angle = ' + str(angle))
f.write(str(nFlies))  # ugggggghhhhh I forgot the + '\n' !!!!!!!
print('nFlies = ' + str(nFlies))

for ii in range(nFlies):
    f.write(str(flyCenters[ii, 0] * 2) + '\n')
    f.write(str(flyCenters[ii, 1] * 2) + '\n')
    print('fly center = ' + str((flyCenters[ii, 0], flyCenters[ii, 1])))

f.close()


import ezodf
import pprint
from logzero import logger

class ReadSheet:
    def __init__(self, spreadsheet_path):
        self.sheet_path = spreadsheet_path
        self.spreadsheet = None
        logger.debug('Opening spreadsheet %s', self.sheet_path)

    def read_cc_sheet(self):
        # Numberical input = [COLUMN, ROW]
        # Returns dict of CC names in format:
        # {
        #     'bank1: [cc1 name, cc2 name, etc],
        #       ...
        #     'bank8: [cc1 name, cc2 name, etc],
        # }
        # from a specified spreadsheet in .ods format. Use carbonite_cc_name_template.ods.

        logger.debug('Reading rosstalk custom control spreadsheet: %s', self.sheet_path)
        self.spreadsheet = ezodf.opendoc(self.sheet_path)
        sheet = self.spreadsheet.sheets['Sheet1']

        cc_data = {}

        banks = 8
        for bank in range(1, banks+1):
            bank_name = 'bank' + str(bank)
            bank_data = []
            for cc in range(1, 33):
                bank_data.append(sheet[cc, bank].value)

            cc_data.update({bank_name: bank_data})
        logger.debug('Got sheet data: %s', cc_data)
        return cc_data

    def read_lbl_file(self):  # reads a nk .lbl sheet, outputs a dict>list containing names of each input/output
        logger.debug('reading nk lbl sheet: %s', self.sheet_path)

        inputs = []
        outputs = []

        with open(self.sheet_path) as f:
            for line in f.readlines():
                outputs.append(line.split(',')[1])  # outputs
                inputs.append(line.split(',')[3])  # inputs

        io = {
            'inputs': inputs,
            'outputs': outputs
        }

        return io

if __name__ == '__main__':
    pprint.pprint(ReadSheet('2021_05_11.lbl').read_lbl_file())


import requests
from bs4 import BeautifulSoup
import time
from multiprocessing import Pool
import os

def download(img, h):
    name = img.split('/')[-1].replace('%', '')
    if os.path.exists('G:\\Pictures\\h\\' + name) and h is True:
        print("The file has existed")
    elif os.path.exists('G:\\Pictures\\yande\\' + name) and h is False:
        print("The file has existed")
    else:
        try:
            imgdata = requests.get(img)
            if not h:
                filepath = 'G:\\Pictures\\yande\\{}'.format(name)
                with open(filepath, 'wb') as code:
                     code.write(imgdata.content)
                code.close()
                print("Download the normal picture.........................ok")
            else:
                filepath = 'G:\\Pictures\\H\\{}'.format(name)
                with open(filepath, 'wb') as code:
                    code.write(imgdata.content)
                code.close()
                print("Download the h picture .........................ok")
        except:
            fp = open('G:\\Pictures\\yande\\yande.log', 'a')
            fp.writelines("[wrong]pic:" + img + "\n")
            fp.close()

def get_pic(pic_url):
    try:
        picData = requests.get(pic_url)
        PicSoup = BeautifulSoup(picData.text, 'lxml')
        PicContent = PicSoup.select('div.sidebar > div:nth-of-type(4) > ul > li')
        ifH = PicSoup.find_all("script", type="text/javascript")[5]
        if '"rating":"e"' in str(ifH):
            h = True
        else:
            h = False
        for pic in PicContent:
            title = pic.text
            if("Image" in title):
                img = pic.select('a')[0].get('href')
            if("Download larger version" in title):
                img = pic.select('a')[0].get('href')
            if ("Download PNG" in title):
                img = pic.select('a')[0].get('href')
        download(img, h)
        fp = open('G:\\Pictures\\yande\\yande.log', 'a')
        fp.writelines(time.asctime(time.localtime(time.time()))+":picture:" + pic_url + "..........................."
                                                                                ".....OK\n")
        fp.close()
    except Exception as e:
        print(str(e))
        fp = open('G:\\Pictures\\yande\\yande.log', 'a')
        fp.writelines(time.asctime(time.localtime(time.time()))+":[wrong-pic-url]:" + pic_url + "\n")
        fp.close()

def get_url(url):
    pic_urls = []
    try:
        urldata = requests.get(url)
    except Exception as e:
        print(str(e))
        fp = open('G:\\Pictures\\yande\\yande.log', 'a')
        fp.writelines(time.asctime(time.localtime(time.time())) + ":[wrong-singeUrl]:" + url + "\n")
        fp.close()
        return
    soup = BeautifulSoup(urldata.text, 'lxml')
    pic_url = soup.select('a.thumb')
    for picHref in pic_url:
        picHref = "https://yande.re" + picHref.get('href')
        pic_urls.append(picHref)
    pool = Pool()
    pool.map(get_pic, pic_urls)
    pool.close()
    pool.join()

def main():
    url = ["https://yande.re/post?page={}".format(str(i)) for i in range(161, 200)]
    for singleurl in url:
        get_url(singleurl)

if __name__ == "__main__":
    main()


import itertools

def do_flips(perm, flip):
    for i in range(3):
        if (flip & (1 << i)) > 0:
            yield (perm[i][1], perm[i][0])
        else:
            yield perm[i]

def main():
    sizes = [tuple(sorted(int(x) for x in input().split())) for _ in range(3)]
    target = max(rect[1] for rect in sizes)

    if all(rect[1] == target for rect in sizes) and sum(rect[0] for rect in sizes) == target:
        print('YES')
        return

    for perm in itertools.permutations(sizes):
        for f in range(8):
            res = list(do_flips(perm, f))
            if res[0][0] == target and res[1][1] == res[2][1] and res[0][1] + res[1][1] == target and res[1][0] + res[2][0] == target:
                print('YES')
                return

    print('NO')

main()


"""empty message

Revision ID: a11b335c4589
Revises: aedf57ef35d3
Create Date: 2021-06-22 21:29:11.066441

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'a11b335c4589'
down_revision = 'aedf57ef35d3'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('venue', sa.Column('looking_for_talent', sa.Boolean(), nullable=True))
    op.drop_column('venue', 'looking_for_Talent')
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('venue', sa.Column('looking_for_Talent', sa.BOOLEAN(), autoincrement=False, nullable=True))
    op.drop_column('venue', 'looking_for_talent')
    # ### end Alembic commands ###


import json
import csv

# enter file path of json file to convert
jsonfile = '/Users/roslynnricard/Google Drive/repo/20191201-143000.json'

# name file with cell serial number
csvfilename = 'SONY_VTC6A_2170_B.csv'

contents = ''
data_parsed = ''
config = ''
tags = ''

def parsejson():

    with open(jsonfile, 'r') as f:
        contents = f.read()

    data_parsed = json.loads(contents)

    # get cell and test details
    cell_type = data_parsed['config']['cell']
    current_sp = data_parsed['config']['discharge_current_A']

    tags = data_parsed['data'][1]['tags']

    #parse json and print to csv
    with open(csvfilename, 'w', newline='') as csvfile:
        fieldnames = ['cell_type','current_sp','time', 'current', 'voltage']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        writer.writeheader()

        for i in range(len(data_parsed['data'])):
            time = data_parsed['data'][i]['time']
            current = data_parsed['data'][i]['fields']['current_a']
            voltage = data_parsed['data'][i]['fields']['voltage_v']
            writer.writerow({'cell_type': cell_type, 'current_sp' : current_sp, 'time': time, 'current' : current, 'voltage' : voltage})

if __name__ == "__main__":
    parsejson()


import cv2
from threading import Thread, Lock
from Queue import Custom_Queue as Queue
from ConvertToGrayscale import convertToGrayscale
from ExtractAndDisplay import extractFrames, displayFrames

file = 'clip.mp4'
vidcap = cv2.VideoCapture(file)
length_of_clip = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
frameCounter = 0

def producer(queue, start, end, step):

    for value in range(start, end, step):
        queue.put(value)
    print('Producer finished')

def consumer(queue, count, result, lock):

    local_result = []
    for _ in range(count):
        local_result.append(queue.get())
    with lock:
        result.update(local_result)
    print('Consumer finished')

def main():
    image_queue = Queue(10)
    for i in range(length_of_clip):
        extractFrames(file, image_queue, i)
        convertToGrayscale(file, image_queue)
        displayFrames(image_queue, i, False)

main()


from ..models import FinAccount

import pytest
from mixer.backend.django import mixer
from django.core.exceptions import ValidationError
from datetime import datetime
pytestmark = pytest.mark.django_db

class TestFinAccount:
    def test_invalid_paid_field(self):
        fin = mixer.blend(FinAccount, paid=50.56, value=20.0)

        with pytest.raises(ValidationError) as call_info:
            fin.clean()

        assert 'The amount paid' \
            ' cannot be greater than the value.' in str(call_info.value)

        fin = mixer.blend(FinAccount, paid=-1, value=10)
        with pytest.raises(ValidationError) as call_info:
            fin.clean_fields()

        assert 'The value must be equal' \
            ' or greater than 0' in str(call_info.value)

    def test_valid_paid_field(self):
        fin = mixer.blend(FinAccount, paid=5.0, value=10)
        try:
            fin.clean()
        except ValidationError:
            pytest.fail('Unexpected ValidationError')

    def test_invalid_value_field(self):
        fin = mixer.blend(FinAccount, paid=0, value=-10)
        with pytest.raises(ValidationError) as call_info:
            fin.clean_fields()

        assert 'The value must be be greater than 0' in str(call_info.value)

        fin = mixer.blend(FinAccount, paid=0, value=0)
        with pytest.raises(ValidationError) as call_info:
            fin.clean_fields()

        assert 'The value must be be greater than 0' in str(call_info.value)

    def test_valid_value_field(self):
        fin = mixer.blend(FinAccount, paid=0, value=1)
        try:
            fin.clean()
        except ValidationError:
            pytest.fail('Unexpected ValidationError')

    def test_balance(self):
        fin = mixer.blend(FinAccount, paid=5, value=10)
        assert fin.balance == 5, 'Balance value should be 5'

    def test_str(self):
        name = 'CTP-123-1/10'
        fin = mixer.blend(FinAccount, description=name)
        assert str(fin) == name

    def test_canceled(self):
        fin = mixer.blend(FinAccount)
        fin.canceled = True

        assert fin.canceled_at.date() == datetime.today().date(), \
            'canceled_at field should be today'

        fin.canceled = False
        assert fin.canceled_at is None, 'canceled_at '\
            'field should be None'


import xml.etree.ElementTree as ET
import nltk
import random
import re
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
from itertools import combinations
from nltk.corpus import wordnet as wn
from nltk.translate import bleu
import numpy as np

def checkCorrect(score,ourSentence,correctSentences):
	ourSentence = ourSentence.split()

	highestBleuScore = 0
	for i in correctSentences:
		if i != "":
			correctSentence = i.split()

			bleuScore = nltk.translate.bleu_score.sentence_bleu([correctSentence], ourSentence, weights = [1])

			if bleuScore > highestBleuScore:
				print("correct with Bleu: ", correctSentence, bleuScore)
				highestBleuScore = bleuScore

	return highestBleuScore

def checkAndCleanCorrect(predicateDict, printResults = False):
	i = 0
	subjects = 0
	objects = 0

	correctPredicateDict = {}

	for predicate in predicateDict:
		correctPredicateDict[predicate] = []
		for sentence in predicateDict[predicate]:
			i += 1
			if '$subject$' in sentence:
				subjects += 1
			if '$object$' in sentence:
				objects += 1
			if '$object$' in sentence and '$subject$' in sentence:
				correctPredicateDict[predicate].append(sentence)

	if printResults is True:
		print("documents: ", i)
		print("subjects: ", subjects, round(subjects*100/i, 2))
		print("objects: ", objects, round(objects*100/i, 2))

	return correctPredicateDict

def getDerivatives(word): #not used now
	allForms = []
	synsets = wn.synsets(word)
	# Word not found
	if not synsets:
		return []

    # Get all  lemmas of the word
	lemmas = [l for s in synsets for l in s.lemmas()]

	# Get related forms
	derivationally_related_forms = [l.derivationally_related_forms() for l in lemmas]

	for form in derivationally_related_forms:
		for elem in form:
			if elem.name() not in allForms:
				allForms.append(elem.name())

	return allForms

def rreplace(s, old, new, occurrence):
	li = s.rsplit(old, occurrence)
	return new.join(li)

def replaceObject(Object, sentence):
	return re.sub(Object + '(\s)', '$object$ ', sentence)

def replaceSubject(Subject, sentence):
	return re.sub(Subject + '(\s)', '$subject$ ', sentence)

def replaceToGeneral(sentence, Subject, Object):
	sentence = sentence.lower()
	sentence = re.sub('\.$', ' ', sentence)

	Object = re.sub('"@en', ' ', Object) #Olive oil Potatoes is caused by the @en stuff...

	ObjectWords = Object.split()
	SubjectWords = Subject.split()
	sentenceWords = sentence.split()

	sentence = sentence.replace('u.s', 'united states')
	sentence = sentence.replace('uk', 'united kingdom')

	### FIRST METHOD: Exact replacement
	sentence = replaceSubject(Subject, sentence)
	sentence = replaceObject(Object, sentence)

	### SECOND METHOD: REPLACING LAST COMMA WITH SOMETHING ELSE
	SubjectAnd = rreplace(Subject, ',', ' and', 1)
	ObjectAnd = rreplace(Object, ',', ' and', 1)

	SubjectOr = rreplace(Subject, ',', ' or', 1)
	ObjectOr = rreplace(Object, ',', ' or', 1)

	sentence = replaceSubject(SubjectAnd, sentence)
	sentence = replaceObject(ObjectAnd, sentence)
	sentence = replaceSubject(SubjectOr, sentence)
	sentence = replaceObject(ObjectOr, sentence)

	### THIRD METHOD: Fuzzy replacement
	if '$subject$' not in sentence or '$object$' not in sentence:
		#print(ObjectWords)
		if '$object$' not in sentence:
			fuzzyObject = []
			#loop through all ObjectWords to match words in the sentence
			for ObjectWord in ObjectWords:
				word, score = process.extractOne(ObjectWord, sentenceWords, scorer=fuzz.ratio)
				if score > 80:
					fuzzyObject.append(word)
			#check if anything in this, otherwise it would replace all whitespaces with $object$
			if len(fuzzyObject) > 0:
				sentence = replaceObject(' '.join(fuzzyObject), sentence)

		if '$subject$' not in sentence:
			fuzzySubject = []
			#loop through all SubjectWords to match words in the sentence
			for SubjectWord in SubjectWords:
				word, score = process.extractOne(SubjectWord, sentenceWords, scorer=fuzz.ratio)
				if score > 80:
					fuzzySubject.append(word)

			#check if anything in this, otherwise it would replace all whitespaces with $subject$
			if len(fuzzySubject) > 0:
				sentence = replaceSubject(' '.join(fuzzySubject), sentence)

	#check to find all wrong sentences
	#if '$subject$' not in sentence or '$object$' not in sentence:
		#print(sentence + '\t '+ Object  + '\t '+  Subject)
	#check to find all correct sentences
	#if '$subject$' in sentence and '$object$'  in sentence:
	#	print(sentence + '\t '+ Object  + '\t '+  Subject)
	return sentence

def showPredicateRecall(predicateDict, minimumAmount = 1):
	i = 0
	for predicate in predicateDict:
		if len(predicateDict[predicate]) >= 1:
			i += 1
	print("Out of the {} predicates, {} have at least {} syntactical sentence(s) ".format(len(predicateDict), i, minimumAmount))

def replaceToSpecific(sentence, Subject, Object):
	return sentence.replace('$subject$', Subject).replace('$object$', Object)

def testData(predicateDict):
	bleuScoreList = []

	score = 0
	treeTest = ET.parse('WebNLG/dev/1triples/1triple_allSolutions_Food_dev_challenge.xml')
	rootTest = treeTest.getroot()
	for i in range(20):#range(len(root[0])):
		tripleSet = rootTest[0][i][1][0].text
		Subject   = tripleSet.split('|')[0].strip().lower().replace("_"," ")
		Predicate = tripleSet.split('|')[1].strip()
		Object    = tripleSet.split('|')[2].strip().lower().replace("_"," ").strip("_\"")

		#to obtain the sentences from the XML files
		n = 2
		correctSentences = []
		while True:
			try:
				sentenceFromData = rootTest[0][i][n].text
				sentenceFromData = re.sub('"', ' ', sentenceFromData)
				sentenceFromData = re.sub('\.$', '', sentenceFromData)
				sentenceFromData = re.sub('"@en', ' ', sentenceFromData)
				sentenceFromData = re.sub('\n', ' ', sentenceFromData)
				sentenceFromData = sentenceFromData.strip()
				correctSentences.append(sentenceFromData.lower())
				n += 1
			except:
				break

		#check if the predicate is in the dict
		if Predicate in predicateDict:
			sentence = random.choice(predicateDict[Predicate])
		else:
			sentence = "oops, this sentence couldn't be parsed" #just an ugly message
		# sentence = predicateDict[Predicate][1]
		print("SPO: ", Subject,Predicate, Object)
		print("sentence: ", sentence)
		print("ourSentence: ", replaceToSpecific(sentence,Subject,Object))
		ourSentence = replaceToSpecific(sentence,Subject,Object)

		bleuScore = checkCorrect(score,ourSentence,correctSentences)

		print('-'*50)

		bleuScoreList.append(bleuScore)

	print("average Bleu score ", np.mean(bleuScoreList))

def main():
	tree = ET.parse('WebNLG/train/1triples/1triple_allSolutions_Food_train_challenge.xml')
	root = tree.getroot()
	predicateDict = {}

	for i in range(len(root[0])):

		tripleSet = root[0][i][1][0].text
		Subject   = tripleSet.split('|')[0].strip().lower().replace("_"," ")
		Predicate = tripleSet.split('|')[1].strip()
		Object    = tripleSet.split('|')[2].strip().lower().replace("_"," ").strip("\"")

		lexList = []
		for elem in root[0][i]:
			if elem.tag == 'lex':
				lexList.append(replaceToGeneral(elem.text, Subject, Object))

		if Predicate not in predicateDict:
			predicateDict[Predicate] = lexList
		else:
			for elem in lexList:
				if elem not in predicateDict[Predicate]:
					predicateDict[Predicate].append(elem)
	showChecks = True
	predicateDict = checkAndCleanCorrect(predicateDict, showChecks) #predicateDict, showCorrect Objects and Subjects = False/True
	if showChecks is True:
		showPredicateRecall(predicateDict, 1)

	testData(predicateDict)

if __name__ == '__main__':
	main()


import csv
from dateutil.parser import parse

allTime = []
date = []
time = []
lat = []
lon = []

with open('uber-raw-data-aug-14.csv', 'r') as data:
    data = csv.reader(data, delimiter=',', quotechar='|')
    for row in data:
        allTime.append((''.join(row[0])))
        lat.append((''.join(row[1])))
        lon.append((''.join(row[2])))

#Working time seperator
# for x in range (1, len(allTime)):
#     temp = allTime[x].split(" ")
#     date.append(temp[0])
#     time.append(temp[1])
# print(date)

test = parse(date)
print(test)


import pandas as pd
import pickle
import numpy as np
import time
import re

import spacy
from spacy.lang.en.stop_words import STOP_WORDS
import sys

# only run one time to download spacy model -- SPECIFICALLY A NOTEBOOK COMMAND
#!{sys.executable} -m spacy download en_core_web_sm

# load saved df.  df['working_abstract'] contains cleaned text.

df = pd.read_pickle("../../../data/prd/Paper/FR_clean_22DEC21.pkl")
df.reset_index(inplace = True)
df.rename(columns={'index':'original index'}, inplace=True)

# Tokenize, POS, Lemmatize, Stop word removal

nlp = spacy.load('en_core_web_sm', disable=['parser', 'ner'])

sentences = df['working_abstract']

print("Lemmatizing...")

t1 = time.time()
lemmas = []

for doc in nlp.pipe(sentences):
    tokens = []
    for token in doc:
        if token.pos_ in ['NOUN', 'VERB', 'ADJ', 'ADV', 'PROPN', 'INTJ', 'NUM', 'X'] and not token.is_stop:
            tokens.append(token.lemma_)
    lemmas.append(tokens)

t2 = time.time()
print(t2-t1)

file = open("../../../data/prd/Paper/FR_lemmas_22DEC21.pkl", 'wb')
pickle.dump(lemmas, file)
file.close()