from collections import defaultdict
from datetime import date

from lxml.etree import Element, SubElement

from pyopteryx.factories.action_factories.action_factory import ActionFactory
from pyopteryx.factories.loop_action_factories.loop_action_factory import LoopActionFactory
from pyopteryx.factories.usage_action_factories.usage_action_factory import UsageActionFactory
from pyopteryx.utils.finalize_utils import delete_unused_processors, precedence_error_check, sort_processor_elements
from pyopteryx.utils.utils import calculate_open_arrival_rate
from pyopteryx.utils.builder_utils import add_processor_element, add_task_to_processor, add_entry_to_task, \
    add_activity_to_entry, add_default_processor, add_internal_actions_to_cpu
from pyopteryx.utils.xml_utils import get_by_id, get_by_name, get_action_type, get_linkage_id, get_entity_name, \
    get_used_components, get_element_from_list, get_xml_schema_type, get_parent_by_tag, is_part_of_composite_component, \
    create_processors_for_composite_components, create_processors_for_external_actions, \
    create_processor_names_for_branch_action, create_server_uids


class LqnBuilder:
    """
    Class that perform PyCM2LQN Transformation.
    """
    def __init__(self, input_data, cache):
        """
        Set __input_data and __cache.
        Create __mapping_cache, __latency, __xml_tree, __repository_components_cleaned.
        Add solver parameter to __xml_tree.
        :param input_data: input containing cpu rates, allocation of components and assembled components
        :param cache: cached PCM files
        """
        self.__input_data = input_data
        self.__cache = cache
        self.__mapping_cache = {
            "action_mapping": defaultdict(lambda: []),  # maps processor name to ExternalCallAction
            "allocation_mapping": defaultdict(lambda: []),  # maps component id to processor id
            "branch_mapping": defaultdict(lambda: {}),  # maps "branches_Branch" id to interface_processor name
            "component_mapping": defaultdict(lambda: {}),  # maps component_id to processor name
            "composite_uid_mapping": defaultdict(lambda: {}),  # map processor_name to uid
            "connector_mapping": defaultdict(lambda: ""),  # maps processor name to server id
            "is_detailed": defaultdict(lambda: {}),  # maps processor_name without uid to all available
            "server_mapping": defaultdict(lambda: {}),  # maps server_id to processor name
            "loop_mapping": defaultdict(lambda: []),  # maps processor_name to loop_action
            "loop_uid_mapping": defaultdict(lambda: {})  # map processor_name to uid
        }
        self.__latency = {}  # lookup for latency entry of linking resource processor
        self.__xml_tree = Element('lqn-model')
        self.__xml_tree.set('name', 'PyCM2LQN_Model')
        self.__add_solver_params_to_root(root=self.__xml_tree)
        self.__repository_components_cleaned = get_used_components(repository_tree=cache.get_xml_tree("repository"),
                                                                   input_data=self.__input_data)

    def __add_solver_params_to_root(self, root):
        """
        Add solver params to xml tree.
        :param root:
        :return:
        """
        solver_params = SubElement(root, 'solver-params')
        solver_params.set("comment", "Generated by PyCM2LQN on {}".format(date.today()))
        solver_params.set("conv_val", self.__input_data["solver_params"]["conv_val"])
        solver_params.set("it_limit", "50")
        solver_params.set("print_int", "10")
        solver_params.set("underrelax_coeff", "0.5")

    def __create_cpu_processors(self):
        """
        Create cpu processors for every cpu_rate entry of input data and add them to xml tree.
        Processors are of format "{server}_CPU_Processor"
        :return: added all cpu processors to lqn-model xml tree
        """
        for server_id in self.__input_data["cpu_rates"]:
            # get current server_cpu_resource from "{setting_type}.resourceenvironment" file
            # and build correct server_cpu_processor_name
            cpu_server = get_by_id(element_id=server_id, element=self.__cache.get_xml_tree("resourceenvironment"))
            cpu_server_name = cpu_server.get("entityName")
            processor_name = '{}_CPU'.format(cpu_server_name)
            # create elements and add them to xml tree
            processor = add_processor_element(xml_tree=self.__xml_tree, processor_name=processor_name,
                                              speed_factor="1.0")
            task = add_task_to_processor(processor=processor)
            entry = add_entry_to_task(task=task, entry_name=processor_name, entry_type='NONE')
            add_activity_to_entry(entry=entry, activity_name=processor_name, host_demand_mean="0.0")
            # map server_id to name of new created processor
            self.__mapping_cache["server_mapping"][server_id] = '{}_Processor'.format(processor_name)

    def __create_linking_resource_processors(self):
        """
        Create linking resource processor for every linking resource declared in "{setting_type}.resourceenvironment" as
        "linkingResources__ResourceEnvironment" and add them to xml tree
        :return: processor of format "LinkingResource_{}_LAN_Processor processors" with:
                    - task:  "LinkingResource_{aName}_LAN_Task"
                    - entry: "throughput_LinkingResource_{aName}_LAN_Entry"
                    - entry: "latency_LinkingResource_{aName}_LAN_SYNCH_Entry"
                    - entry: "latency_LinkingResource_{aName}_LAN_ASYNCH_Entry"
        """
        for resource_linking_environment in self.__cache.get_xml_tree("resourceenvironment").findall(
                "./linkingResources__ResourceEnvironment"):
            # add entityName to processor id if it is declared
            entity_name = get_entity_name(resource_linking_environment)
            linking_processor_name = 'LinkingResource_{entity_name}_LAN'.format(entity_name=entity_name)
            # create elements and add them to xml tree
            processor = add_processor_element(self.__xml_tree, processor_name=linking_processor_name,
                                              speed_factor="1.0")
            task = add_task_to_processor(processor=processor)
            # latency and throughput specifications are stored in:
            # {setting_type}.resourceenvironment > linkingResources__ResourceEnvironment >
            # communicationLinkResourceSpecifications_LinkingResource:
            # - latency_CommunicationLinkResourceSpecification
            latency = resource_linking_environment.find(".//latency_CommunicationLinkResourceSpecification").get(
                "specification")
            # - throughput_CommunicationLinkResourceSpecification
            throughput = resource_linking_environment.find(".//throughput_CommunicationLinkResourceSpecification").get(
                "specification")
            throughput_host_demand_mean = '{throughput}'.format(throughput=1 / int(throughput))
            # add throughput entry
            throughput_entry_id = 'throughput_{processor_id}'.format(processor_id=linking_processor_name)
            throughput_entry = add_entry_to_task(task=task,
                                                 entry_name=throughput_entry_id,
                                                 entry_type="PH1PH2")
            add_activity_to_entry(entry=throughput_entry,
                                  activity_name=throughput_entry_id,
                                  host_demand_mean=throughput_host_demand_mean)
            # add synch latency entry
            latency_synch_id = 'latency_{linking_processor_name}_SYNCH'.format(
                linking_processor_name=linking_processor_name)
            self.__latency["SYNCH"] = '{latency_synch_id}_Entry'.format(latency_synch_id=latency_synch_id)
            latency_synch_entry = add_entry_to_task(task=task, entry_name=latency_synch_id, entry_type="PH1PH2")
            add_activity_to_entry(entry=latency_synch_entry,
                                  activity_name=latency_synch_id,
                                  host_demand_mean=latency,
                                  host_demand_cvsq="0.0")
            # add asynch latency entry
            latency_specification_asynch_id = 'latency_{linking_processor_name}_ASYNCH'.format(
                linking_processor_name=linking_processor_name)

            latency_asynch_entry = add_entry_to_task(task=task,
                                                     entry_name=latency_specification_asynch_id,
                                                     entry_type="PH1PH2")
            add_activity_to_entry(entry=latency_asynch_entry,
                                  activity_name=latency_specification_asynch_id,
                                  host_demand_mean=latency,
                                  host_demand_cvsq="0.0")

    def __create_usage_delay_processor(self):
        """
        Add usage delay processor to lqn-model xml tree.
        :return:
        """
        processor = add_processor_element(self.__xml_tree, processor_name="USAGE_DELAY", scheduling="inf")
        task = add_task_to_processor(processor)
        entry = add_entry_to_task(task=task, entry_name="USAGE_DELAY0", entry_type="PH1PH2")
        add_activity_to_entry(entry=entry, activity_name="USAGE_DELAY0", host_demand_mean="0.0")

    def __create_usage_scenario_processors(self):
        """
        Create usage scenario processor for every usage scenario declared in "{setting_type}.usagemodel" as
        "usageScenario_UsageModel" and add them to xml tree. Create processor of format
        "UsageScenario_{scenario_name}_{amount_scenarios}_Processor" with:
            - task: "UsageScenario_{scenario_name}_{amount_scenarios}_Task"
            - entry: "UsageScenario_{scenario_name}_{amount_scenarios}_Entry"
        """
        usage_scenario_counter = 1
        for usage_scenario in self.__cache.get_xml_tree("usagemodel").findall("./usageScenario_UsageModel"):
            usage_scenario_entity_name = usage_scenario.get("entityName")
            # get specification from "{setting_type}.usagemodel" file > workload_UsageScenario >
            # interArrivalTime_OpenWorkload > specification
            specification = usage_scenario.find(".//interArrivalTime_OpenWorkload").get("specification")
            open_arrival_rate = calculate_open_arrival_rate(specification)
            usage_scenario_processor_name = 'UsageScenario_{}_{}'.format(usage_scenario_entity_name,
                                                                         usage_scenario_counter)
            # add usage scenario processor, task and entry
            processor = add_processor_element(self.__xml_tree, processor_name=usage_scenario_processor_name)
            task = add_task_to_processor(processor=processor)
            add_entry_to_task(task=task,
                              entry_name=usage_scenario_processor_name,
                              open_arrival_rate=open_arrival_rate)
            SubElement(task, 'task-activities')
            usage_scenario_counter += 1

    def __create_component_interface_processors(self):
        """
        Create all component interface processors for every used components of repository.
        :return:
        """
        # To avoid duplicates
        used_components = []
        for component_id in self.__input_data["component_allocations"]:
            # allocation
            allocation_context = get_by_id(element_id=component_id,
                                           element=self.__cache.get_xml_tree("allocation"))
            # allocation > system
            allocation_to_system_linkage_id = get_linkage_id(identifier='assemblyContext_AllocationContext',
                                                             element_tree=allocation_context)
            # system
            assembly_context = get_by_id(element_id=allocation_to_system_linkage_id,
                                         element=self.__cache.get_xml_tree('system'))
            # system > repository
            system_to_repository_linkage_id = get_linkage_id(identifier='encapsulatedComponent__AssemblyContext',
                                                             element_tree=assembly_context)
            # check if system_to_repository_linkage_id is an unused alternative design option,
            # if yes, get currently used corresponding alternative
            for used_option in self.__input_data["alternative_design_options"]:
                if system_to_repository_linkage_id in self.__input_data["alternative_design_options"][used_option]:
                    system_to_repository_linkage_id = used_option

            # repository
            repository_component = get_by_id(element_id=system_to_repository_linkage_id,
                                             element=self.__cache.get_xml_tree("repository"))
            self.__create_interface_processors(component_id=component_id,
                                               used_components=used_components,
                                               repository_component=repository_component)

            # map component_id to name of new created processor
            self.__mapping_cache['component_mapping'][component_id] = repository_component.get("id")

    def __create_interface_processors(self, component_id, used_components, repository_component):
        """
        Add processors for every operation that can be called from an used component of the repository that
        are of format {component}_{interface}_{operation}
        :param component_id: component of allocation
        :param used_components:
        :return:
        """
        component_type = repository_component.get(get_xml_schema_type())
        if "CompositeComponent" in component_type:
            repository_components = []
            assembly_contexts = repository_component.findall(".//assemblyContexts__ComposedStructure")
            for repository_component in assembly_contexts:
                context_id = repository_component.get("encapsulatedComponent__AssemblyContext")
                repository_component = get_element_from_list(search_string=context_id,
                                                             attribute="id",
                                                             element_list=self.__repository_components_cleaned)
                repository_components.append(repository_component)
            for repository_component in repository_components:
                self.__add_interface_processors(component_id, repository_component, used_components)
        else:
            self.__add_interface_processors(component_id, repository_component, used_components)
        return used_components

    def __create_loop_processors(self):
        """
        Create processor for every LoopAction, that contains all actions of current LoopAction
        :return:
        """
        for component in self.__cache.get_xml_tree("repository"):
            for action in component.findall(".//steps_Behaviour"):
                action_type = get_action_type(action=action)
                if "LoopAction" in action_type:
                    processors_to_add = []
                    # build processor name and add processor
                    action_name = get_entity_name(entity=action)
                    action_id = action.get("id")
                    processor_name = '{type}_{name}_{id}'.format(type=action_type, name=action_name, id=action_id)
                    if is_part_of_composite_component(element=action,
                                                      components=self.__cache.get_xml_tree("repository").findall(
                                                          ".//components__Repository")):
                        allocated_server_ids = []
                        allocated_server_ids = create_server_uids(allocated_server_ids=allocated_server_ids,
                                                                  component_id=component.get("id"), cache=self.__cache,
                                                                  composite_component_allocation=self.__input_data[
                                                                      "composite_component_allocation"])

                        for server_uid in allocated_server_ids:
                            uid = '#{uid}#'.format(uid=server_uid)
                            unique_processor_name = '{processor_name}_{uid}'.format(processor_name=processor_name,
                                                                                    uid=uid)
                            self.__mapping_cache["loop_uid_mapping"][unique_processor_name] = uid
                            self.__mapping_cache["loop_mapping"][action].append(unique_processor_name)
                            # self.__mapping_cache["composite_uid_mapping"][unique_processor_name] = uid
                            processors_to_add.append(unique_processor_name)

                    # for alternative in branch_uids
                    # add processor_name to
                    if not processors_to_add:
                        processors_to_add.append(processor_name)
                        self.__mapping_cache["loop_mapping"][action].append(processor_name)

                    for processor_name in processors_to_add:
                        add_default_processor(xml_tree=self.__xml_tree, processor_name=processor_name)

    def __add_interface_processors(self, component_id, repository_component, used_components):
        """
        Add component interface processors to xml_tree. Processors must be created for every 'described_SEFF' of a
        repository component, that stands for an 'operation'. An interface processor name is of format:
        "{component_name}_{interface_name}_{operation_name}[_uid]_Processor" (note: that 'uid' is optional)
        :param component_id: id of component from allocation
        :param repository_component: component element from repository
        :param used_components: list of component interface processors already created
        :return:
        """
        # first part of processor_name is component_name
        component_name = repository_component.get("entityName")
        # get provided interfaces for component
        service_effect_specifications = repository_component.findall("./serviceEffectSpecifications__BasicComponent")
        repository_id = repository_component.get("id")
        for seff in service_effect_specifications:
            # list of processor names that must be created according to this seff
            processors_to_add = []
            # get "Signatures__OperationInterface" element from "describedService__SEFF" of current seff
            described_seff_id = seff.get("describedService__SEFF")
            interface_operation = get_by_id(element_id=described_seff_id,
                                            element=self.__cache.get_xml_tree("repository"))
            # second part of processor_name is interface name
            interface_name = get_parent_by_tag(element=interface_operation,
                                               tag="interfaces__Repository").get("entityName")
            # third part of processor_name is operation_name
            operation_name = interface_operation.get("entityName")
            # Create component interface processor name
            processor_name = '{component}_{interface}_{operation}'.format(component=component_name,
                                                                          interface=interface_name,
                                                                          operation=operation_name)
            # Check if processors must be duplicated due to included branch action,
            # if yes return list that contains the corresponding processor names with branch specific uid
            processors_to_add = create_processor_names_for_branch_action(processor_name=processor_name,
                                                                         processors_to_add=processors_to_add, seff=seff,
                                                                         mapping_cache=self.__mapping_cache)

            # Check if ExternalCallAction links to processor that includes BranchAction, which is mandatory to copy
            # if yes: copy processor for ExternalCallAction also
            processors_to_add = create_processors_for_external_actions(processor_name=processor_name,
                                                                       processors_to_add=processors_to_add,
                                                                       seff=seff)

            # Check if component is of type CompositeComponent, if yes: add duplicated processor names to list
            # for every described_SEFF that contains an InternalAction. Make processor names unique by
            # adding allocated server name as uid
            processors_to_add = create_processors_for_composite_components(processor_name=processor_name,
                                                                           processors_to_add=processors_to_add,
                                                                           seff=seff, cache=self.__cache,
                                                                           input_data=self.__input_data,
                                                                           mapping_cache=self.__mapping_cache)
            # If none of the above create-functions return a processor_name, no extra processor_names must be created,
            # so add normal processor_name to 'processors_to_add'
            if not processors_to_add:
                processors_to_add.append(processor_name)
            # Add processors to xml tree and avoid duplicated processors
            for processor_name in processors_to_add:
                if processor_name not in used_components:
                    used_components.append(processor_name)
                    # map component id to processor id
                    self.__mapping_cache['allocation_mapping'][component_id].append(processor_name)
                    # map component and interface processors to corresponding
                    # "describedService__SEFF" of basic component
                    self.__mapping_cache['action_mapping'][processor_name] = described_seff_id
                    if repository_id in self.__input_data["composite_component_allocation"].keys():
                        # located on same server
                        if len(self.__input_data["composite_component_allocation"][repository_id]) != 1:
                            for server_id, used in self.__input_data["composite_component_allocation"][
                                    repository_id].items():
                                if not used:
                                    self.__mapping_cache['connector_mapping'][processor_name] = server_id
                                    self.__input_data["composite_component_allocation"][repository_id][server_id] = True
                                    break
                        else:
                            server_id = list(self.__input_data["composite_component_allocation"][repository_id].keys())[
                                0]
                            self.__mapping_cache['connector_mapping'][processor_name] = server_id
                            self.__input_data["composite_component_allocation"][repository_id][server_id] = True
                    else:
                        self.__mapping_cache['connector_mapping'][processor_name] = self.__input_data[
                            "component_allocations"][component_id]

                    add_default_processor(xml_tree=self.__xml_tree, processor_name=processor_name)

            if repository_id in self.__input_data["composite_component_allocation"].keys():
                for server_id, allocation in self.__input_data["composite_component_allocation"][repository_id].items():
                    self.__input_data["composite_component_allocation"][repository_id][server_id] = False

    def __add_actions_to_loop_processors(self):
        """
        adds actions to rel
        :return:
        """
        for action in self.__mapping_cache["loop_mapping"]:
            for processor_name in self.__mapping_cache["loop_mapping"][action]:
                processor = get_by_name(element=self.__xml_tree,
                                        element_name='{processor_name}_Processor'.format(processor_name=processor_name))
                # Get all actions within LoopAction and create activities and precedences via LoopActionFactory
                loop_actions = action.find("bodyBehaviour_Loop").findall("./steps_Behaviour")
                for loop_action in loop_actions:
                    action_factory = LoopActionFactory(xml_cache=self.__cache,
                                                       input_data=self.__input_data,
                                                       processor=processor,
                                                       action=loop_action,
                                                       mapping_cache=self.__mapping_cache).create_action_factory()
                    action_factory.add_action()

        # ----- PROCESSOR CREATION END -----

    def __add_actions(self, action, processor):
        """
        Create ActionFactory and add action as <activity> to processors' <task-activities>
        and all corresponding <precedences> for action
        :param processor: processor to add actions to
        :param action: first action of a component is always StartAction
        :return:
        """
        # Create ActionFactory for action
        task_activities = processor.find(".//task-activities")
        action_factory = ActionFactory(xml_cache=self.__cache,
                                       mapping_cache=self.__mapping_cache,
                                       action=action,
                                       input_data=self.__input_data,
                                       latency=self.__latency,
                                       processor=processor).create_action_factory(task_activities=task_activities)
        # If action type is 'SetVariableAction' no factory will be created, since 'SetVariableAction' is not supported
        if action_factory:
            action_factory.add_action()
        # Add actions recursively if current action has successor action
        successor_id = action.get("successor_AbstractAction")
        if successor_id is not None:
            successor = get_by_id(element=action.getparent(), element_id=successor_id)
            self.__add_actions(action=successor, processor=processor)

    def __add_actions_to_usage_scenario_processor(self):
        """
        Create actions to usage scenario processor AND create "UsageScenario_Loop_" processors with actions
        :return:
        """
        usage_counter = 1
        for usage_model in self.__cache.get_xml_tree("usagemodel").findall("./usageScenario_UsageModel"):
            usage_scenario = usage_model.find("scenarioBehaviour_UsageScenario")
            usage_processor_name = 'UsageScenario_{}_{}_Processor'.format(usage_model.get("entityName"),
                                                                          usage_counter)
            processor = get_by_name(element=self.__xml_tree, element_name=usage_processor_name)
            # add usage actions to usage processor
            for action in usage_scenario.findall("./actions_ScenarioBehaviour"):
                action_factory = UsageActionFactory(xml_cache=self.__cache, input_data=self.__input_data,
                                                    processor=processor, action=action,
                                                    mapping_cache=self.__mapping_cache).create_action_factory()
                action_factory.add_action()
            # Create UsageScenario_Loop processor for every "bodyBehaviour_Loop" and
            # add actions that are within it to created processor
            for body_behaviour in usage_model.findall(".//bodyBehaviour_Loop"):
                processor_id = get_parent_by_tag(element=body_behaviour, tag="actions_ScenarioBehaviour").get("id")
                processor_name = 'UsageScenario_Loop_{id}'.format(id=processor_id)
                # Add processor, task, entry, task_activities and reply entry
                processor = add_processor_element(self.__xml_tree, processor_name)
                task = add_task_to_processor(processor=processor)
                entry = add_entry_to_task(task=task, entry_name=processor_name)
                task_activities = SubElement(task, 'task-activities')
                reply_entry = SubElement(task_activities, 'reply-entry')
                reply_entry.set("name", entry.get('name'))
                # Add loop actions to "UsageScenario_Loop" processor
                for action in body_behaviour.findall("./"):
                    action_factory = UsageActionFactory(xml_cache=self.__cache, input_data=self.__input_data,
                                                        processor=processor, action=action,
                                                        mapping_cache=self.__mapping_cache).create_action_factory()
                    action_factory.add_loop_config()
        usage_counter += 1

    def __add_actions_to_component_interface_processors(self):
        """
        create actions as activities and corresponding precedence tags
        :return:
        """
        # for every component interface processor that was created
        for component_interface_processor in self.__mapping_cache["action_mapping"]:
            processor_name = '{name}_Processor'.format(name=component_interface_processor)
            processor = get_by_name(element_name=processor_name, element=self.__xml_tree)
            for component in self.__repository_components_cleaned:
                service_effect_specifications = component.findall(".//serviceEffectSpecifications__BasicComponent")
                for seff in service_effect_specifications:
                    if seff.get("describedService__SEFF") == self.__mapping_cache["action_mapping"][
                            component_interface_processor]:
                        # StartAction is always first element with tag "steps_Behaviour"
                        start_action = seff.find("./steps_Behaviour")
                        # Create start_action and all it's successor actions linear
                        self.__add_actions(action=start_action, processor=processor)

    def __finalize_xml_tree(self):
        """
        Execute clean up steps:
        1. Delete unused processors,
        2. Check synch calls
        3. Add internal actions of remaining processors to allocated cpu processors
        4. Sort processor elements
        :return: finalized lqn-model
        """
        # Delete those processors that's entry is not used as "dest" in any other processor
        delete_unused_processors(mapping_cache=self.__mapping_cache, xml_tree=self.__xml_tree)
        # Check xml tree for precedence duplications
        precedence_error_check(tree=self.__xml_tree)
        # Add internal actions to allocated cpu processors and sort processor elements
        processors = self.__xml_tree.findall("processor")
        for processor in processors:
            add_internal_actions_to_cpu(processor=processor, mapping_cache=self.__mapping_cache,
                                        xml_tree=self.__xml_tree, cpu_rates=self.__input_data["cpu_rates"],
                                        repository=self.__cache.get_xml_tree("repository"))
            sort_processor_elements(processor=processor)
        return self.__xml_tree

    def transform_pycm2lqn(self):
        """
        Perform transformation from PCM to LQXO file.
        """
        # 1) Create cpu processors, e.g. "Server1_CPU_Processor"
        self.__create_cpu_processors()

        # 2) Create linking resource processors, e.g. "LinkingResource_{}_LAN_Processor processors"
        self.__create_linking_resource_processors()

        # 3) Create USAGE_DELAY processors, e.g. "USAGE_DELAY_Processor"
        self.__create_usage_delay_processor()

        # 4) Create component/interface processors,
        # e.g. "{component_name}_{interface_name}_{operation_name}[_uid]_Processor" (note: [uid] is optional)
        self.__create_component_interface_processors()
        self.__add_actions_to_component_interface_processors()

        # 6) Create usage UsageScenario, e.g. "UsageScenario_{usage_scenario}_1_Processor"
        self.__create_usage_scenario_processors()
        self.__add_actions_to_usage_scenario_processor()

        # 7) Create Loop Processors
        self.__create_loop_processors()
        self.__add_actions_to_loop_processors()

        return self.__finalize_xml_tree()
