"""
PySCeS - Python Simulator for Cellular Systems (http://pysces.sourceforge.net)

Copyright (C) 2004-2022 B.G. Olivier, J.M. Rohwer, J.-H.S Hofmeyr all rights reserved,

Brett G. Olivier (bgoli@users.sourceforge.net)
Triple-J Group for Molecular Cell Physiology
Stellenbosch University, South Africa.

Permission to use, modify, and distribute this software is given under the
terms of the PySceS (BSD style) license. See LICENSE.txt that came with
this distribution for specifics.

NO WARRANTY IS EXPRESSED OR IMPLIED.  USE AT YOUR OWN RISK.
Brett G. Olivier
"""
from __future__ import division, print_function
from __future__ import absolute_import
from __future__ import unicode_literals

from .version import __version__

import os, copy, re, time
from getpass import getuser
from xml.etree import ElementTree
import io
import itertools
import numpy

from .. import output_dir, __CHGDIR_ON_START__
if __CHGDIR_ON_START__:
    CWD = output_dir
else:
    CWD = os.getcwd()

from .InfixParser import MyInfixParser

InfixParser = MyInfixParser()
InfixParser.buildlexer()
InfixParser.buildparser(
    debug=0,
    debugfile='infix.dbg',
    tabmodule='infix_tabmodule',
    outputdir=output_dir,
)
InfixParser.setNameStr('self.', '()')

os.chdir(CWD)

class CoreToPsc(object):
    core = None
    name = ''
    header_block = ''
    fixed_block = ''
    compartment_block = ''
    function_block = ''
    reaction_block = ''
    species_block = ''
    parameter_block = ''
    rrule_block = ''
    arule_block = ''
    event_block = ''
    notes_block = ''
    SPECIES_CURRENT_VALUE = False
    FIXED_SPECIES_CURRENT_VALUE = False
    PARAMETER_CURRENT_VALUE = False
    time = None
    pw_symbols = None
    __DEBUG__ = False

    # future
    compartment_block = ''

    def __init__(self, core):
        self.core = core
        self.name = self.core.name.replace('.psc', '')
        self.name = self.core.name.replace('.xml', '')
        self.pw_symbols = [pw.name for pw in self.core.piecewise_functions]

    def setHeader(self, s=''):
        out = '# Generated by PySCeS %s (%s)\n' % (
            __version__,
            time.strftime("20%y-%m-%d %H:%M"),
        )
        if len(s) > 0:
            out += '\n# '
        ccount = 0
        for c in range(len(s)):
            ccount += 1
            if ccount >= 60 and ccount <= 75 and s[c] in [' ', '\t', '\n', '-']:
                out += s[c] + '\n# '
                ccount = 0
            else:
                out += s[c]
        out += ' \n# Keywords\n'
        kk = list(self.core.__KeyWords__.keys())
        kk.sort()
        for key in kk:
            if self.core.__KeyWords__[key] != None:
                if key == 'ModelType':
                    pass  # TODO
                    ##  out += '%s: ' % (key)
                    ##  for mt in range(len(self.core.__KeyWords__[key])):
                    ##  out += '%s,' % (self.core.__KeyWords__[key][mt])
                    ##  out = out[:-1] + '\n'
                else:
                    out += '%s: %s\n' % (key, self.core.__KeyWords__[key])
        out += ' \n# GlobalUnitDefinitions\n'
        for key in list(self.core.__uDict__.keys()):
            if key in ['substance', 'volume', 'time', 'length', 'area']:
                k1, k2 = key[0].upper(), key[1:]
                k = k1 + k2
                v = self.core.__uDict__[key]
                out += 'Unit%s: %s, %s, %s, %s\n' % (
                    k,
                    v['kind'],
                    v['multiplier'],
                    v['scale'],
                    v['exponent'],
                )
        out += ' \n'
        self.header_block = out

    def setFixed(self):
        out = ''
        fcnt = 0
        for f in self.core.species:
            if f.fixed:
                fcnt += 1
                out += '%s ' % f.name
        if fcnt > 0:
            out = 'FIX: ' + out
            out += '\n \n'
        self.fixed_block = out

    def setCompartments(self):
        out = ''
        if len(self.core.compartments) > 0:
            out += '# Compartments\n'
            for c in self.core.compartments:
                ra = ''
                if c.area != None:
                    ra = ', ' + c.area
                out += 'Compartment: %s, %s, %s %s\n' % (
                    c.name,
                    c.size,
                    c.dimensions,
                    ra,
                )
            out += ' \n'
        self.compartment_block = out

    def setFunctions(self):
        out = ''
        start = True
        for f in self.core.functions:
            if start:
                out = '# Function definitions\n'
                start = False
            out += 'Function: %s' % f.name
            for a in f.args:
                out += ', %s ' % a
            out += ' {\n%s\n}\n' % f.formula
        if out != '':
            out += ' \n'
        self.function_block = out

    def setReactions(self):
        out = '# Reactions'
        for r in self.core.reactions:
            C = ''
            if not r.hasCompartment() and len(self.core.compartments) == 1:
                print(
                    'Info: single compartment model: locating \"%s\" in default compartment'
                    % r.name
                )
                C = '@%s' % self.core.compartments[0].getName()
            elif not r.hasCompartment() and len(self.core.compartments) > 1:
                print(
                    'Info: multiple compartments defined but reaction %s is not located in any of them.'
                    % r.name
                )
            elif r.hasCompartment():
                C = '@%s' % r.getCompartment().getName()

            rout = '\n%s%s:\n    ' % (r.name, C)
            cnt = 0

            # we assume that multistoich_enabled will only be true if NewCore.netStoich is False
            ##  print r.hasSubstrates()
            ##  print r.hasProducts()

            if not r.multistoich_enabled:
                for lh in r.hasSubstrates():
                    ##  print '\nFuck'
                    ##  print lh
                    ##  print r.stoichiometry
                    if cnt != 0:
                        rout += ' + '
                    if abs(r.stoichiometry[lh]) == 1.0:
                        rout += '%s' % lh
                    else:
                        rout += '{%s}%s' % (abs(r.stoichiometry[lh]), lh)
                    cnt += 1
                if len(r.hasSubstrates()) == 0:
                    rout += '$pool'
                if r.reversible:
                    rout += ' = '
                else:
                    rout += ' > '
                cnt = 0

                for rh in r.hasProducts():
                    ##  print '\nFuck2'
                    ##  print rh
                    ##  print r.stoichiometry
                    if cnt != 0:
                        rout += ' + '
                    if abs(r.stoichiometry[rh]) == 1.0:
                        rout += '%s' % rh
                    else:
                        rout += '{%s}%s' % (abs(r.stoichiometry[rh]), rh)
                    cnt += 1
                if len(r.hasProducts()) == 0:
                    rout += '$pool'
            else:
                LHS = ''
                RHS = ''
                REVS = ''
                for lh in r.multistoich:
                    if lh[1] < 0.0:
                        if abs(lh[1]) == 1.0:
                            LHS += '%s' % lh[0]
                        else:
                            LHS += '{%s}%s' % ((lh[1]), lh[0])
                        LHS += ' + '
                    else:
                        if abs(lh[1]) == 1.0:
                            RHS += '%s' % lh[0]
                        else:
                            RHS += '{%s}%s' % (abs(lh[1]), lh[0])
                        RHS += ' + '

                if LHS == '':
                    LHS += '$pool'
                else:
                    LHS = LHS[:-3]
                if RHS == '':
                    RHS += '$pool'
                else:
                    RHS = RHS[:-3]
                if r.reversible:
                    REVS += ' = '
                else:
                    REVS += ' > '
                rout = '%s%s %s %s' % (rout, LHS, REVS, RHS)

            # if we have piecewise symbols test if they are in the formula
            # and replace them with the piecewise expression
            formula = r.formula
            if len(self.pw_symbols) > 0:
                pwreplace = 'piecewise('
                for pwd in self.pw_symbols:
                    if pwd in r.formula:
                        for pwc in self.core.__piecewises__[pwd]:
                            if pwc != 'other':
                                pwreplace += '%s,%s,' % (
                                    self.core.__piecewises__[pwd][pwc][0],
                                    self.core.__piecewises__[pwd][pwc][1],
                                )
                        if self.core.__piecewises__[pwd]['other'] != None:
                            pwreplace += '%s) ' % self.core.__piecewises__[pwd]['other']
                        else:
                            pwreplace = pwreplace[:-1] + ')'
                        formula = formula.replace(pwd, pwreplace)
            rout += '\n    %s\n' % formula
            if len(r.hasModifiers()) > 0:
                rout += '# %s has modifier(s): ' % r.name
                for m in r.hasModifiers():
                    rout += '%s ' % m
                rout += ' \n'
            out += rout
        out += ' \n'
        self.reaction_block = out

    def setSpecies(self):
        out = ''
        fixed = ''
        var = ''
        for f in self.core.species:
            C = ''
            if not f.hasCompartment() and len(self.core.compartments) == 1:
                print(
                    'Info: single compartment model %s is in default compartment'
                    % f.name
                )
                C = '@%s' % self.core.compartments[0].getName()
            elif not f.hasCompartment() and len(self.core.compartments) > 1:
                print(
                    'Warning: multiple compartments defined but species %s is not located in any of them.'
                    % f.name
                )
            elif f.hasCompartment():
                C = '@%s' % f.getCompartment().getName()

            if not f.fixed:
                if not self.SPECIES_CURRENT_VALUE:
                    value = f.value_initial
                else:
                    value = f()
                var += '%s%s = %s\n' % (f.name, C, value)
            else:
                if not self.FIXED_SPECIES_CURRENT_VALUE:
                    value = f.value_initial
                else:
                    value = f()
                fixed += '%s%s = %s\n' % (f.name, C, value)
        out += '# Fixed species\n'
        out += fixed
        out += ' \n'
        out += '# Variable species\n'
        out += var
        out += ' \n'
        self.species_block = out

    def setParameters(self):
        out = '# Parameters\n'
        processed = []

        def setP(p2, out2, proced):
            if not self.PARAMETER_CURRENT_VALUE:
                # take into account that parameters can be compartments now as well
                # brett 2008
                if hasattr(p2, 'value_initial') and p2.value_initial != None:
                    value = p2.value_initial
                else:
                    value = p2()
            else:
                value = p2()
            if p2.name in proced:
                # out2 += '# '
                pass
            else:
                out2 += '%s = %s' % (p2.name, value)
            if not hasattr(p2, 'code_string'):
                if p2.name in proced:
                    pass
                    # out2 += '\t# (initialised)'
                else:
                    out2 += '\n'
            else:
                if p2.name in proced:
                    pass
                    # out2 += '\t# (initialised)\n'
                else:
                    out2 += '\t# (rule)\n'
            return out2

        for r in self.core.reactions:
            ##  out += '# %s parameters\n' % r.name
            for p in r.parameters:
                if p.name not in self.core.hasCompartments():
                    out = setP(p, out, processed)
                    processed.append(p.name)
            ##  out += '\n'
        ##  if len(processed) != len(self.core.hasGlobalParameters()):
        ##  out += '# Additional (global) parameters\n'
        for p in self.core.hasGlobalParameters():
            if p not in processed:
                p = self.core.get(p)
                out = setP(p, out, processed)
                processed.append(p.name)
        out += ' \n'
        self.parameter_block = out

    def setAssignmentRules(self):
        out = ''
        start = True
        rids = [r.name for r in self.core.reactions]
        for p in self.core.global_parameters + self.core.species:
            if hasattr(p, 'type') and getattr(p, 'type') == 'assignment':
                if start:
                    out = '# Assignment rules\n'
                    start = False
                # add support for reaction names, symbol repalce R --> R()
                formula = p.formula
                for n_ in p._names:
                    if n_ in rids:
                        formula = formula.replace(n_, '{}()'.format(n_))

                # if we have piecewise symbols test if they are in the formula
                # and replace them with the piecewise expression
                if len(self.pw_symbols) > 0:
                    pwreplace = ' piecewise('
                    for pwd in self.pw_symbols:
                        if pwd in p.formula:
                            for pwc in self.core.__piecewises__[pwd]:
                                if pwc != 'other':
                                    pwreplace += '%s,%s,' % (
                                        self.core.__piecewises__[pwd][pwc][0],
                                        self.core.__piecewises__[pwd][pwc][1],
                                    )
                            if self.core.__piecewises__[pwd]['other'] != None:
                                pwreplace += (
                                    '%s) ' % self.core.__piecewises__[pwd]['other']
                                )
                            else:
                                pwreplace = pwreplace[:-1] + ')'
                            formula = formula.replace(pwd, pwreplace)
                out += '!F %s = %s\n' % (p.name, formula)
        if out != '':
            out += ' \n'
        self.arule_block = out

    def setRateRules(self):
        out = ''
        start = True
        if len(self.core.rate_rules) > 0:
            for rr in self.core.rate_rules:
                if start:
                    out = '# Rate rules\n'
                    start = False
                out += 'RateRule: %s {\n%s\n}\n' % (rr.name, rr.formula)
            out += '\n'
        self.rrule_block = out

    def setEvents(self):
        out = ''
        start = True
        for ev in self.core.events:
            if start:
                out = '# Event definitions\n'
                start = False
            ##  formula = ev.code_string.split('=',1)
            formula = ev.formula
            out += 'Event: %s, %s, %s \n{\n' % (ev.name, formula, ev.delay)
            for ass in ev.assignments:
                out += '%s = %s\n' % (ass.variable.name, ass.formula)
            out += '}\n'
        if out != '':
            out += ' \n'
        self.event_block = out

    def setNotes(self, s=''):
        if len(s) > 1:
            out = '# Notes\n# '
            out += '"""%s\n"""\n' % s
            ##  ccount = 0
            ##  for c in range(len(s)):
            ##  ccount += 1
            ##  if ccount >= 55 and ccount <=75 and s[c] in [' ', '\t', '\n', '-']:
            ##  out += s[c]+'\n# '
            ##  ccount = 0
            ##  else:
            ##  out += s[c]
            ##  out += ' \n'
            self.notes_block = out
        else:
            self.notes_block = '\n'

    def write(self, filename=None, directory=None, getstrbuf=False):
        if filename == None:
            filename = self.name + '.psc'
        if directory != None:
            assert os.path.exists(directory), '\n%s does not exist.' % directory
            filename = os.path.join(directory, filename)
        print('Writing file: %s' % filename)
        outF = open(filename, 'w')
        outF.write(self.header_block)
        outF.write(self.fixed_block)
        outF.write(self.compartment_block)
        outF.write(self.function_block)
        outF.write(self.reaction_block)
        outF.write(self.rrule_block)
        outF.write(self.arule_block)
        outF.write(self.event_block)
        outF.write(self.species_block)
        outF.write(self.parameter_block)
        outF.write(self.notes_block)
        outF.flush()
        outF.close()
        if getstrbuf:
            fb = io.StringIO()
            outF = open(filename, 'r')
            fb.write(outF.read())
            outF.flush()
            outF.close()
            return fb


class PscToCore(object):
    ModelDir = None
    WorkDir = None

    # old
    ModelFile = None
    _PysMod__InitStrings = None
    InitParams = None  # suspect
    fixed_species = None
    species = None
    parameters = None
    reactions = None
    modifiers = None
    ##  __function_forced_str__ = None
    _Function_time = None
    _Function_user = None
    _Function_init = None
    __nDict__ = None

    __functions__ = None
    __rules__ = None
    time = None
    __events__ = None
    __uDict__ = None

    def __init__(self):
        try:
            from pysces.PyscesParse import PySCeSParser

            self.pscParser = PySCeSParser(debug=0)
        except:
            print("\nYou need PySCeS installed to use this module")

    def getPSCFileFromDisk(self, ModelFile, ModelDir=None, WorkDir=None):
        """
        find and set up an existing psc file for parsing
        """

        if ModelFile[-4:] != '.psc':
            print("Assuming .psc extension")
            ModelFile += '.psc'
        if ModelDir == None:
            # this will probably change in future - bgoli
            ModelDir = CWD
        if WorkDir == None:
            WorkDir = CWD
        assert os.path.exists(
            os.path.join(ModelDir, ModelFile)
        ), '\nFile %s does not exist' % os.path.join(ModelDir, ModelFile)
        setattr(self, 'ModelFile', ModelFile)
        setattr(self, 'ModelDir', ModelDir)
        assert os.path.exists(WorkDir), '\nDirectory %s does not exist' % WorkDir
        setattr(self, 'WorkDir', WorkDir)

    def getPSCFileFromString(self, ModelString, WorkDir=None):
        """
        set up a temporary psc file for parsing from model string
        """
        if WorkDir == None:
            WorkDir = CWD
        assert os.path.exists(WorkDir), '\nDirectory %s does not exist' % WorkDir
        setattr(self, 'WorkDir', WorkDir)
        setattr(self, 'ModelDir', WorkDir)
        ModelFile = '%s.psc' % time.time()
        Mfile = open(os.path.join(WorkDir, ModelFile), 'w')
        Mfile.write(ModelString)
        Mfile.close()
        setattr(self, 'ModelFile', ModelFile)

    def getParsedModel(self):
        """
        Parse the input file associated with the PySCeS model instance and assign the basic model attributes
        """

        self.pscParser.ParsePSC(self.ModelFile, self.ModelDir, self.WorkDir)

        # from __nDict__
        self._PysMod__InitStrings = [
            s.replace('self.', '') for s in self.pscParser.InitStrings
        ]
        for s in self.pscParser.InitStrings:
            exec(s)
        self.InitParams = [s.replace('self.', '') for s in self.pscParser.InitParams]
        self.fixed_species = copy.copy(self.pscParser.fixed_species)
        self.species = copy.copy(self.pscParser.species)
        self.parameters = copy.copy(self.pscParser.parameters)
        self.reactions = copy.copy(self.pscParser.reactions)
        self.modifiers = copy.copy(self.pscParser.modifiers)
        self.__functions__ = copy.copy(self.pscParser.functions)

        # print self.modifiers
        self._Function_forced = self.pscParser.ForceFunc.replace('self.', '')
        self._Function_forced = self.pscParser.ForceFunc.replace('scipy.', '')
        self._Function_time = self.pscParser.TimeFunc.replace('self.', '')
        self._Function_user = self.pscParser.UserFunc.replace('self.', '')
        self._Function_init = self.pscParser.InitFunc.replace('self.', '')
        self.__nDict__ = self.pscParser.NetworkDict.copy()
        self.__sDict__ = self.pscParser.sDict.copy()
        self.__compartments__ = self.pscParser.Compartments.copy()
        self.__uDict__ = self.pscParser.uDict.copy()
        # new

    def getModel(self):
        return self


# TODO: PIECEWISE WRITING


class CoreToSBML(object):
    core = None
    name = None
    SBML = None
    level = 2
    version = 1
    model = None
    document = None
    time = None
    __events__ = None
    __DEBUG__ = False
    UNINIT_DEFAULT = 1e-3

    NumpyToMathML = {
        'numpy.greater_equal': 'geq',
        'numpy.greater': 'gt',
        'numpy.less_equal': 'leq',
        'numpy.less': 'lt',
        'numpy.not_equal': 'neq',
        'numpy.equal': 'eq',
        'numpy.arcsinh': 'arcsinh',
        'numpy.arccosh': 'arccosh',
        'numpy.arctanh': 'arctanh',
        'numpy.arcsin': 'arcsin',
        'numpy.arccos': 'arccos',
        'numpy.arctan': 'arctan',
        'numpy.sinh': 'sinh',
        'numpy.cosh': 'cosh',
        'numpy.tanh': 'tanh',
        'numpy.sin': 'sin',
        'numpy.cos': 'cos',
        'numpy.tan': 'tan',
        'math.log10': 'log',
        'math.log': 'ln',
        'numpy.floor': 'floor',
        'numpy.ceil': 'ceil',
        'numpy.sqrt': 'sqrt',
        'math.sqrt': 'sqrt',
        'math.exp': 'exp',
        'operator.eq': 'eq',
        'operator.ne': 'neq',
        'operator.gt': 'gt',
        'operator.ge': 'geq',
        'operator.lt': 'lt',
        'operator.le': 'leq',
        'operator.eq': 'equal',
        'operator.ne': 'neq',
        'self._piecewise_': 'piecewise',
        '_piecewise_': 'piecewise',
        'operator.not_': 'not',
        ' and ' : ' && ',
        ' or ' : ' || ',

    }

    NumpyToMathMLkeys = [
        'numpy.greater_equal',
        'numpy.greater',
        'numpy.less_equal',
        'numpy.less',
        'numpy.not_equal',
        'numpy.equal',
        'numpy.arcsinh',
        'numpy.arccosh',
        'numpy.arctanh',
        'numpy.arcsin',
        'numpy.arccos',
        'numpy.arctan',
        'numpy.sinh',
        'numpy.cosh',
        'numpy.tanh',
        'numpy.sin',
        'numpy.cos',
        'numpy.tan',
        'math.log10',
        'math.log',
        'numpy.floor',
        'numpy.ceil',
        'numpy.sqrt',
        'math.sqrt',
        'math.exp',
        'operator.eq',
        'operator.ne',
        'operator.gt',
        'operator.ge',
        'operator.lt',
        'operator.le',
        'operator.eq',
        'operator.ne',
        'self._piecewise_',
        '_piecewise_',
        'operator.not_',
        ' and ',
        ' or ',
    ]

    def __init__(self, core):
        self.core = core
        self.name = self.core.getName().replace('.psc', '').replace('.xml', '')
        try:
            import libsbml as SBML

            self.SBML = SBML
        except Exception as e:
            print(e)
            print('Posix sbml load error')
            self.SBML = None

    def createModel(self):
        """
        Create an SBML model and document uses the class attributes:

         - *self.level* [default=2] SBML level
         - *self.version* [default=4] SBML version

        and creates:

         - self.model and SBML model
         - self.document an SBML document

        """
        if self.SBML.getLibSBMLVersion() < 40000:
            self.model = self.SBML.Model()
            self.model.setName(self.name)
            self.document = self.SBML.SBMLDocument()
            self.document.setLevelAndVersion(self.level, self.version)
        else:
            self.model = self.SBML.Model(self.level, self.version)
            self.model.setName(self.name)
            self.document = self.SBML.SBMLDocument(self.level, self.version)

    def setCompartments(self):
        if len(self.core.compartments) < 1:
            print('Warning: no compartments defined adding one called \"Cell\"')
            self.core.addOneCompartment('Cell', 1.0, 3)
        for cs in self.core.compartments:
            comp_def = self.model.createCompartment()
            comp_def.setId(cs.name)
            comp_def.setName(cs.name)
            comp_def.setVolume(float(cs.size))
            # TODO need to adapt this for rate rules
            if self.level == 3:
                comp_def.setConstant(False)

    def setDescription(self, txt=None):
        '''
        Sets the model description as a <note> containing `txt` in an HTML paragraph on the model object.

         - *txt* [default=None] the text to insert as the description

        '''

        if txt == None:
            txt = self.core.getDescription()
        else:
            self.core.setDescription(txt)
        notes = ''
        notes = '<body xhtml="http://www.w3.org/1999/xhtml>'
        notes += (
            '<p><span style="font-family: Courier New,Courier,monospace;">%s</span></p>\n'
            % txt
        )
        notes += (
            '<p><span style="font-family: Courier New,Courier,monospace;"><a href="http://pysces.sourceforge.net">PySCeS</a> (%s) generated model (%s)</span></p>'
            % (__version__, self.core.getName())
        )
        notes += '</body>'
        self.model.setNotes(notes)

    # TODO: update this to handle composite units
    def setUnits(self):
        """
        Adds the unit definitions to the SBML model object

        """
        ud = self.core.getGlobalUnits()
        for un in list(ud.keys()):
            # for now just the basic stuff
            if un in ['substance', 'volume', 'time', 'length', 'area']:
                vdef = self.model.createUnitDefinition()
                vdef.setId(un)
                vdef.setName(un)
                vu = self.model.createUnit()
                vu.setKind(self.SBML.UnitKind_forName(ud[un]['kind']))
                vu.setMultiplier(ud[un]['multiplier'])
                vu.setScale(int(ud[un]['scale']))
                vu.setExponent(int(ud[un]['exponent']))
                vu.setOffset(0)

        # new super cool way of doing arbitrary unit definitions
        """
        ud = {'area': {0 : {'exponent': 2, 'kind': 'metre', 'multiplier': 1.0, 'scale': 0}},
                    'length': {0 : {'exponent': 1, 'kind': 'metre', 'multiplier': 1.0, 'scale': 0}},
                    'substance': {0 : {'exponent': 1, 'kind': 'mole', 'multiplier': 1.0, 'scale': 0}},
                    'time': {0 : {'exponent': 1, 'kind': 'second', 'multiplier': 1.0, 'scale': 0}},
                    'volume': {0 : {'exponent': 1, 'kind': 'litre', 'multiplier': 1.0, 'scale': 0}},
                    'mmol_per_gDW_per_hr': {0 : {'exponent': 1, 'kind': 'mole', 'multiplier': 1.0, 'scale': -3},
                                            1 : {'exponent': -1, 'kind': 'gram', 'multiplier': 1.0, 'scale': 0},
                                            2 : {'exponent': -1, 'kind': 'second', 'multiplier': 0.00027777, 'scale': 0}
                                          }
                    }

        for un in ud.keys():
            vdef = model.createUnitDefinition()
            vdef.setId(un)
            vdef.setName(un)
            for u in range(len(ud[un].keys())):
                vu = model.createUnit()
                vu.setKind(libsbml.UnitKind_forName(ud[un][u]['kind']))
                vu.setMultiplier(ud[un][u]['multiplier'])
                vu.setScale(int(ud[un][u]['scale']))
                vu.setExponent(int(ud[un][u]['exponent']))
                vu.setOffset(0)
        """

    def setSpecies(self):
        """
        Add the species definitions to the SBML model object:

        """
        for spe in self.core.species:
            s = self.model.createSpecies()
            s.setId(spe.name)
            s.setName(spe.name)
            if not spe.hasCompartment() and len(self.core.compartments) == 1:
                print(
                    'Warning: species %s does not have a compartment locating it in \"%s\"'
                    % (spe.getName(), self.core.compartments[0].getName())
                )
                spe.setCompartment(self.core.compartments[0])
            elif not spe.hasCompartment() and len(self.core.compartments) >= 1:
                raise UserWarning(
                    "%s does not have a compartment and there are more than 1 to choose from!"
                    % spe.getName()
                )
            s.setCompartment(spe.getCompartment().getName())
            if spe.name in self.core.hasFixedSpecies():
                s.setBoundaryCondition(True)
                s.setConstant(True)
            else:
                s.setBoundaryCondition(False)
            if spe() is None:
                print(
                    'Warning, species {} has not been initialised setting to {}'.format(spe.getName(), self.UNINIT_DEFAULT)
                )

                if spe.name in self.core.__InitDict__ and self.core.__InitDict__[spe.name] is None:
                    self.core.__InitDict__[spe.name] = self.UNINIT_DEFAULT

                spe.value = spe.value_initial = self.core.__InitDict__[spe.name]


            if spe.isAmount():
                s.setInitialAmount(spe())
                s.setHasOnlySubstanceUnits(True)
            else:
                s.setInitialConcentration(spe())
                s.setHasOnlySubstanceUnits(False)

    def setParameters(self):
        for par in self.core.global_parameters:
            p = self.model.createParameter()
            p.setId(par.name)
            p.setName(par.name)

            # check for None value with no value and assign a default
            #print('PAR', par.name, par())
            # TODO: sort out this ZeroDivision and parameter initialisation error in a more sophisticated way, for example, evaluation assignment rules on export.
            try:
                if par() is None:
                    print(
                        'INFO: parameter \"{}\" not been defined setting to {}'.format(par.getName(), self.UNINIT_DEFAULT)
                    )
                    if par.name in self.core.__InitDict__ and self.core.__InitDict__[par.name] is None:
                        self.core.__InitDict__[par.name] = self.UNINIT_DEFAULT
                    par.value = par.value_initial = self.core.__InitDict__[par.name]
                # check for uninitialise parameters that have a value
                elif par.name in self.core.__InitDict__:
                    print(
                        'INFO: parameter \"{}\" not initialised setting to ({})'.format(par.name, par())
                    )
                    self.core.__InitDict__[par.name] = par()
                    par.value = par.value_initial = self.core.__InitDict__[par.name]
            except ZeroDivisionError:
                print('INFO: parameter \"{}\"initialisation error setting to 0.0'.format(par.name))
                par.value = par.value_initial = self.core.__InitDict__[par.name] = 0.0



            # first attempt, check for a formula ... could be done with introspection
            if hasattr(par, 'formula'):
                p.setConstant(False)
                r = self.model.createAssignmentRule()
                r.setVariable(par.name)
                formula = (
                    par.code_string.split('=')[1].replace('self.', '').replace('()', '')
                )
                #print('LOOKATME PARSE ASSIGNMENT RULE line 861')
                #print(formula)
                formula = self.infixPSC2SBML(formula)
                #print(formula)
                sbml_ast = self.SBML.parseL3Formula(formula)
                assert sbml_ast is not None, '\nERROR: cannot interpret assignment rule ({}): {}'.format(par.name, formula)
                #print(self.SBML.formulaToL3String(sbml_ast))

                if r.setMath(sbml_ast) < 0:
                    print('\nERROR: cannot assign assignment rule ({}): {}'.format(par.name, formula))
                    raise(RuntimeError)

                #r.setFormula(par.formula)
                #print(self.core.__InitDict__)
                #print(par.name)
                #print(self.core.__InitDict__[par.name])
                p.setValue(self.core.__InitDict__[par.name])
            else:
                p.setValue(par())

            # TODO need to adapt this for rate rules
            if self.level == 3:
                p.setConstant(False)


    def infixPSC2SBML(self, infix):
        """replace NumPy infix with libSBMl infix"""

        # symbol replacement parse (sometimes simple is better)
        infix2 = str(infix)
        for f in self.NumpyToMathMLkeys:
            infix2 = infix2.replace(f, self.NumpyToMathML[f])
        return infix2

    def astSetCSymbolTime(self, ast):
        """set ASTNode type <cn> _TIME_ </cn> to <csymbol> time </csymbol>"""
        strBuf = io.StringIO()
        mathMLin = self.SBML.writeMathMLToString(ast)
        strBuf.write(mathMLin)
        strBuf.seek(0)
        etree = ElementTree.parse(strBuf)
        root = etree.getroot()
        counter = itertools.count(1)

        def idxNode(node, idx=0):
            if node.text != None and node.text.strip() == '_TIME_':
                # <csymbol encoding="text" definitionURL="http://www.sbml.org/sbml/symbols/time"> t </csymbol>
                node.text = node.text.replace('_TIME_', 'time')
                node.tag = "{http://www.sbml.org/sbml/symbols/time}csymbol"
                node.attrib.update({'encoding': 'text'})
                node.attrib.update(
                    {'definitionURL': 'http://www.sbml.org/sbml/symbols/time'}
                )

            children = node.getchildren()
            for child in range(len(children)):
                idxNode(children[child], next(counter))

        idxNode(root, idx=0)

        strBuf = io.StringIO()
        etree.write(strBuf)
        strBuf.seek(0)
        mathMLout = strBuf.read()
        return self.SBML.readMathMLFromString(mathMLout)

    def setRules(self):
        """Set rate rules"""
        for rule in self.core.rate_rules:
            RR = self.model.createRateRule()
            RR.setVariable(rule.getName())
            # replace PySCeS infix with libSBML infix
            form = rule.code_string.split('=')[1].replace('self.', '').replace('()', '')
            form = self.infixPSC2SBML(form)
            req_replacements = {}
            for symb in rule._names:
                if symb in self.core.hasReactions():
                    req_replacements.update(
                        {
                            symb: '(%s)'
                            % self.infixPSC2SBML(
                                self.core.get(symb)
                                .code_string.split('=')[1]
                                .replace('self.', '')
                                .replace('()', '')
                            )
                        }
                    )
            if len(list(req_replacements.keys())) > 0:
                InfixParser.setNameStr('', '')
                InfixParser.SymbolReplacements = req_replacements
                InfixParser.parse(form)
                form = InfixParser.output
            ASTnode = self.SBML.parseFormula(form)
            assert ASTnode != None, "ERROR: unable to parse formula (%s) to AST" % form
            if self.__DEBUG__:
                print('Adding RateRule: %s = %s' % (rule.getName(), form))
            # set _TIME_ ASTnode tag to <csymbol> time </csymbol>
            ASTnode = self.astSetCSymbolTime(ASTnode)
            RR.setMath(ASTnode)
            # after creating the rule set the parameter to be non constant
            rpar = self.model.getParameter(rule.getName())
            if rpar == None:
                rpar = self.model.getCompartment(rule.getName())
            if rpar == None:
                rpar = self.model.getSpecies(rule.getName())
            if rpar != None:
                rpar.setConstant(False)

    def setFunctions(self):
        for func in self.core.functions:
            #print(func)
            FNC = self.model.createFunctionDefinition()
            FNC.setName(func.getName())
            FNC.setId(func.getName())
            form = func.code_string.split('=')[1].replace('self.', '').replace('()', '')
            #print(form)
            form = self.infixPSC2SBML(form)
            #print(form)
            ASTnode = self.SBML.parseL3Formula(form)

            assert ASTnode is not None, "ERROR: unable to parse function (%s) to AST" % form
            assert FNC.setMath(ASTnode) == 0, "ERROR: unable to set Function math."



    def setEvents(self):
        """Set events"""
        for ev in self.core.events:
            if self.__DEBUG__:
                print('Adding event: %s' % ev.getName())
            EV = self.model.createEvent()
            EV.setName(ev.getName())
            EV.setId(ev.getName())

            #print(ev.getName())
            #print(ev.formula)
            #print(ev.code_string)
            # replace PySCeS infix with libSBML infix
            #print('LOOKATME PARSE EVENT line 979')
            form = ev.code_string.replace('self.state=', '').replace('self.', '').replace('()', '')
            #print(form)
            form = self.infixPSC2SBML(form)
            #print(form)
            if self.__DEBUG__:
                print('\tTrigger: %s' % form)
            ASTnode = self.SBML.parseL3Formula(form)
            assert ASTnode != None, "ERROR: unable to parse formula (%s) to AST" % form
            #print(self.SBML.formulaToL3String(ASTnode))
            ## set _TIME_ ASTnode tag to <csymbol> time
            #ASTnode = self.astSetCSymbolTime(ASTnode)

            tr = self.SBML.Trigger(self.level, self.version)
            tr.setMath(ASTnode)
            EV.setTrigger(tr)
            ea_cntr = 0
            for ass in ev.assignments:
                #print('LOOKATME PARSE EVENTASSIGNMENT line 1003')
                if self.__DEBUG__:
                    print('\tAssignment: %s = %s' % (ass.getName(), ass.formula))
                #print(ass.getName())
                #print(ass.formula)
                #print(ass.variable.getName())
                form = self.infixPSC2SBML(ass.formula)
                #print(form)
                ASTnode = self.SBML.parseL3Formula(form)

                # TODO in future we need to move all id's from SBML to CORE including triggers and event assignments
                # this will affect this code

                eass = self.SBML.EventAssignment(self.level, self.version)
                eass.setId('eas_{}_{}'.format(ev.getName(), ea_cntr))
                ea_cntr += 1
                eass.setVariable(ass.variable.getName())
                assert eass.setMath(ASTnode) == 0, 'ERROR: EventAssignment math set error.'
                EV.addEventAssignment(eass)
            if ev.delay != 0:
                dform = self.infixPSC2SBML(ev.delay)
                ASTnodeD = self.SBML.parseFormula(dform)
                ASTnodeD = self.astSetCSymbolTime(ASTnodeD)
                D = self.SBML.Delay(ASTnodeD)
                EV.setDelay(D)

    def setReactions(self):
        for rxn in self.core.reactions:
            # print 'Adding reaction:', rxn.name
            SBML_R = self.model.createReaction()
            SBML_R.setId(rxn.name)
            SBML_R.setName(rxn.name)
            for s in rxn.substrates:
                ##  print '\t' + rxn.name +' has substrate: ' + s.name + ' (%s)' % abs(rxn.stoichiometry[s.name])
                if self.SBML.getLibSBMLVersion() < 40000:
                    sref = self.SBML.SpeciesReference(
                        s.name, abs(rxn.stoichiometry[s.name])
                    )
                else:
                    sref = self.SBML.SpeciesReference(self.level, self.version)
                    sref.setStoichiometry(abs(rxn.stoichiometry[s.name]))
                    sref.setSpecies(s.name)
                # TODO: check if this needs to be refined
                if self.level == 3:
                    sref.setConstant(True)
                SBML_R.addReactant(sref)
            for p in rxn.products:
                ##  print '\t' + rxn.name +' has product: ' + p.name + ' (%s)' % abs(rxn.stoichiometry[p.name])
                if self.SBML.getLibSBMLVersion() < 40000:
                    pref = self.SBML.SpeciesReference(
                        p.name, abs(rxn.stoichiometry[p.name])
                    )
                else:
                    pref = self.SBML.SpeciesReference(self.level, self.version)
                    pref.setStoichiometry(abs(rxn.stoichiometry[p.name]))
                    pref.setSpecies(p.name)

                # TODO: check if this needs to be refined
                if self.level == 3:
                    pref.setConstant(True)
                # print(pref.toSBML())
                SBML_R.addProduct(pref)
            if not rxn.multistoich_enabled:
                for m in rxn.modifiers:
                    self.model.createModifier().setSpecies(m.name)
            else:
                AllReagents = [r[0] for r in rxn.multistoich]
                for m in rxn.modifiers:
                    if m.name not in AllReagents:
                        # print '\t' + rxn.name +' has modifier: ' + m.name
                        self.model.createModifier().setSpecies(m.name)
            formula = (
                rxn.code_string.split('=')[1].replace('self.', '').replace('()', '')
            )
            formula = self.infixPSC2SBML(formula)
            if self.SBML.getLibSBMLVersion() < 40000:
                sb_kl = self.SBML.KineticLaw(formula)
            else:
                sb_kl = self.SBML.KineticLaw(self.level, self.version)
                sb_kl.setFormula(formula)
            SBML_R.setKineticLaw(sb_kl)

            if rxn.reversible:
                SBML_R.setReversible(True)
            else:
                SBML_R.setReversible(False)

            # TODO: check if this needs to be refined
            if self.level == 3:
                SBML_R.setFast(False)
            # print(SBML_R.toSBML())

    def getSBMLmodel(self):
        return self.model

    def getSBMLdocument(self):
        self.document.setModel(self.model)
        return self.document

    def getSBML(self):
        return self.getSBMLdocument().toSBML()

    def getSBMLFileAsStrBuf(self):
        try:
            UseR = getuser()
        except:
            UseR = ''
        fb = io.StringIO()
        h1 = '<?xml version="1.0" encoding="utf-8"?>\n'
        h1 += (
            '<!-- Created with PySCeS ('
            + __version__
            + ') on '
            + time.strftime("%a, %d %b %Y %H:%M:%S")
            + ' by '
            + UseR
            + ' -->\n'
        )
        fb.write(h1 + self.getSBML())
        return fb

    def writeSBML2file(self, filename=None, directory=None):
        """
        Writes the SBML model to file:

         - *filename* [default=None] the output filename, default is <model_name>.xml
         - *directory* [default=None] by default use filename otherwise join, <dir><filename>

        """
        if filename == None:
            filename = self.name + '.xml'
        if directory != None:
            assert os.path.exists(directory), '\n%s does not exist.' % directory
            filename = os.path.join(directory, filename)
        print('Writing file: %s' % filename)

        try:
            UseR = getuser()
        except:
            UseR = ''
        h1 = '<?xml version="1.0" encoding="utf-8"?>\n'
        h1 += (
            '<!-- Created with PySCeS ('
            + __version__
            + ') on '
            + time.strftime("%a, %d %b %Y %H:%M:%S")
            + ' by '
            + UseR
            + ' -->\n'
        )
        F = open(filename, 'w')
        F.write(h1 + self.getSBML())
        F.flush()
        F.close()
        print('Model %s exported as: %s' % (self.name, filename))


class SbmlToCore(object):
    SBML = None
    level = 2
    version = 5
    sbml_string = None
    sbml_file = None
    model = None
    document = None
    core = None
    # old
    ModelFile = None
    InitParams = None
    fixed_species = None
    species = None
    parameters = None
    reactions = None
    modifiers = None
    compartments = None
    _Function_time = None
    _Function_user = None
    _Function_init = None
    __nDict__ = None
    __functions__ = None
    __rules__ = None
    time = None
    __eDict__ = None
    notes = None
    IS_SINGLE_COMPARTMENT = False
    SPECIES_IN_AMOUNTS = False
    __reserved__ = None
    __sDict__ = None
    __InitDict__ = None
    __KeyWords__ = None
    __Errors__ = None
    __piecewises__ = None
    __error_sleep_time__ = 1
    _ecount = None
    __uDict__ = None
    __DEBUG__ = False
    __model_in_level__ = None
    __model_in_version__ = None
    COMP_FUDGE_FACTOR = 1.0

    def __init__(self):
        try:
            import libsbml as SBML

            self.SBML = SBML
        except Exception as e:
            print(e)
            print('SBML load error')
            self.SBML = None
        assert self.SBML != None, '\nNo SBML library available'

        self.__reserved__ = {'lambda': 'Lambda'}
        self.__KeyWords__ = {
            'Description': 'ModelDescription',
            'Modelname': 'PySCeSModel',
            'Species_In_Conc': True,
            'Output_In_Conc': True,
        }
        self.__piecewises__ = {}

    def sbmlFormulaToInfix(self, formula):
        """
        Convert and SBML MathML formula to infix. Returns an infix string representation of a formula

        - **formula** SBML AST math

        """
        infix = self.SBML.formulaToL3String(formula)
        infix = infix.replace('^', '**').replace('&&', 'and').replace('||','or')
        return infix


    def searchForCsymbolTime(self, sbml_math):
        """
        Takes SBML math xxx.getMath() and returns the results of a regex multi search for the CSymbol time
        r'<csymbol encoding="text" definitionURL="http://www.sbml.org/sbml/symbols/time">.*<'

        - **sbml_math** SBML math generated with math_element.getMath()

        """

        # check for csymbol time
        csymb = r'<csymbol encoding="text" definitionURL="http://www.sbml.org/sbml/symbols/time">.*<'
        hasTimeS = re.search(
            csymb, self.SBML.writeMathMLToString(sbml_math)
        )

        return hasTimeS


    def setReservedTerm(self, term, replacement):
        self.__reserved__.update({term: replacement})

    def getSbmlStringFromDisk(self, sbml, Dir=None):
        if sbml[-4:] != '.xml':
            print("Assuming .xml extension")
            sbml += '.xml'
        if Dir is None:
            Dir = CWD
        assert os.path.exists(
            os.path.join(Dir, sbml)
        ), '\nFile %s does not exist' % os.path.join(Dir, sbml)
        self.sbml_file = os.path.join(Dir, sbml)
        sbmlF = open(self.sbml_file, 'r')
        self.sbml_string = sbmlF.read()
        sbmlF.close()

    def getSbmlStringFromString(self, sbml_string):
        self.sbml_string = sbml_string
        self.sbml_file = os.path.join(CWD, 'sbml_string_loader.xml')

    def getSbmlModel(self, document=None):
        r = self.SBML.SBMLReader()
        if document is None:
            self.document = r.readSBMLFromString(self.sbml_string)
        else:
            self.document = document
            self.sbml_string = self.document.toSBML()
            self.sbml_file = os.path.join(CWD, 'sbml_string_loader.xml')
        self.model = self.document.getModel()
        self.__model_in_level__ = self.model.getLevel()
        self.__model_in_version__ = self.model.getVersion()
        if self.model.getId() not in ['untitled','']:
            self.__KeyWords__['Modelname'] = self.model.getId()
        else:
            self.__KeyWords__['Modelname'] = self.model.getName()
        self.__KeyWords__['Description'] = self.model.getName()

    def checkSbmlSupport(self, action=2):
        """
        Check level and warn about unsupported functionality, errors logged to sbml_conversion_errors.txt

        - **action** [default=2] action to take: 1, raise error, 2 print warning with delay, 3 silently ignore

        """
        print('Check SBML support is at action level {}'.format(2))
        print('SBML file is L{}V{}'.format(self.__model_in_level__, self.__model_in_version__))
        logfile = '{}-sbml_conversion_errors.txt'.format(os.path.split(self.sbml_file)[-1])
        warn = []
        fatal = []

        if self.model.getLevel() >  2:
            warn.append('Model is encoded as SBML Level 3, PySCeS only officially supports L2V5.')
        if self.model.getNumConstraints() > 0:
            fatal.append('PySCeS does not support Constraints.')
        if self.model.getNumInitialAssignments() > 0:
            fatal.append('PySCeS does not support InitialAssignments.')
        if self.model.getNumEvents() > 0 and self.model.getLevel() >  2:
            fatal.append('PySCeS does not support L3 events.')
        if len([r.getType() for r in  self.model.getListOfRules() if r.getType() > 1]) > 0:
            fatal.append('PySCeS only supports rate and assignment rules.')


        if len(warn) == 0 and len(fatal) == 0:
            return
        else:
            print('\n\n\n*********ERRORS***********\n')
            with open(logfile, 'w') as F:
                for err in warn:
                    print('WARNING: {}'.format(err))
                    F.write('WARNING: {}\n'.format(err))
                for err in fatal:
                    print('FATAL: {}'.format(err))
                    F.write('FATAL: {}\n'.format(err))
            print('\n*********ERRORS***********\n')
            print('\nPossible errors detected in SBML conversion, Model may be incomplete. Please check the error log file \"{}\" for details.\n'.format(logfile))
            if action == 1:
                raise RuntimeError
            elif action == 2:
                time.sleep(5)
            else:
                time.sleep(0.1)


    def getUnits(self):
        self.__uDict__ = {
            'substance': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'mole'},
            'volume': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'litre'},
            'time': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'second'},
            'length': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'},
            'area': {'exponent': 2, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'},
        }
        for ud in self.model.getListOfUnitDefinitions():
            self.__uDict__.update({ud.getId(): {}})
            if ud.getNumUnits() == 1:
                u = ud.getListOfUnits()[0]
                self.__uDict__[ud.getId()].update(
                    {
                        'multiplier': u.getMultiplier(),
                        'exponent': u.getExponent(),
                        'scale': u.getScale(),
                        'kind': self.SBML.UnitKind_toString(u.getKind()),
                    }
                )
            else:
                for u in ud.getListOfUnits():
                    self.__uDict__[ud.getId()].update(
                        {
                            self.SBML.UnitKind_toString(u.getKind()): {
                                'multiplier': u.getMultiplier(),
                                'exponent': u.getExponent(),
                                'scale': u.getScale(),
                                'kind': self.SBML.UnitKind_toString(u.getKind()),
                            }
                        }
                    )

    def getEvents(self):
        self.__eDict__ = {}
        for ev in self.model.getListOfEvents():
            name = self.getId(ev)
            trigger = ev.getTrigger()
            triggerf = self.sbmlFormulaToInfix(trigger.getMath())

            # check for csymbol time
            hasTimeS = self.searchForCsymbolTime(trigger.getMath())
            tSymb = None
            if hasTimeS != None:
                tSymb = hasTimeS.group()[hasTimeS.group().find('>') :]
                tSymb = tSymb.replace('>', '').replace('<', '').strip()
                print(
                    'csymbol time defined as \"%s\" in event %s'
                    % (tSymb, trigger.getId())
                )

            delay = ev.getDelay()
            if delay != None:
                delay = self.sbmlFormulaToInfix(ev.getDelay().getMath())
            else:
                delay = 0.0

            self.__eDict__.update(
                {
                    name: {
                        'name': name,
                        'trigger': triggerf,
                        'delay': delay,
                        'assignments': {},
                        'tsymb': tSymb,
                    }
                }
            )
            for a in ev.getListOfEventAssignments():
                self.__eDict__[name]['assignments'].update(
                    {a.getVariable(): self.sbmlFormulaToInfix(a.getMath())}
                )

    def checkParsedInfix(self):
        assert len(InfixParser.SymbolErrors) == 0, (
            '\nUndefined symbols:\n%s' % InfixParser.SymbolErrors
        )
        assert InfixParser.LexOK, '\nLexer Failure:\n%s' % InfixParser.LexErrors
        assert InfixParser.ParseOK, '\nParser Failure:\n%s' % InfixParser.ParseErrors

    def getId(self, e):
        name = e.getId()
        if name in self.__reserved__:
            print(
                '%s is a reserved symbol, replacing with %s'
                % (name, self.__reserved__[name])
            )
            self.__Errors__.update(
                {
                    next(self._ecount): 'Reserved symbol %s replaced with %s'
                    % (name, self.__reserved__[name])
                }
            )
            name = self.__reserved__[name]
        return name

    def updatePiecewiseDict(self, piecewises):
        if len(list(piecewises.keys())) > 0:
            for p in piecewises:
                if len(list(piecewises[p].keys())) == 2:
                    piecewises[p][0].reverse()
                self.__piecewises__.update({p: piecewises[p]})

    def getParsedModel(self):
        init_fixed = {}
        init_var = {}
        init_par = {}
        self.__compartments__ = {}
        # new stuff
        self.__functions__ = {}
        self.SPECIES_IN_AMOUNTS = False
        self.__sDict__ = {}
        self.__InitDict__ = {}
        self.__Errors__ = {}
        self._ecount = itertools.count()

        # Initialise compartment volumes as a parameter - brett 20050908
        for comp in self.model.getListOfCompartments():
            cont = comp.getOutside()
            if cont == '':
                cont = None
            cSize = float(comp.getSize())
            self.__compartments__.update(
                {
                    self.getId(comp): {
                        'name': self.getId(comp),
                        ##  'volume': float(comp.getVolume()),
                        'size': cSize,
                        'dimensions': int(comp.getSpatialDimensions()),
                        'compartment': cont,
                        'area': None,
                        'fullName': comp.getName(),
                    }
                }
            )
        self.COMP_FUDGE_FACTOR = 1.0
        startFudging = 1.0e-6
        I_AM_FUDGING = False
        tmp = min(
            [
                abs(self.__compartments__[c]['size'])
                for c in list(self.__compartments__.keys())
            ]
        )
        if tmp < startFudging:
            self.COMP_FUDGE_FACTOR = tmp  # sneaky b%*))_)%$^&*@rds

        for c in list(self.__compartments__.keys()):
            if self.COMP_FUDGE_FACTOR < startFudging:
                newsize = self.__compartments__[c]['size'] / self.COMP_FUDGE_FACTOR
                print(
                    'INFO: Rescaling compartment with size %s to %s'
                    % (self.__compartments__[c]['size'], newsize)
                )
                self.__compartments__[c]['size'] = newsize
                self.__compartments__[c].update({'scale': self.COMP_FUDGE_FACTOR})
                I_AM_FUDGING = True

        if len(self.__compartments__) == 1:
            self.IS_SINGLE_COMPARTMENT = True
        else:
            self.IS_SINGLE_COMPARTMENT = False

        if len(self.model.getListOfSpecies()) < 1:
            self.__Errors__.update(
                {next(self._ecount): 'No free species!? ... help I\'m confused!'}
            )
        for i in self.model.getListOfSpecies():
            specname = self.getId(i)
            if i.getHasOnlySubstanceUnits():
                if not self.SPECIES_IN_AMOUNTS:
                    self.SPECIES_IN_AMOUNTS = True
        if self.SPECIES_IN_AMOUNTS:
            self.__KeyWords__['Species_In_Conc'] = False
        else:
            self.__KeyWords__['Species_In_Conc'] = True
            self.__KeyWords__['Output_In_Conc'] = True

        for i in self.model.getListOfSpecies():
            specname = self.getId(i)
            if self.__DEBUG__:
                print(
                    '%s has only substance units: %s'
                    % (specname, i.getHasOnlySubstanceUnits())
                )
            IS_CONC = i.isSetInitialConcentration()
            IS_AMNT = i.isSetInitialAmount()
            Conc = float(i.getInitialConcentration())
            Amount = float(i.getInitialAmount())
            if self.SPECIES_IN_AMOUNTS or IS_AMNT:
                if I_AM_FUDGING:
                    Conc = Amount / self.COMP_FUDGE_FACTOR
                else:
                    Conc = Amount
            fxd = None
            if i.getBoundaryCondition() or i.getConstant():
                if i.getConstant() and not i.getBoundaryCondition():
                    print(
                        '%s is set as constant, assuming: BoundaryCondition = True'
                        % specname
                    )
                init_fixed.setdefault(specname, Conc)
                fxd = True
            else:
                init_var.setdefault(specname, Conc)
                fxd = False

            self.__sDict__.update(
                {
                    specname: {
                        'name': specname,
                        'initial': Conc,
                        'compartment': i.getCompartment(),
                        'fixed': fxd,
                        'isamount': i.getHasOnlySubstanceUnits(),
                        'fullName': i.getName(),
                    }
                }
            )

        reactions = self.model.getListOfReactions()

        NetworkDict = dict(
            [
                (
                    i,
                    dict.fromkeys(
                        [
                            'Params',
                            'RateEq',
                            'Reagents',
                            'AllReagents',
                            'Modifiers',
                            'Type',
                        ]
                    ),
                )
                for i in [self.getId(j) for j in reactions]
            ]
        )

        # Add global parameters
        Punits = []

        if len(self.model.getListOfParameters()) > 0:
            for xp in self.model.getListOfParameters():
                if xp.isSetUnits():
                    Punits.append(self.getId(xp))
                init_par.update({self.getId(xp): float(xp.getValue())})
                # TODO: this needs to be overwritten when initial assignments are introduced
                if numpy.isnan(xp.getValue()):
                    print('ERROR: NaN detected as parameter value: {} setting to default value: 0.0'.format(self.getId(xp)))
                    #print(self.getId(xp), xp.getValue(), type(xp.getValue()))
                    init_par[self.getId(xp)] = 0.0
                    time.sleep(1)

        if len(Punits) > 0:
            self.__Errors__.update(
                {
                    next(self._ecount): 'Parameter units ignored for parameters:\n%s'
                    % Punits
                }
            )

        # add any function definitions
        if self.model.getNumFunctionDefinitions() > 0:
            for fnc in self.model.getListOfFunctionDefinitions():
                name = self.getId(fnc)

                func = self.sbmlFormulaToInfix(fnc.getMath())
                if 'lambda' in func:
                    func = func.replace('lambda', '')[1:-1]

                args = []
                for _ in range(fnc.getNumArguments()):
                    arg = func[: func.find(',')]
                    func = func[func.find(',') + 1 :]
                    args.append(arg)
                args = [s.strip() for s in args]
                func = func.strip()

                # check for csymbol time
                hasTimeS = self.searchForCsymbolTime(fnc.getMath())
                tSymb = None
                if hasTimeS != None:
                    tSymb = hasTimeS.group()[hasTimeS.group().find('>') :]
                    tSymb = tSymb.replace('>', '').replace('<', '').strip()
                    print(
                        'csymbol time defined as \"%s\" in formula %s'
                        % (tSymb, self.getId(fnc))
                    )
                    SRs = {tSymb: '_TIME_'}
                    SRs.update(self.__reserved__)
                    InfixParser.SymbolReplacements = SRs
                InfixParser.setNameStr('', '')
                InfixParser.parse(func)
                p_names = InfixParser.names
                func = InfixParser.output
                self.checkParsedInfix()
                # update piecewise dict ... don't ask!
                self.updatePiecewiseDict(InfixParser.piecewises)

                self.__functions__.update(
                    {name: {'args': args, 'formula': func, 'name': name}}
                )

        # ======= per reaction stuff ========
        hasFast = []
        Punits = []
        delayignore = []
        for i in reactions:
            rDict = NetworkDict[self.getId(i)]
            rDict.update({'name': self.getId(i)})
            rDict.update({'fullName': i.getName()})
            j = i.getKineticLaw()
            # ignore fast and warn
            if i.isSetFast():
                hasFast.append(self.getId(i))

            par = []
            if j != None:
                #req = j.getFormula()
                req = self.sbmlFormulaToInfix(j.getMath())
                p_names = None

                # check for csymbol time
                hasTimeS = self.searchForCsymbolTime(j.getMath())

                tSymb = None
                if hasTimeS != None:
                    tSymb = hasTimeS.group()[hasTimeS.group().find('>') :]
                    tSymb = tSymb.replace('>', '').replace('<', '').strip()
                    print(
                        'csymbol time defined as \"%s\" in reaction %s'
                        % (tSymb, self.getId(i))
                    )
                # if there are local parameters hash them to R_P
                if len(j.getListOfParameters()) > 0:
                    InfixParser.setNameStr('@', '@')
                    if hasTimeS != None:
                        SRs = {tSymb: '_TIME_'}
                        SRs.update(self.__reserved__)
                        InfixParser.SymbolReplacements = SRs
                    else:
                        InfixParser.SymbolReplacements = self.__reserved__
                    InfixParser.parse(req)
                    p_names = InfixParser.names
                    # catch delay and friends (ie check for shitty rate equations)
                    self.checkParsedInfix()
                    req = InfixParser.output
                    if InfixParser.DelayRemoved:
                        delayignore.append(self.getId(i))
                    if self.__DEBUG__:
                        print('Setting local parameter:')
                    for k in j.getListOfParameters():
                        if k.isSetUnits() and self.getId(k) not in Punits:
                            Punits.append(self.getId(k))
                        # TODO: replace with a regular expression based strategy
                        req = req.replace(
                            '@' + self.getId(k) + '@',
                            self.getId(i) + '_' + self.getId(k),
                        )
                        par.append(self.getId(i) + '_' + self.getId(k))
                        init_par.setdefault(
                            self.getId(i) + '_' + self.getId(k), k.getValue()
                        )
                        # local parameters are hashed with reaction name
                        if self.__DEBUG__:
                            print('%s_%s' % (self.getId(i), self.getId(k)), end=' ')
                    if self.__DEBUG__:
                        print('')
                    req = req.replace('@', '')
                else:
                    InfixParser.setNameStr('', '')
                    if hasTimeS != None:
                        SRs = {tSymb: '_TIME_'}
                        SRs.update(self.__reserved__)
                        InfixParser.SymbolReplacements = SRs
                    else:
                        InfixParser.SymbolReplacements = self.__reserved__
                    InfixParser.parse(req)
                    p_names = InfixParser.names
                    req = InfixParser.output
                    if InfixParser.DelayRemoved:
                        delayignore.append(self.getId(i))
                    self.checkParsedInfix()

                # update piecewise dict ... don't ask!
                self.updatePiecewiseDict(InfixParser.piecewises)
            else:
                print(
                    'WARNING: No rate equation for reaction %s in file: %s'
                    % (self.getId(i), self.sbml_file)
                )
                req = None

            rDict['Params'] = par
            rDict['RateEq'] = req
            rDict['compartment'] = None

            Substrates = []
            Products = []

            for k in i.getListOfReactants():
                species = k.getSpecies()
                stoich = -k.getStoichiometry()
                Substrates.append((species, stoich))
                # kill/collect stoichiometrymath for future processing
                smath = k.getStoichiometryMath()
                if smath != None:
                    self.__Errors__.update(
                        {
                            next(
                                self._ecount
                            ): 'StoichiometryMath (%s) not supported and ignored'
                            % k.getSpecies()
                        }
                    )

            for k in i.getListOfProducts():
                species = k.getSpecies()
                stoich = k.getStoichiometry()
                Products.append((species, stoich))
                # kill/collect stoichiometrymath for future processing
                smath = k.getStoichiometryMath()
                if smath != None:
                    self.__Errors__.update(
                        {
                            next(
                                self._ecount
                            ): 'StoichiometryMath (%s) not supported and ignored'
                            % k.getSpecies()
                        }
                    )

            # work with net stoichiometries
            rDict['Reagents'] = {}
            rDict['AllReagents'] = Substrates + Products
            rtmp = rDict['Reagents']

            for sp in Substrates + Products:
                if sp[0] not in rtmp:
                    rtmp.update({sp[0]: sp[1]})
                else:
                    rtmp[sp[0]] += sp[1]
            for r in list(rtmp.keys()):
                if abs(rtmp[r]) < 1.0e-14:
                    rtmp.pop(r)
            if i.getReversible() == True:
                t = 'Rever'
            else:
                t = 'Irrev'
            rDict['Type'] = t
            NetworkDict[self.getId(i)].update(rDict)

            mods = []
            if len(i.getListOfModifiers()) > 0:
                for m in i.getListOfModifiers():
                    # TODO: this should be done with SBML object.attributes 20110523
                    msbml = m.toSBML().split('species=\"')[1].split('\"')[0]
                    mods.append(msbml)
            else:
                mods = []
            rDict['Modifiers'] = mods
            # print "Modifiers", rDict['Modifiers']
        if len(hasFast) > 0:
            self.__Errors__.update(
                {
                    next(self._ecount): 'Fast attribute ignored for reactions:\n%s'
                    % hasFast
                }
            )
        if len(Punits) > 0:
            self.__Errors__.update(
                {
                    next(
                        self._ecount
                    ): 'Parameter units ignored for (local) parameters:\n%s'
                    % Punits
                }
            )
        if len(delayignore) > 0:
            self.__Errors__.update(
                {
                    next(self._ecount): 'delay function removed in reactions:\n%s'
                    % delayignore
                }
            )

        del hasFast, Punits, delayignore

        # ======= Add Other Model components ========
        self.getEvents()
        self.getRules()

        self.notes = ''
        if self.model.getNotes() is not None:
            self.notes = self.model.getNotesString().replace('\n', '\n# ')

        self.ModelFile = self.model.getId()
        if len(self.ModelFile) == 0:
            self.ModelFile = self.model.getName()

        InitStrings = []

        for s in list(init_var.keys()):
            self.__InitDict__.update({s: float(init_var[s])})
            setattr(self, s, float(init_var[s]))
        for s in list(init_fixed.keys()):
            self.__InitDict__.update({s: float(init_fixed[s])})
            setattr(self, s, float(init_fixed[s]))
        for s in list(init_par.keys()):
            self.__InitDict__.update({s: float(init_par[s])})
            setattr(self, s, float(init_par[s]))

        self.InitParams = list(init_par.keys())
        self.fixed_species = list(init_fixed.keys())
        self.species = list(init_var.keys())
        self.parameters = list(init_par.keys())
        self.reactions = list(NetworkDict.keys())
        self.modifiers = []
        for r in list(NetworkDict.keys()):
            self.modifiers.append((r, NetworkDict[r]['Modifiers']))
        if len(list(self.__rules__.keys())) > 0:
            self._Function_forced = ''
            for r in list(self.__rules__.keys()):
                self._Function_forced += (
                    self.__rules__[r]['name']
                    + ' = '
                    + self.__rules__[r]['formula']
                    + '\n'
                )
        else:
            self._Function_forced = 'pass\n'
        self._Function_time = ''
        self._Function_user = ''
        self._Function_init = ''
        self.__nDict__ = NetworkDict

        if len(list(self.__Errors__.keys())) > 0:
            print(
                '\n*******************************************************************'
            )
            ##  print 'Errors encountered in SBML translated!'
            print('Issues encountered in SBML translation (model processed anyway)')
            print('SBML source: %s' % self.sbml_file)
            print(
                '*******************************************************************\n'
            )
            ekeys = list(self.__Errors__.keys())
            ekeys.sort()
            for e in ekeys:
                print(self.__Errors__[e], '\n')
            print(
                '*******************************************************************\n'
            )
            time.sleep(self.__error_sleep_time__)

    def getRules(self):
        self.__rules__ = {}
        for rule in self.model.getListOfRules():
            rtype = 'unknown'
            if rule.isAssignment():
                rtype = 'assignment'
            elif rule.isRate():
                rtype = 'rate'
            elif rule.isAlgebraic():
                rtype = 'algebraic'
                self.__Errors__.update(
                    {
                        next(self._ecount): 'Algebraic rule (%s) ignored'
                        #% rule.getFormula()
                        % self.sbmlFormulaToInfix(rule.getMath())
                    }
                )

            assert rtype in ['assignment', 'rate', 'algebraic'], (
                '\n%s rules currently not supported.' % rtype
            )

            # check for csymbol time
            hasTimeS = self.searchForCsymbolTime(rule.getMath())

            tSymb = None
            #formula = rule.getFormula()
            formula = self.sbmlFormulaToInfix(rule.getMath())
            p_names = None
            if hasTimeS != None:
                tSymb = hasTimeS.group()[hasTimeS.group().find('>') :]
                tSymb = tSymb.replace('>', '').replace('<', '').strip()
                print(
                    'csymbol time defined as \"%s\" in rule %s'
                    % (tSymb, self.getId(rule))
                )
                InfixParser.setNameStr('', '')
                SRs = {tSymb: '_TIME_'}
                SRs.update(self.__reserved__)
                InfixParser.SymbolReplacements = SRs
                InfixParser.parse(formula)
                p_names = InfixParser.names
                formula = InfixParser.output
                pws = InfixParser.piecewises
                self.checkParsedInfix()
            else:
                InfixParser.setNameStr('', '')
                InfixParser.SymbolReplacements = self.__reserved__
                InfixParser.parse(formula)
                p_names = InfixParser.names
                formula = InfixParser.output
                pws = InfixParser.piecewises
                self.checkParsedInfix()

            # update piecewise dict ... don't ask!
            self.updatePiecewiseDict(InfixParser.piecewises)

            self.__rules__.update(
                {
                    rule.getVariable(): {
                        'name': rule.getVariable(),
                        'formula': formula,
                        'type': rtype,
                        '_names': p_names,
                    }
                }
            )

    def removeCompartmentFromKineticLaw(self, kl):
        strBuf = io.StringIO()
        mathMLin = self.sbmlFormulaToInfix(kl.getMath())
        strBuf.write(mathMLin)
        strBuf.seek(0)

        etree = ElementTree.parse(strBuf)
        root = etree.getroot()

        node_idx = {}
        counter = itertools.count(1)
        self._comp_idx = []

        def idxNode(node, idx=0):
            ##  print 'node.tag', node.tag
            node.attrib.update({'idx': str(idx)})
            node_idx.update({idx: node})
            if node.text != None and node.text.strip() in list(
                self.__compartments__.keys()
            ):
                node.attrib.update({'compartment': '1'})
                self._comp_idx.append(idx)
            else:
                node.attrib.update({'compartment': '0'})

            children = node.getchildren()
            for child in range(len(children)):
                children[child].attrib.update({'parent': str(idx)})
                ##  print '\tchild.tag', children[child].tag
                idxNode(children[child], next(counter))

        idxNode(root, idx=0)

        if len(self._comp_idx) > 0:
            for comp_idx in self._comp_idx:
                cParent = int(node_idx[comp_idx].attrib['parent'])
                if (
                    len(node_idx[cParent].getchildren()) == 3
                    and node_idx[cParent].getchildren()[0].tag
                    == '{http://www.w3.org/1998/Math/MathML}times'
                ):
                    for c in node_idx[cParent].getchildren():
                        if c.tag != '{http://www.w3.org/1998/Math/MathML}times' and c.text.strip() not in list(
                            self.__compartments__.keys()
                        ):
                            cParentParent = int(node_idx[cParent].attrib['parent'])
                            for cp in node_idx[cParentParent].getchildren():
                                if cp.attrib['idx'] == node_idx[cParent].attrib['idx']:
                                    cidx = node_idx[cParentParent]._children.index(cp)
                                    node_idx[cParentParent].remove(node_idx[cParent])
                                    c.attrib.update(
                                        {
                                            'parent': node_idx[cParentParent].attrib[
                                                'idx'
                                            ]
                                        }
                                    )
                                    node_idx[cParentParent].insert(cidx, c)
                                    break
                elif (
                    len(node_idx[cParent].getchildren()) == 2
                    and node_idx[cParent].getchildren()[0].tag
                    == '{http://www.w3.org/1998/Math/MathML}minus'
                ):
                    for c in node_idx[cParent].getchildren():
                        ##  print c.tag, c.attrib
                        if c.tag == '{http://www.w3.org/1998/Math/MathML}minus':
                            cParentParent = int(node_idx[cParent].attrib['parent'])
                            for cp in node_idx[cParentParent].getchildren():
                                ##  print cp.attrib['idx'], node_idx[cParent].attrib['idx']
                                if cp.attrib['idx'] == node_idx[cParent].attrib['idx']:
                                    cidx = node_idx[cParentParent]._children.index(cp)
                                    node_idx[cParentParent].remove(node_idx[cParent])
                                    c.attrib.update(
                                        {
                                            'parent': node_idx[cParentParent].attrib[
                                                'idx'
                                            ]
                                        }
                                    )
                                    node_idx[cParentParent].insert(cidx, c)
                                    break
                elif len(node_idx[cParent].getchildren()) > 3:
                    for c in node_idx[cParent].getchildren():
                        if c.text != None and c.text.strip() in list(
                            self.__compartments__.keys()
                        ):
                            node_idx[cParent].remove(node_idx[comp_idx])
                            break
        for n in node_idx:
            if 'idx' in node_idx[n].attrib:
                node_idx[n].attrib.pop('idx')
            if 'compartment' in node_idx[n].attrib:
                node_idx[n].attrib.pop('compartment')
            if 'parent' in node_idx[n].attrib:
                node_idx[n].attrib.pop('parent')

        strBuf = io.StringIO()
        etree.write(strBuf)
        strBuf.seek(0)
        mathMLout = strBuf.read()
        newAST = self.SBML.readMathMLFromString(mathMLout)
        kl.setMath(newAST)
