<commit_before># -*- coding: utf-8 -*-
"""
Tellurium SED-ML support.

This module implements SED-ML L1V2 support for tellurium.
In the current implementation all SED-ML constructs with exception of
    XML transformation changes of the model
        - Change.RemoveXML
        - Change.AddXML
        - Change.ChangeXML
are supported.

SBML models are fully supported, whereas for CellML models only basic support
is implemented (when additional support is requested it be implemented).
CellML models are transformed to SBML models which results in different XPath expressions,
so that targets, selections cannot be easily resolved in the CellMl-SBML.

Supported input for SED-ML are either SED-ML files ('.sedml' extension),
SED-ML XML strings or archives ('.sedx'|'.omex' extension).
Executable python code is generated from the SED-ML which allows the
execution of the defined simulation experiment.

SED-ML is build of five main classes
    the Model Class,
    the Simulation Class,
    the Task Class,
    the DataGenerator Class,
    and the Output Class.

The Model Class
    The Model class is used to reference the models used in the simulation experiment.
    SED-ML itself is independent of the model encoding underlying the models. The only
    requirement is that the model needs to be referenced by using an unambiguous identifier
    which allows for finding it, for example using a MIRIAM URI. To specify the language in
    which the model is encoded, a set of predefined language URNs is provided.
    The SED-ML Change class allows the application of changes to the referenced models,
    including changes on the XML attributes, e.g. changing the value of an observable,
    computing the change of a value using mathematics, or general changes on any XML element
    of the model representation that is addressable by XPath expressions, e.g. substituting
    a piece of XML by an updated one.

The Simulation Class
    The Simulation class defines the simulation settings and the steps taken during simulation.
    These include the particular type of simulation and the algorithm used for the execution of
    the simulation; preferably an unambiguous reference to such an algorithm should be given,
    using a controlled vocabulary, or ontologies. One example for an ontology of simulation
    algorithms is the Kinetic Simulation Algorithm Ontology KiSAO. Further information encodable
    in the Simulation class includes the step size, simulation duration, and other
    simulation-type dependent information.

The Task Class
    SED-ML makes use of the notion of a Task class to combine a defined model (from the Model class)
    and a defined simulation setting (from the Simulation class). A task always holds one reference each.
    To refer to a specific model and to a specific simulation, the corresponding IDs are used.

The DataGenerator Class
    The raw simulation result sometimes does not correspond to the desired output of the simulation,
    e.g. one might want to normalise a plot before output, or apply post-processing like mean-value calculation.
    The DataGenerator class allows for the encoding of such post-processings which need to be applied to the
    simulation result before output. To define data generators, any addressable variable or parameter
    of any defined model (from instances of the Model class) may be referenced, and new entities might
    be specified using MathML definitions.

The Output Class
    The Output class defines the output of the simulation, in the sense that it specifies what shall be
    plotted in the output. To do so, an output type is defined, e.g. 2D-plot, 3D-plot or data table,
    and the according axes or columns are all assigned to one of the formerly specified instances
    of the DataGenerator class.
"""

# TODO: implement full resolving of xpath expressions (target, selection) (see xpath.py for example)
# TODO: handle OMEX correctly & execute OMEX with saving results

# TODO: implement XML changes
# TODO: implement full concatenation of subtasks
# TODO: better handling of model.reset for task tree
# FIXME: rk4 integration not working on linux (https://github.com/sys-bio/roadrunner/issues/307)

from __future__ import print_function, division

import sys
import os.path
import warnings
import datetime
import zipfile
from collections import namedtuple
import re
import numpy as np
from jinja2 import Environment, FileSystemLoader
from mathml import evaluableMathML

import libsedml

import tellurium as te
from tellurium.tecombine import CombineArchive

try:
    # requrired imports within generated code
    import pandas
    import matplotlib.pyplot as plt
    import mpl_toolkits.mplot3d
except ImportError:
    warnings.warn("Dependencies for SEDML code execution not fullfilled.")


######################################################################################################################

def sedml_to_python(input):
    """ Convert sedml file to python code.

    Deprecated: use sedmlToPython()

    :param inputstring:
    :type inputstring:
    :return:
    :rtype:
    """
    warnings.warn('Use sedmlToPython instead, will be removed in v1.4',
                  DeprecationWarning, stacklevel=2)
    return sedmlToPython(input)


def sedmlToPython(inputStr):
    """ Convert sedml file to python code.

    :param inputstring: full path name to SedML model or SED-ML string
    :type inputstring: path
    :return: contents
    :rtype:
    """
    # FIXME: allow working directories (! the model & input files must be changed)
    factory = SEDMLCodeFactory(inputStr)
    return factory.toPython()


def executeSEDML(inputStr, workingDir=None):
    """ Run a SED-ML file or archive.

    If a workingDir is provided the files and results are written in the workingDir.

    :param inputStr:
    :type inputStr:
    :return:
    :rtype:
    """
    # execute the sedml
    factory = SEDMLCodeFactory(inputStr, workingDir=workingDir)
    factory.executePython()


def executeOMEX(omexPath, workingDir=None):
    """ Run all SED-ML simulations in given OMEX COMBINE archive.

    TODO: Necessary to get results back.

    :param omexPath: OMEX Combine archive
    :type omexPath: path
    :param workingDir: directory to extract archive to
    :type workingDir: directory path

    """
    filename, extension = os.path.splitext(os.path.basename(omexPath))

    # Archive
    if zipfile.is_zipfile(omexPath):

        # a directory is created in which the files are extracted
        if workingDir is None:
            extractDir = os.path.join(os.path.dirname(os.path.realpath(omexPath)), '_te_{}'.format(filename))
        else:
            extractDir = workingDir

        # extract the archive to working directory
        CombineArchive.extractArchive(omexPath, extractDir)
        # get SEDML files from archive
        sedmlFiles = CombineArchive.filePathsFromExtractedArchive(extractDir, filetype='sed-ml')

        if len(sedmlFiles) == 0:
            raise IOError("No SEDML files found in COMBINE archive: {}".format(omexPath))

        dgs = {}
        for sedmlFile in sedmlFiles:
            factory = SEDMLCodeFactory(sedmlFile, workingDir=os.path.dirname(sedmlFile))
            sedml_dgs = factory.executePython()
            dgs[sedmlFile] = sedml_dgs
        return dgs
    else:
        raise IOError("File is not an OMEX Combine Archive in zip format: {}".format(omexPath))



######################################################################################################################
# KISAO MAPPINGS
######################################################################################################################

KISAOS_CVODE = [  # 'cvode'
    'KISAO:0000019',  # CVODE
    'KISAO:0000433',  # CVODE-like method
    'KISAO:0000407',
    'KISAO:0000099',
    'KISAO:0000035',
    'KISAO:0000071',
    "KISAO:0000288",  # "BDF" cvode, stiff=true
    "KISAO:0000280",  # "Adams-Moulton" cvode, stiff=false
]

KISAOS_RK4 = [  # 'rk4'
    'KISAO:0000032',  # RK4 explicit fourth-order Runge-Kutta method
    'KISAO:0000064',  # Runge-Kutta based method
]

KISAOS_RK45 = [  # 'rk45'
    'KISAO:0000086',  # RKF45 embedded Runge-Kutta-Fehlberg 5(4) method
]

KISAOS_GILLESPIE = [  # 'gillespie'
    'KISAO:0000241',  # Gillespie-like method
    'KISAO:0000029',
    'KISAO:0000319',
    'KISAO:0000274',
    'KISAO:0000333',
    'KISAO:0000329',
    'KISAO:0000323',
    'KISAO:0000331',
    'KISAO:0000027',
    'KISAO:0000082',
    'KISAO:0000324',
    'KISAO:0000350',
    'KISAO:0000330',
    'KISAO:0000028',
    'KISAO:0000038',
    'KISAO:0000039',
    'KISAO:0000048',
    'KISAO:0000074',
    'KISAO:0000081',
    'KISAO:0000045',
    'KISAO:0000351',
    'KISAO:0000084',
    'KISAO:0000040',
    'KISAO:0000046',
    'KISAO:0000003',
    'KISAO:0000051',
    'KISAO:0000335',
    'KISAO:0000336',
    'KISAO:0000095',
    'KISAO:0000022',
    'KISAO:0000076',
    'KISAO:0000015',
    'KISAO:0000075',
    'KISAO:0000278',
]

KISAOS_NLEQ = [  # 'nleq'
    'KISAO:0000099',
    'KISAO:0000274',
    'KISAO:0000282',
    'KISAO:0000283',
    'KISAO:0000355',
    'KISAO:0000356',
    'KISAO:0000407',
    'KISAO:0000408',
    'KISAO:0000409',
    'KISAO:0000410',
    'KISAO:0000411',
    'KISAO:0000412',
    'KISAO:0000413',
    'KISAO:0000432',
    'KISAO:0000437',
]

# allowed algorithms for simulation type
KISAOS_STEADYSTATE = KISAOS_NLEQ
KISAOS_UNIFORMTIMECOURSE = KISAOS_CVODE + KISAOS_RK4 + KISAOS_RK45 + KISAOS_GILLESPIE
KISAOS_ONESTEP = KISAOS_UNIFORMTIMECOURSE

# supported algorithm parameters
KISAOS_ALGORITHMPARAMETERS = {
    'KISAO:0000209': ('relative_tolerance', float),  # the relative tolerance
    'KISAO:0000211': ('absolute_tolerance', float),  # the absolute tolerance
    'KISAO:0000220': ('maximum_bdf_order', int),  # the maximum BDF (stiff) order
    'KISAO:0000219': ('maximum_adams_order', int),  # the maximum Adams (non-stiff) order
    'KISAO:0000415': ('maximum_num_steps', int),  # the maximum number of steps that can be taken before exiting
    'KISAO:0000467': ('maximum_time_step', float),  # the maximum time step that can be taken
    'KISAO:0000485': ('minimum_time_step', float),  # the minimum time step that can be taken
    'KISAO:0000332': ('initial_time_step', float),  # the initial value of the time step for algorithms that change this value
    'KISAO:0000107': ('variable_step_size', bool),  # whether or not the algorithm proceeds with an adaptive step size or not
    'KISAO:0000486': ('maximum_iterations', int),  # [nleq] the maximum number of iterations the algorithm should take before exiting
    'KISAO:0000487': ('minimum_damping', float),  # [nleq] minimum damping value
    'KISAO:0000488': ('seed', int),  # the seed for stochastic runs of the algorithm
}
######################################################################################################################


class SEDMLCodeFactory(object):
    """ Code Factory generating executable code."""

    # template location
    TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')

    def __init__(self, inputStr, workingDir=None):
        """ Create CodeFactory for given input.

        :param inputStr:
        :type inputStr:
        :return:
        :rtype:
        """
        self.inputStr = inputStr
        self.workingDir = workingDir
        info = SEDMLTools.readSEDMLDocument(inputStr, workingDir)
        self.doc = info['doc']
        self.inputType = info['inputType']
        self.workingDir = info['workingDir']

        # parse the models (resolve the source models & the applied changes for all models)
        model_sources, model_changes = SEDMLTools.resolveModelChanges(self.doc)
        self.model_sources = model_sources
        self.model_changes = model_changes

    def __str__(self):
        """ Print.

        :return:
        :rtype:
        """
        lines = [
            '{}'.format(self.__class__),
            'doc: {}'.format(self.doc),
            'workingDir: {}'.format(self.workingDir),
            'inputType: {}'.format(self.inputType)
        ]
        if self.inputType != SEDMLTools.INPUT_TYPE_STR:
            lines.append('input: {}'.format(self.inputStr))
        return '\n'.join(lines)

    def sedmlString(self):
        """ Get the SEDML XML string of the current document.

        :return: SED-ML XML
        :rtype: str
        """
        return libsedml.writeSedMLToString(self.doc)

    def toPython(self, python_template='tesedml_template.template'):
        """ Create python code by rendering the python template.
        Uses the information in the SED-ML document to create
        python code

        Renders the respective template.

        :return: returns the rendered template
        :rtype: str
        """
        # template environment
        env = Environment(loader=FileSystemLoader(self.TEMPLATE_DIR),
                          extensions=['jinja2.ext.autoescape'],
                          trim_blocks=True,
                          lstrip_blocks=True)

        # additional filters
        # for key in sedmlfilters.filters:
        #      env.filters[key] = getattr(sedmlfilters, key)
        template = env.get_template(python_template)
        env.globals['modelToPython'] = self.modelToPython
        env.globals['taskToPython'] = self.taskToPython
        env.globals['dataGeneratorToPython'] = self.dataGeneratorToPython
        env.globals['outputToPython'] = self.outputToPython

        # timestamp
        time = datetime.datetime.now()
        timestamp = time.strftime('%Y-%m-%dT%H:%M:%S')

        # Context
        c = {
            'version': te.getTelluriumVersion(),
            'timestamp': timestamp,
            'factory': self,
            'doc': self.doc,
            'model_sources': self.model_sources,
            'model_changes': self.model_changes,
        }
        try:
            pysedml = template.render(c)
        except Exception as e:
            # something went wrong in the conversion to python
            # show detailed information about the factory
            # and the libsedml document
            # msg = e.message + '\n' + self.__str__() + '\n' + libsedml.writeSedMLToString(self.doc) + '\n'
            msg = e.message
            raise type(e), type(e)(msg), sys.exc_info()[2]

        return pysedml

    def executePython(self):
        """ Executes python code.

        The python code is created during the function call.
        See :func:`createpython`
        """
        execStr = self.toPython()
        try:
            # This calls exec. Be very sure that nothing bad happens here.
            exec execStr

            # return dictionary of data generators
            dg_data = {}
            for dg in self.doc.getListOfDataGenerators():
                dg_id = dg.getId()
                dg_data[dg_id] = locals()[dg_id]
            return dg_data

        except Exception as e:
            # something went wrong in the conversion to python
            # show detailed information about the factory
            # and the libsedml document
            raise type(e), type(e)('-'*80 + '\n'
                                   + self.__str__() + '\n'
                                   + '*'*80 + '\n'
                                   + execStr + '\n'
                                   + '*'*80 + '\n'
                                   + e.message + '\n'
                                   ), sys.exc_info()[2]

    def modelToPython(self, model):
        """ Python code for SedModel.

        :param model: SedModel instance
        :type model: SedModel
        :return: python str
        :rtype: str
        """
        lines = []
        mid = model.getId()
        language = model.getLanguage()
        source = self.model_sources[mid]

        def isUrn():
            return source.startswith('urn') or source.startswith('URN')

        def isHttp():
            return source.startswith('http') or source.startswith('HTTP')

        # read SBML
        if 'sbml' in language:
            if isUrn():
                lines.append("import tellurium.temiriam as temiriam")
                lines.append("__{}_sbml = temiriam.getSBMLFromBiomodelsURN('{}')".format(mid, source))
                lines.append("{} = te.loadSBMLModel(__{}_sbml)".format(mid, mid))
            elif isHttp():
                lines.append("{} = te.loadSBMLModel('{}')".format(mid, source))
            else:
                if not source.endswith('.xml'):
                    source = source + '.xml'
                lines.append("{} = te.loadSBMLModel(os.path.join(workingDir, '{}'))".format(mid, source))
        # read CellML
        elif 'cellml' in language:
            if isHttp():
                lines.append("{} = te.loadCellMLModel('{}')".format(mid, source))
            else:
                lines.append("{} = te.loadCellMLModel(os.path.join(workingDir, '{}'))".format(mid, self.model_sources[mid]))
        # other
        else:
            warnings.warn("Unsupported model language:".format(language))

        # apply model changes
        for change in self.model_changes[mid]:
            lines.extend(SEDMLCodeFactory.modelChangeToPython(model, change))

        return '\n'.join(lines)

    @staticmethod
    def modelChangeToPython(model, change):
        """ Creates the apply change python string for given model and change.

        Currently only a very limited subset of model changes is supported.
        Namely changes of parameters and concentrations within a SedChangeAttribute.

        :param model: given model
        :type model: SedModel
        :param change: model change
        :type change: SedChange
        :return:
        :rtype: str
        """
        lines = []
        mid = model.getId()
        xpath = change.getTarget()

        if change.getTypeCode() == libsedml.SEDML_CHANGE_ATTRIBUTE:
            # resolve target change
            value = change.getNewValue()
            lines.append("# {} {}".format(xpath, value))
            lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid))

        elif change.getTypeCode() == libsedml.SEDML_CHANGE_COMPUTECHANGE:
            variables = {}
            for par in change.getListOfParameters():
                variables[par.getId()] = par.getValue()
            for var in change.getListOfVariables():
                vid = var.getId()
                selection = SEDMLCodeFactory.selectionFromVariable(var, mid)
                expr = selection.id
                if selection.type == "concentration":
                    expr = "init([{}])".format(selection.id)
                elif selection.type == "amount":
                    expr = "init({})".format(selection.id)
                lines.append("__var__{} = {}['{}']".format(vid, mid, expr))
                variables[vid] = "__var__{}".format(vid)

            # value is calculated with the current state of model
            value = evaluableMathML(change.getMath(), variables=variables)
            lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid))

        elif change.getTypeCode() in [libsedml.SEDML_CHANGE_REMOVEXML,
                                      libsedml.SEDML_CHANGE_ADDXML,
                                      libsedml.SEDML_CHANGE_CHANGEXML]:
            lines.append("# Unsupported change: {}".format(change.getElementName()))
            warnings.warn("Unsupported change: {}".format(change.getElementName()))
        else:
            lines.append("# Unsupported change: {}".format(change.getElementName()))
            warnings.warn("Unsupported change: {}".format(change.getElementName()))

        return lines

    ################################################################################################
    # Here the main work is done,
    # transformation of tasks to python code
    ################################################################################################
    @staticmethod
    def taskToPython(doc, task):
        """ Create python for arbitrary task (repeated or simple).

        :param doc:
        :type doc:
        :param task:
        :type task:
        :return:
        :rtype:
        """
        # If no DataGenerator references the task, no execution is necessary
        dgs = SEDMLCodeFactory.getDataGeneratorsForTask(doc, task)
        if len(dgs) == 0:
            return "# not part of any DataGenerator: {}".format(task.getId())

        # tasks contain other subtasks, which can contain subtasks. This
        # results in a tree of task dependencies where the
        # simple tasks are the node leaves. These tree has to be resolved to
        # generate code for more complex task dependencies.

        # resolve task tree (order & dependency of tasks) & generate code
        taskTree = SEDMLCodeFactory.createTaskTree(doc, rootTask=task)
        return SEDMLCodeFactory.taskTreeToPython(doc, tree=taskTree)

    class TaskNode(object):
        """ Tree implementation of task tree. """
        def __init__(self, task, depth):
            self.task = task
            self.depth = depth
            self.children = []
            self.parent = None

        def add_child(self, obj):
            obj.parent = self
            self.children.append(obj)

        def is_leaf(self):
            return len(self.children) == 0

        def __str__(self):
            lines = ["<[{}] {} ({})>".format(self.depth, self.task.getId(), self.task.getElementName())]
            for child in self.children:
                child_str = child.__str__()
                lines.extend(["\t{}".format(line) for line in child_str.split('\n')])
            return "\n".join(lines)

        def info(self):
            return "<[{}] {} ({})>".format(self.depth, self.task.getId(), self.task.getElementName())

        def __iter__(self):
            """ Depth-first iterator which yields TaskNodes."""
            yield self
            for child in self.children:
                for node in child:
                    yield node

    class Stack(object):
        """ Stack implementation for nodes."""
        def __init__(self):
            self.items = []

        def isEmpty(self):
            return self.items == []

        def push(self, item):
            self.items.append(item)

        def pop(self):
            return self.items.pop()

        def peek(self):
            return self.items[len(self.items)-1]

        def size(self):
            return len(self.items)

        def __str__(self):
            return "stack: " + str([item.info() for item in self.items])

    @staticmethod
    def createTaskTree(doc, rootTask):
        """ Creates the task tree.
        Required for resolution of order of all simulations.
        """
        def add_children(node):
            typeCode = node.task.getTypeCode()
            if typeCode == libsedml.SEDML_TASK:
                return  # no children
            elif typeCode == libsedml.SEDML_TASK_REPEATEDTASK:
                # add the ordered list of subtasks as children
                subtasks = SEDMLCodeFactory.getOrderedSubtasks(node.task)
                for st in subtasks:
                    # get real task for subtask
                    t = doc.getTask(st.getTask())
                    child = SEDMLCodeFactory.TaskNode(t, depth=node.depth+1)
                    node.add_child(child)
                    # recursive adding of children
                    add_children(child)
            else:
                raise IOError('Unsupported task type: {}'.format(node.task.getElementName()))

        # create root
        root = SEDMLCodeFactory.TaskNode(rootTask, depth=0)
        # recursive adding of children
        add_children(root)
        return root

    @staticmethod
    def getOrderedSubtasks(task):
        """ Ordered list of subtasks for task."""
        subtasks = task.getListOfSubTasks()
        subtaskOrder = [st.getOrder() for st in subtasks]
        # sort by order, if all subtasks have order (not required)
        if all(subtaskOrder) != None:
            subtasks = [st for (stOrder, st) in sorted(zip(subtaskOrder, subtasks))]
        return subtasks

    @staticmethod
    def taskTreeToPython(doc, tree):
        """ Python code generation from task tree. """
        # print(tree)

        # TODO: implement the merge of subtasks & and collection of simulations

        # go forward through task tree
        lines = []
        nodeStack = SEDMLCodeFactory.Stack()
        treeNodes = [n for n in tree]

        # iterate over the tree
        for kn, node in enumerate(treeNodes):
            # print("* [{}] <{} ({})>".format(node.depth, node.task.getId(), node.task.getElementName()))
            # print(nodeStack)
            taskType = node.task.getTypeCode()

            # Create information for task
            # We are going down in the tree
            if taskType == libsedml.SEDML_TASK_REPEATEDTASK:
                taskLines = SEDMLCodeFactory.repeatedTaskToPython(doc, node=node)

            elif taskType == libsedml.SEDML_TASK:
                tid = node.task.getId()
                taskLines = SEDMLCodeFactory.simpleTaskToPython(doc=doc, node=node)
            else:
                lines.append("# Unsupported task: {}".format(taskType))
                warnings.warn("Unsupported task: {}".format(taskType))

            lines.extend(["    "*node.depth + line for line in taskLines])

            '''
            @staticmethod
            def simpleTaskToPython(doc, task):
                """ Create python for simple task. """
                for ksub, subtask in enumerate(subtasks):
                    t = doc.getTask(subtask.getTask())

                    resultVariable = "__subtask__".format(t.getId())
                    selections = SEDMLCodeFactory.selectionsForTask(doc=doc, task=task)
                    if t.getTypeCode() == libsedml.SEDML_TASK:
                        forLines.extend(SEDMLCodeFactory.subtaskToPython(doc, task=t,
                                                                  selections=selections,
                                                                  resultVariable=resultVariable))
                        forLines.append("{}.extend([__subtask__])".format(task.getId()))

                    elif t.getTypeCode() == libsedml.SEDML_TASK_REPEATEDTASK:
                        forLines.extend(SEDMLCodeFactory.repeatedTaskToPython(doc, task=t))
                        forLines.append("{}.extend({})".format(task.getId(), t.getId()))
            '''

            # Collect information
            # We have to go back up
            # Look at next node in the treeNodes (this is the next one to write)
            if kn == (len(treeNodes)-1):
                nextNode = None
            else:
                nextNode = treeNodes[kn+1]

            # The next node is further up in the tree, or there is no next node
            # and still nodes on the stack
            if (nextNode is None) or (nextNode.depth < node.depth):
                # if (nextNode is None):
                #     print('last node')
                # else:
                #     print('nextNode higher')

                # necessary to pop nodes from the stack and close the code
                test = True
                while test is True:
                    # stack is empty
                    if nodeStack.size() == 0:
                        test = False
                        continue
                    # try to pop next one
                    peek = nodeStack.peek()
                    if (nextNode is None) or (peek.depth > nextNode.depth):
                        # TODO: reset evaluation has to be defined here
                        lines.extend([
                            "",
                            "    "*node.depth + "{}.extend({})".format(peek.task.getId(), node.task.getId()),
                        ])
                        node = nodeStack.pop()
                        # print("pop:", peek.info())

                    else:
                        test = False
            else:
                # we are going done or next subtask -> put node on stack
                nodeStack.push(node)
                # print("push:", node.info())

        return "\n".join(lines)

    @staticmethod
    def simpleTaskToPython(doc, node):
        """ Creates the simulation python code for a given taskNode.

        The taskNodes are required to handle the relationships between
        RepeatedTasks, SubTasks and SimpleTasks (Task).

        :param doc: sedml document
        :type doc: SEDDocument
        :param node: taskNode of the current task
        :type node: TaskNode
        :return:
        :rtype:
        """
        lines = []
        task = node.task
        lines.append("# Task: <{}>".format(task.getId()))
        lines.append("{} = [None]".format(task.getId()))

        mid = task.getModelReference()
        sid = task.getSimulationReference()
        simulation = doc.getSimulation(sid)

        simType = simulation.getTypeCode()
        algorithm = simulation.getAlgorithm()
        if algorithm is None:
            warnings.warn("Algorithm missing on simulation, defaulting to 'cvode: KISAO:0000019'")
            algorithm = simulation.createAlgorithm()
            algorithm.setKisaoID("KISAO:0000019")
        kisao = algorithm.getKisaoID()

        # is supported algorithm
        if not SEDMLCodeFactory.isSupportedAlgorithmForSimulationType(kisao=kisao, simType=simType):
            lines.append("# Unsupported Algorithm {} for SimulationType {}".format(kisao, simulation.getElementName()))
            return lines

        # set integrator/solver
        integratorName = SEDMLCodeFactory.getIntegratorNameForKisaoID(kisao)
        if not integratorName:
            warnings.warn("No integrator exists for {} in roadrunner".format(kisao))
            return lines

        if simType is libsedml.SEDML_SIMULATION_STEADYSTATE:
            lines.append("{}.setSteadyStateSolver('{}')".format(mid, integratorName))
        else:
            lines.append("{}.setIntegrator('{}')".format(mid, integratorName))

        if kisao == "KISAO:0000288":  # BDF
            lines.append("{}.integrator.setValue('{}', {})".format(mid, 'stiff', True))
        elif kisao == "KISAO:0000280":  # Adams-Moulton
            lines.append("{}.integrator.setValue('{}', {})".format(mid, 'stiff', False))

        # integrator/solver settings (AlgorithmParameters)
        for par in algorithm.getListOfAlgorithmParameters():
            pkey = SEDMLCodeFactory.algorithmParameterToParameterKey(par)
            if pkey.dtype is str:
                value = "'{}'".format(pkey.value)
            else:
                value = pkey.value
                
            if value == str('inf') or pkey.value == float('inf'):
                value = "float('inf')"
            else:<commit_msg>issue warnings for missing packages<commit_after># -*- coding: utf-8 -*-
"""
Tellurium SED-ML support.

This module implements SED-ML L1V2 support for tellurium.
In the current implementation all SED-ML constructs with exception of
    XML transformation changes of the model
        - Change.RemoveXML
        - Change.AddXML
        - Change.ChangeXML
are supported.

SBML models are fully supported, whereas for CellML models only basic support
is implemented (when additional support is requested it be implemented).
CellML models are transformed to SBML models which results in different XPath expressions,
so that targets, selections cannot be easily resolved in the CellMl-SBML.

Supported input for SED-ML are either SED-ML files ('.sedml' extension),
SED-ML XML strings or archives ('.sedx'|'.omex' extension).
Executable python code is generated from the SED-ML which allows the
execution of the defined simulation experiment.

SED-ML is build of five main classes
    the Model Class,
    the Simulation Class,
    the Task Class,
    the DataGenerator Class,
    and the Output Class.

The Model Class
    The Model class is used to reference the models used in the simulation experiment.
    SED-ML itself is independent of the model encoding underlying the models. The only
    requirement is that the model needs to be referenced by using an unambiguous identifier
    which allows for finding it, for example using a MIRIAM URI. To specify the language in
    which the model is encoded, a set of predefined language URNs is provided.
    The SED-ML Change class allows the application of changes to the referenced models,
    including changes on the XML attributes, e.g. changing the value of an observable,
    computing the change of a value using mathematics, or general changes on any XML element
    of the model representation that is addressable by XPath expressions, e.g. substituting
    a piece of XML by an updated one.

The Simulation Class
    The Simulation class defines the simulation settings and the steps taken during simulation.
    These include the particular type of simulation and the algorithm used for the execution of
    the simulation; preferably an unambiguous reference to such an algorithm should be given,
    using a controlled vocabulary, or ontologies. One example for an ontology of simulation
    algorithms is the Kinetic Simulation Algorithm Ontology KiSAO. Further information encodable
    in the Simulation class includes the step size, simulation duration, and other
    simulation-type dependent information.

The Task Class
    SED-ML makes use of the notion of a Task class to combine a defined model (from the Model class)
    and a defined simulation setting (from the Simulation class). A task always holds one reference each.
    To refer to a specific model and to a specific simulation, the corresponding IDs are used.

The DataGenerator Class
    The raw simulation result sometimes does not correspond to the desired output of the simulation,
    e.g. one might want to normalise a plot before output, or apply post-processing like mean-value calculation.
    The DataGenerator class allows for the encoding of such post-processings which need to be applied to the
    simulation result before output. To define data generators, any addressable variable or parameter
    of any defined model (from instances of the Model class) may be referenced, and new entities might
    be specified using MathML definitions.

The Output Class
    The Output class defines the output of the simulation, in the sense that it specifies what shall be
    plotted in the output. To do so, an output type is defined, e.g. 2D-plot, 3D-plot or data table,
    and the according axes or columns are all assigned to one of the formerly specified instances
    of the DataGenerator class.
"""

# TODO: implement full resolving of xpath expressions (target, selection) (see xpath.py for example)
# TODO: handle OMEX correctly & execute OMEX with saving results

# TODO: implement XML changes
# TODO: implement full concatenation of subtasks
# TODO: better handling of model.reset for task tree
# FIXME: rk4 integration not working on linux (https://github.com/sys-bio/roadrunner/issues/307)

from __future__ import print_function, division

import sys
import os.path
import warnings
import datetime
import zipfile
from collections import namedtuple
import re
import numpy as np
try:
    from jinja2 import Environment, FileSystemLoader
except:
    warnings.warn("'jinja2' could not be imported; SEDML not supported", ImportWarning)
from mathml import evaluableMathML

try:
    import libsedml
    # import libsedml before libsbml to handle
    # https://github.com/fbergmann/libSEDML/issues/21
except ImportError as e:
    libsedml = None
    roadrunner.Logger.log(roadrunner.Logger.LOG_WARNING, str(e))
    warnings.warn("'libsedml' could not be imported", ImportWarning, stacklevel=2)

import tellurium as te
from tellurium.tecombine import CombineArchive

try:
    # requrired imports within generated code
    import pandas
    import matplotlib.pyplot as plt
    import mpl_toolkits.mplot3d
except ImportError:
    warnings.warn("Dependencies for SEDML code execution not fullfilled.")


######################################################################################################################

def sedml_to_python(input):
    """ Convert sedml file to python code.

    Deprecated: use sedmlToPython()

    :param inputstring:
    :type inputstring:
    :return:
    :rtype:
    """
    warnings.warn('Use sedmlToPython instead, will be removed in v1.4',
                  DeprecationWarning, stacklevel=2)
    return sedmlToPython(input)


def sedmlToPython(inputStr):
    """ Convert sedml file to python code.

    :param inputstring: full path name to SedML model or SED-ML string
    :type inputstring: path
    :return: contents
    :rtype:
    """
    # FIXME: allow working directories (! the model & input files must be changed)
    factory = SEDMLCodeFactory(inputStr)
    return factory.toPython()


def executeSEDML(inputStr, workingDir=None):
    """ Run a SED-ML file or archive.

    If a workingDir is provided the files and results are written in the workingDir.

    :param inputStr:
    :type inputStr:
    :return:
    :rtype:
    """
    # execute the sedml
    factory = SEDMLCodeFactory(inputStr, workingDir=workingDir)
    factory.executePython()


def executeOMEX(omexPath, workingDir=None):
    """ Run all SED-ML simulations in given OMEX COMBINE archive.

    TODO: Necessary to get results back.

    :param omexPath: OMEX Combine archive
    :type omexPath: path
    :param workingDir: directory to extract archive to
    :type workingDir: directory path

    """
    filename, extension = os.path.splitext(os.path.basename(omexPath))

    # Archive
    if zipfile.is_zipfile(omexPath):

        # a directory is created in which the files are extracted
        if workingDir is None:
            extractDir = os.path.join(os.path.dirname(os.path.realpath(omexPath)), '_te_{}'.format(filename))
        else:
            extractDir = workingDir

        # extract the archive to working directory
        CombineArchive.extractArchive(omexPath, extractDir)
        # get SEDML files from archive
        sedmlFiles = CombineArchive.filePathsFromExtractedArchive(extractDir, filetype='sed-ml')

        if len(sedmlFiles) == 0:
            raise IOError("No SEDML files found in COMBINE archive: {}".format(omexPath))

        dgs = {}
        for sedmlFile in sedmlFiles:
            factory = SEDMLCodeFactory(sedmlFile, workingDir=os.path.dirname(sedmlFile))
            sedml_dgs = factory.executePython()
            dgs[sedmlFile] = sedml_dgs
        return dgs
    else:
        raise IOError("File is not an OMEX Combine Archive in zip format: {}".format(omexPath))



######################################################################################################################
# KISAO MAPPINGS
######################################################################################################################

KISAOS_CVODE = [  # 'cvode'
    'KISAO:0000019',  # CVODE
    'KISAO:0000433',  # CVODE-like method
    'KISAO:0000407',
    'KISAO:0000099',
    'KISAO:0000035',
    'KISAO:0000071',
    "KISAO:0000288",  # "BDF" cvode, stiff=true
    "KISAO:0000280",  # "Adams-Moulton" cvode, stiff=false
]

KISAOS_RK4 = [  # 'rk4'
    'KISAO:0000032',  # RK4 explicit fourth-order Runge-Kutta method
    'KISAO:0000064',  # Runge-Kutta based method
]

KISAOS_RK45 = [  # 'rk45'
    'KISAO:0000086',  # RKF45 embedded Runge-Kutta-Fehlberg 5(4) method
]

KISAOS_GILLESPIE = [  # 'gillespie'
    'KISAO:0000241',  # Gillespie-like method
    'KISAO:0000029',
    'KISAO:0000319',
    'KISAO:0000274',
    'KISAO:0000333',
    'KISAO:0000329',
    'KISAO:0000323',
    'KISAO:0000331',
    'KISAO:0000027',
    'KISAO:0000082',
    'KISAO:0000324',
    'KISAO:0000350',
    'KISAO:0000330',
    'KISAO:0000028',
    'KISAO:0000038',
    'KISAO:0000039',
    'KISAO:0000048',
    'KISAO:0000074',
    'KISAO:0000081',
    'KISAO:0000045',
    'KISAO:0000351',
    'KISAO:0000084',
    'KISAO:0000040',
    'KISAO:0000046',
    'KISAO:0000003',
    'KISAO:0000051',
    'KISAO:0000335',
    'KISAO:0000336',
    'KISAO:0000095',
    'KISAO:0000022',
    'KISAO:0000076',
    'KISAO:0000015',
    'KISAO:0000075',
    'KISAO:0000278',
]

KISAOS_NLEQ = [  # 'nleq'
    'KISAO:0000099',
    'KISAO:0000274',
    'KISAO:0000282',
    'KISAO:0000283',
    'KISAO:0000355',
    'KISAO:0000356',
    'KISAO:0000407',
    'KISAO:0000408',
    'KISAO:0000409',
    'KISAO:0000410',
    'KISAO:0000411',
    'KISAO:0000412',
    'KISAO:0000413',
    'KISAO:0000432',
    'KISAO:0000437',
]

# allowed algorithms for simulation type
KISAOS_STEADYSTATE = KISAOS_NLEQ
KISAOS_UNIFORMTIMECOURSE = KISAOS_CVODE + KISAOS_RK4 + KISAOS_RK45 + KISAOS_GILLESPIE
KISAOS_ONESTEP = KISAOS_UNIFORMTIMECOURSE

# supported algorithm parameters
KISAOS_ALGORITHMPARAMETERS = {
    'KISAO:0000209': ('relative_tolerance', float),  # the relative tolerance
    'KISAO:0000211': ('absolute_tolerance', float),  # the absolute tolerance
    'KISAO:0000220': ('maximum_bdf_order', int),  # the maximum BDF (stiff) order
    'KISAO:0000219': ('maximum_adams_order', int),  # the maximum Adams (non-stiff) order
    'KISAO:0000415': ('maximum_num_steps', int),  # the maximum number of steps that can be taken before exiting
    'KISAO:0000467': ('maximum_time_step', float),  # the maximum time step that can be taken
    'KISAO:0000485': ('minimum_time_step', float),  # the minimum time step that can be taken
    'KISAO:0000332': ('initial_time_step', float),  # the initial value of the time step for algorithms that change this value
    'KISAO:0000107': ('variable_step_size', bool),  # whether or not the algorithm proceeds with an adaptive step size or not
    'KISAO:0000486': ('maximum_iterations', int),  # [nleq] the maximum number of iterations the algorithm should take before exiting
    'KISAO:0000487': ('minimum_damping', float),  # [nleq] minimum damping value
    'KISAO:0000488': ('seed', int),  # the seed for stochastic runs of the algorithm
}
######################################################################################################################


class SEDMLCodeFactory(object):
    """ Code Factory generating executable code."""

    # template location
    TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')

    def __init__(self, inputStr, workingDir=None):
        """ Create CodeFactory for given input.

        :param inputStr:
        :type inputStr:
        :return:
        :rtype:
        """
        self.inputStr = inputStr
        self.workingDir = workingDir
        info = SEDMLTools.readSEDMLDocument(inputStr, workingDir)
        self.doc = info['doc']
        self.inputType = info['inputType']
        self.workingDir = info['workingDir']

        # parse the models (resolve the source models & the applied changes for all models)
        model_sources, model_changes = SEDMLTools.resolveModelChanges(self.doc)
        self.model_sources = model_sources
        self.model_changes = model_changes

    def __str__(self):
        """ Print.

        :return:
        :rtype:
        """
        lines = [
            '{}'.format(self.__class__),
            'doc: {}'.format(self.doc),
            'workingDir: {}'.format(self.workingDir),
            'inputType: {}'.format(self.inputType)
        ]
        if self.inputType != SEDMLTools.INPUT_TYPE_STR:
            lines.append('input: {}'.format(self.inputStr))
        return '\n'.join(lines)

    def sedmlString(self):
        """ Get the SEDML XML string of the current document.

        :return: SED-ML XML
        :rtype: str
        """
        return libsedml.writeSedMLToString(self.doc)

    def toPython(self, python_template='tesedml_template.template'):
        """ Create python code by rendering the python template.
        Uses the information in the SED-ML document to create
        python code

        Renders the respective template.

        :return: returns the rendered template
        :rtype: str
        """
        # template environment
        env = Environment(loader=FileSystemLoader(self.TEMPLATE_DIR),
                          extensions=['jinja2.ext.autoescape'],
                          trim_blocks=True,
                          lstrip_blocks=True)

        # additional filters
        # for key in sedmlfilters.filters:
        #      env.filters[key] = getattr(sedmlfilters, key)
        template = env.get_template(python_template)
        env.globals['modelToPython'] = self.modelToPython
        env.globals['taskToPython'] = self.taskToPython
        env.globals['dataGeneratorToPython'] = self.dataGeneratorToPython
        env.globals['outputToPython'] = self.outputToPython

        # timestamp
        time = datetime.datetime.now()
        timestamp = time.strftime('%Y-%m-%dT%H:%M:%S')

        # Context
        c = {
            'version': te.getTelluriumVersion(),
            'timestamp': timestamp,
            'factory': self,
            'doc': self.doc,
            'model_sources': self.model_sources,
            'model_changes': self.model_changes,
        }
        try:
            pysedml = template.render(c)
        except Exception as e:
            # something went wrong in the conversion to python
            # show detailed information about the factory
            # and the libsedml document
            # msg = e.message + '\n' + self.__str__() + '\n' + libsedml.writeSedMLToString(self.doc) + '\n'
            msg = e.message
            raise type(e), type(e)(msg), sys.exc_info()[2]

        return pysedml

    def executePython(self):
        """ Executes python code.

        The python code is created during the function call.
        See :func:`createpython`
        """
        execStr = self.toPython()
        try:
            # This calls exec. Be very sure that nothing bad happens here.
            exec execStr

            # return dictionary of data generators
            dg_data = {}
            for dg in self.doc.getListOfDataGenerators():
                dg_id = dg.getId()
                dg_data[dg_id] = locals()[dg_id]
            return dg_data

        except Exception as e:
            # something went wrong in the conversion to python
            # show detailed information about the factory
            # and the libsedml document
            raise type(e), type(e)('-'*80 + '\n'
                                   + self.__str__() + '\n'
                                   + '*'*80 + '\n'
                                   + execStr + '\n'
                                   + '*'*80 + '\n'
                                   + e.message + '\n'
                                   ), sys.exc_info()[2]

    def modelToPython(self, model):
        """ Python code for SedModel.

        :param model: SedModel instance
        :type model: SedModel
        :return: python str
        :rtype: str
        """
        lines = []
        mid = model.getId()
        language = model.getLanguage()
        source = self.model_sources[mid]

        def isUrn():
            return source.startswith('urn') or source.startswith('URN')

        def isHttp():
            return source.startswith('http') or source.startswith('HTTP')

        # read SBML
        if 'sbml' in language:
            if isUrn():
                lines.append("import tellurium.temiriam as temiriam")
                lines.append("__{}_sbml = temiriam.getSBMLFromBiomodelsURN('{}')".format(mid, source))
                lines.append("{} = te.loadSBMLModel(__{}_sbml)".format(mid, mid))
            elif isHttp():
                lines.append("{} = te.loadSBMLModel('{}')".format(mid, source))
            else:
                if not source.endswith('.xml'):
                    source = source + '.xml'
                lines.append("{} = te.loadSBMLModel(os.path.join(workingDir, '{}'))".format(mid, source))
        # read CellML
        elif 'cellml' in language:
            if isHttp():
                lines.append("{} = te.loadCellMLModel('{}')".format(mid, source))
            else:
                lines.append("{} = te.loadCellMLModel(os.path.join(workingDir, '{}'))".format(mid, self.model_sources[mid]))
        # other
        else:
            warnings.warn("Unsupported model language:".format(language))

        # apply model changes
        for change in self.model_changes[mid]:
            lines.extend(SEDMLCodeFactory.modelChangeToPython(model, change))

        return '\n'.join(lines)

    @staticmethod
    def modelChangeToPython(model, change):
        """ Creates the apply change python string for given model and change.

        Currently only a very limited subset of model changes is supported.
        Namely changes of parameters and concentrations within a SedChangeAttribute.

        :param model: given model
        :type model: SedModel
        :param change: model change
        :type change: SedChange
        :return:
        :rtype: str
        """
        lines = []
        mid = model.getId()
        xpath = change.getTarget()

        if change.getTypeCode() == libsedml.SEDML_CHANGE_ATTRIBUTE:
            # resolve target change
            value = change.getNewValue()
            lines.append("# {} {}".format(xpath, value))
            lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid))

        elif change.getTypeCode() == libsedml.SEDML_CHANGE_COMPUTECHANGE:
            variables = {}
            for par in change.getListOfParameters():
                variables[par.getId()] = par.getValue()
            for var in change.getListOfVariables():
                vid = var.getId()
                selection = SEDMLCodeFactory.selectionFromVariable(var, mid)
                expr = selection.id
                if selection.type == "concentration":
                    expr = "init([{}])".format(selection.id)
                elif selection.type == "amount":
                    expr = "init({})".format(selection.id)
                lines.append("__var__{} = {}['{}']".format(vid, mid, expr))
                variables[vid] = "__var__{}".format(vid)

            # value is calculated with the current state of model
            value = evaluableMathML(change.getMath(), variables=variables)
            lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid))

        elif change.getTypeCode() in [libsedml.SEDML_CHANGE_REMOVEXML,
                                      libsedml.SEDML_CHANGE_ADDXML,
                                      libsedml.SEDML_CHANGE_CHANGEXML]:
            lines.append("# Unsupported change: {}".format(change.getElementName()))
            warnings.warn("Unsupported change: {}".format(change.getElementName()))
        else:
            lines.append("# Unsupported change: {}".format(change.getElementName()))
            warnings.warn("Unsupported change: {}".format(change.getElementName()))

        return lines

    ################################################################################################
    # Here the main work is done,
    # transformation of tasks to python code
    ################################################################################################
    @staticmethod
    def taskToPython(doc, task):
        """ Create python for arbitrary task (repeated or simple).

        :param doc:
        :type doc:
        :param task:
        :type task:
        :return:
        :rtype:
        """
        # If no DataGenerator references the task, no execution is necessary
        dgs = SEDMLCodeFactory.getDataGeneratorsForTask(doc, task)
        if len(dgs) == 0:
            return "# not part of any DataGenerator: {}".format(task.getId())

        # tasks contain other subtasks, which can contain subtasks. This
        # results in a tree of task dependencies where the
        # simple tasks are the node leaves. These tree has to be resolved to
        # generate code for more complex task dependencies.

        # resolve task tree (order & dependency of tasks) & generate code
        taskTree = SEDMLCodeFactory.createTaskTree(doc, rootTask=task)
        return SEDMLCodeFactory.taskTreeToPython(doc, tree=taskTree)

    class TaskNode(object):
        """ Tree implementation of task tree. """
        def __init__(self, task, depth):
            self.task = task
            self.depth = depth
            self.children = []
            self.parent = None

        def add_child(self, obj):
            obj.parent = self
            self.children.append(obj)

        def is_leaf(self):
            return len(self.children) == 0

        def __str__(self):
            lines = ["<[{}] {} ({})>".format(self.depth, self.task.getId(), self.task.getElementName())]
            for child in self.children:
                child_str = child.__str__()
                lines.extend(["\t{}".format(line) for line in child_str.split('\n')])
            return "\n".join(lines)

        def info(self):
            return "<[{}] {} ({})>".format(self.depth, self.task.getId(), self.task.getElementName())

        def __iter__(self):
            """ Depth-first iterator which yields TaskNodes."""
            yield self
            for child in self.children:
                for node in child:
                    yield node

    class Stack(object):
        """ Stack implementation for nodes."""
        def __init__(self):
            self.items = []

        def isEmpty(self):
            return self.items == []

        def push(self, item):
            self.items.append(item)

        def pop(self):
            return self.items.pop()

        def peek(self):
            return self.items[len(self.items)-1]

        def size(self):
            return len(self.items)

        def __str__(self):
            return "stack: " + str([item.info() for item in self.items])

    @staticmethod
    def createTaskTree(doc, rootTask):
        """ Creates the task tree.
        Required for resolution of order of all simulations.
        """
        def add_children(node):
            typeCode = node.task.getTypeCode()
            if typeCode == libsedml.SEDML_TASK:
                return  # no children
            elif typeCode == libsedml.SEDML_TASK_REPEATEDTASK:
                # add the ordered list of subtasks as children
                subtasks = SEDMLCodeFactory.getOrderedSubtasks(node.task)
                for st in subtasks:
                    # get real task for subtask
                    t = doc.getTask(st.getTask())
                    child = SEDMLCodeFactory.TaskNode(t, depth=node.depth+1)
                    node.add_child(child)
                    # recursive adding of children
                    add_children(child)
            else:
                raise IOError('Unsupported task type: {}'.format(node.task.getElementName()))

        # create root
        root = SEDMLCodeFactory.TaskNode(rootTask, depth=0)
        # recursive adding of children
        add_children(root)
        return root

    @staticmethod
    def getOrderedSubtasks(task):
        """ Ordered list of subtasks for task."""
        subtasks = task.getListOfSubTasks()
        subtaskOrder = [st.getOrder() for st in subtasks]
        # sort by order, if all subtasks have order (not required)
        if all(subtaskOrder) != None:
            subtasks = [st for (stOrder, st) in sorted(zip(subtaskOrder, subtasks))]
        return subtasks

    @staticmethod
    def taskTreeToPython(doc, tree):
        """ Python code generation from task tree. """
        # print(tree)

        # TODO: implement the merge of subtasks & and collection of simulations

        # go forward through task tree
        lines = []
        nodeStack = SEDMLCodeFactory.Stack()
        treeNodes = [n for n in tree]

        # iterate over the tree
        for kn, node in enumerate(treeNodes):
            # print("* [{}] <{} ({})>".format(node.depth, node.task.getId(), node.task.getElementName()))
            # print(nodeStack)
            taskType = node.task.getTypeCode()

            # Create information for task
            # We are going down in the tree
            if taskType == libsedml.SEDML_TASK_REPEATEDTASK:
                taskLines = SEDMLCodeFactory.repeatedTaskToPython(doc, node=node)

            elif taskType == libsedml.SEDML_TASK:
                tid = node.task.getId()
                taskLines = SEDMLCodeFactory.simpleTaskToPython(doc=doc, node=node)
            else:
                lines.append("# Unsupported task: {}".format(taskType))
                warnings.warn("Unsupported task: {}".format(taskType))

            lines.extend(["    "*node.depth + line for line in taskLines])

            '''
            @staticmethod
            def simpleTaskToPython(doc, task):
                """ Create python for simple task. """
                for ksub, subtask in enumerate(subtasks):
                    t = doc.getTask(subtask.getTask())

                    resultVariable = "__subtask__".format(t.getId())
                    selections = SEDMLCodeFactory.selectionsForTask(doc=doc, task=task)
                    if t.getTypeCode() == libsedml.SEDML_TASK:
                        forLines.extend(SEDMLCodeFactory.subtaskToPython(doc, task=t,
                                                                  selections=selections,
                                                                  resultVariable=resultVariable))
                        forLines.append("{}.extend([__subtask__])".format(task.getId()))

                    elif t.getTypeCode() == libsedml.SEDML_TASK_REPEATEDTASK:
                        forLines.extend(SEDMLCodeFactory.repeatedTaskToPython(doc, task=t))
                        forLines.append("{}.extend({})".format(task.getId(), t.getId()))
            '''

            # Collect information
            # We have to go back up
            # Look at next node in the treeNodes (this is the next one to write)
            if kn == (len(treeNodes)-1):
                nextNode = None
            else:
                nextNode = treeNodes[kn+1]

            # The next node is further up in the tree, or there is no next node
            # and still nodes on the stack
            if (nextNode is None) or (nextNode.depth < node.depth):
                # if (nextNode is None):
                #     print('last node')
                # else:
                #     print('nextNode higher')

                # necessary to pop nodes from the stack and close the code
                test = True
                while test is True:
                    # stack is empty
                    if nodeStack.size() == 0:
                        test = False
                        continue
                    # try to pop next one
                    peek = nodeStack.peek()
                    if (nextNode is None) or (peek.depth > nextNode.depth):
                        # TODO: reset evaluation has to be defined here
                        lines.extend([
                            "",
                            "    "*node.depth + "{}.extend({})".format(peek.task.getId(), node.task.getId()),
                        ])
                        node = nodeStack.pop()
                        # print("pop:", peek.info())

                    else:
                        test = False
            else:
                # we are going done or next subtask -> put node on stack
                nodeStack.push(node)
                # print("push:", node.info())

        return "\n".join(lines)

    @staticmethod
    def simpleTaskToPython(doc, node):
        """ Creates the simulation python code for a given taskNode.

        The taskNodes are required to handle the relationships between
        RepeatedTasks, SubTasks and SimpleTasks (Task).

        :param doc: sedml document
        :type doc: SEDDocument
        :param node: taskNode of the current task
        :type node: TaskNode
        :return:
        :rtype:
        """
        lines = []
        task = node.task
        lines.append("# Task: <{}>".format(task.getId()))
        lines.append("{} = [None]".format(task.getId()))

        mid = task.getModelReference()
        sid = task.getSimulationReference()
        simulation = doc.getSimulation(sid)

        simType = simulation.getTypeCode()
        algorithm = simulation.getAlgorithm()
        if algorithm is None:
            warnings.warn("Algorithm missing on simulation, defaulting to 'cvode: KISAO:0000019'")
            algorithm = simulation.createAlgorithm()
            algorithm.setKisaoID("KISAO:0000019")
        kisao = algorithm.getKisaoID()

        # is supported algorithm
        if not SEDMLCodeFactory.isSupportedAlgorithmForSimulationType(kisao=kisao, simType=simType):
            lines.append("# Unsupported Algorithm {} for SimulationType {}".format(kisao, simulation.getElementName()))
            return lines

        # set integrator/solver
        integratorName = SEDMLCodeFactory.getIntegratorNameForKisaoID(kisao)
        if not integratorName:
            warnings.warn("No integrator exists for {} in roadrunner".format(kisao))
            return lines

        if simType is libsedml.SEDML_SIMULATION_STEADYSTATE:
            lines.append("{}.setSteadyStateSolver('{}')".format(mid, integratorName))
        else:
            lines.append("{}.setIntegrator('{}')".format(mid, integratorName))

        if kisao == "KISAO:0000288":  # BDF
            lines.append("{}.integrator.setValue('{}', {})".format(mid, 'stiff', True))
        elif kisao == "KISAO:0000280":  # Adams-Moulton
            lines.append("{}.integrator.setValue('{}', {})".format(mid, 'stiff', False))

        # integrator/solver settings (AlgorithmParameters)
        for par in algorithm.getListOfAlgorithmParameters():
            pkey = SEDMLCodeFactory.algorithmParameterToParameterKey(par)
            if pkey.dtype is str:
                value = "'{}'".format(pkey.value)
            else:
                value = pkey.value

            if value == str('inf') or pkey.value == float('inf'):
                value = "float('inf')"
            else:
{'fraction_non_alphanumeric': 0.11733842500481355, 'fraction_numerical': 0.018612412553751363, 'mean_word_length': 2.3016369126450176, 'pattern_counts': {'":': 8, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6929739', 'n_tokens_mistral': 18277, 'n_tokens_neox': 16709, 'n_words': 4955}