# python 2/3 compatibility
from __future__ import division, print_function

import sys
import os.path
import numpy
import pandas
import copy
import json
import jxmlease
import xml.etree.ElementTree as ET
import csv
from sbtab import SBtab

import rba
from rbatools.data_block import DataBlock


class SimulationDataRBA(object):
    """
    Class holding information on simulations with the model.
    Attributes
    ----------
    StructuralInformation : rbatools.rba_model_structure.ModelStructureRBA object.
         Model description
    SessionName : str
         Name of simulation session
    ProteinData : rbatools.data_block DataBlock object.
         Simulated protein levels
    ReactionData : rbatools.data_block DataBlock object.
         Simulated reaction data
    EnzymeData : rbatools.data_block DataBlock object.
         Simulated enzyme data
    ProcessData : rbatools.data_block DataBlock object.
         Simulated process machinery data
    MetaboliteConstraintData : rbatools.data_block DataBlock object.
         Simulation information on mass-balance constraints
    DensityConstraintData : rbatools.data_block DataBlock object.
         Simulation information on compartment-density constraints
    ProcessConstraintData : rbatools.data_block DataBlock object.
         Simulation information on process-capacity constraints
    EnzymeConstraintData : rbatools.data_block DataBlock object.
         Simulation information on enzyme-efficiency constraints
    """

    def __init__(self, rbaModelStructure):
        """
        Initiates Simulation data object
        Parameters
        ----------
        rbaModelStructure : rbatools.rba_model_structure.ModelStructureRBA
        """

        self.StructuralInformation = copy.deepcopy(rbaModelStructure)

        self.ProteinData = DataBlock()
        self.ProtoProteinData = DataBlock()
        self.ReactionData = DataBlock()
        self.ExchangeData = DataBlock()
        self.uniqueReactionData = DataBlock()
        self.EnzymeData = DataBlock()
        self.ProcessData = DataBlock()
        self.MetaboliteConstraintData = DataBlock()
        self.DensityConstraintData = DataBlock()
        self.EnzymeConstraintData = DataBlock()
        self.ProcessConstraintData = DataBlock()
        self.GeneralRunInfo = DataBlock()
        self.ObjectiveFunctionInfo = DataBlock()

        self.SessionName = ''

        self.ProteinData.from_dict({})
        self.ProtoProteinData.from_dict({})
        self.ReactionData.from_dict({})
        self.ExchangeData.from_dict({})
        self.uniqueReactionData.from_dict({})
        self.EnzymeData.from_dict({})
        self.ProcessData.from_dict({})
        self.MetaboliteConstraintData.from_dict({})
        self.DensityConstraintData.from_dict({})
        self.EnzymeConstraintData.from_dict({})
        self.ProcessConstraintData.from_dict({})
        self.GeneralRunInfo.from_dict({})
        self.ObjectiveFunctionInfo.from_dict({})

    def from_simulation_results(self, rbaSession, session_name:str=''):
        """
        Imports data from rbatools.RBA_Controler object
        Parameters
        ----------
        rbaSession : rbatools.rba_session.SessionRBA
            Defines from which object to extract the data from
        session_name: str
            Defines the name of the session
            Is appended to the filenames generated by export methods.
        """
        self.SessionName = session_name

        self.run_names = list(rbaSession.Results['ObjectiveValue'])
        ObjDict = {}
        for run in list(rbaSession.Results['ObjectiveValue']):
            ObjDict.update({run: rbaSession.Results['ObjectiveValue'].loc['ObjectiveValue', run]})

        SolutionType_Dict = {}
        for run in list(rbaSession.Results['SolutionType']):
            SolutionType_Dict.update(
                {run: rbaSession.Results['SolutionType'].loc['SolutionType', run]})

        MuDict = {}
        for run in list(rbaSession.Results['Mu']):
            MuDict.update({run: rbaSession.Results['Mu'].loc['Mu', run]})

        self.GeneralRunInfo.add_entries({'ProblemType': SolutionType_Dict})
        self.GeneralRunInfo.add_entries({'Mu': MuDict})
        self.GeneralRunInfo.add_entries({'Obj_Val': ObjDict})

        for var in list(rbaSession.Results['ObjectiveFunction'].index):
            if var not in self.ObjectiveFunctionInfo.Elements:
                self.ObjectiveFunctionInfo.Elements.update({var: {}})
            for run in list(rbaSession.Results['ObjectiveFunction']):
                self.ObjectiveFunctionInfo.Elements[var].update(
                    {run: rbaSession.Results['ObjectiveFunction'].loc[var, run]})

        for exchange in list(rbaSession.Results['ExchangeFluxes'].index):
            if exchange not in self.ExchangeData.Elements:
                self.ExchangeData.Elements.update({exchange: {}})
            for run in list(rbaSession.Results['ExchangeFluxes']):
                self.ExchangeData.Elements[exchange].update(
                    {run: rbaSession.Results['ExchangeFluxes'].loc[exchange, run]})

        for reaction in list(rbaSession.Results['Reactions'].index):
            if reaction not in self.ReactionData.Elements:
                self.ReactionData.Elements.update({reaction: {}})
            for run in list(rbaSession.Results['Reactions']):
                self.ReactionData.Elements[reaction].update(
                    {run: rbaSession.Results['Reactions'].loc[reaction, run]})

        for reaction in list(rbaSession.Results['uniqueReactions'].index):
            if reaction not in self.uniqueReactionData.Elements:
                self.uniqueReactionData.Elements.update({reaction: {}})
            for run in list(rbaSession.Results['uniqueReactions']):
                self.uniqueReactionData.Elements[reaction].update(
                    {run: rbaSession.Results['uniqueReactions'].loc[reaction, run]})

        for enzyme in list(rbaSession.Results['Enzymes'].index):
            if enzyme not in self.EnzymeData.Elements:
                self.EnzymeData.Elements.update({enzyme: {}})
            for run in list(rbaSession.Results['Enzymes']):
                self.EnzymeData.Elements[enzyme].update(
                    {run: rbaSession.Results['Enzymes'].loc[enzyme, run]})

        for process in list(rbaSession.Results['Processes'].index):
            if process not in self.ProcessData.Elements:
                self.ProcessData.Elements.update({process: {}})
            for run in list(rbaSession.Results['Processes']):
                self.ProcessData.Elements[process].update(
                    {run: rbaSession.Results['Processes'].loc[process, run]})

        for protein in list(rbaSession.Results['Proteins'].index):
            if protein not in self.ProteinData.Elements:
                self.ProteinData.Elements.update({protein: {}})
            for run in list(rbaSession.Results['Proteins']):
                self.ProteinData.Elements[protein].update(
                    {run: rbaSession.Results['Proteins'].loc[protein, run]})

        for protoprotein in list(rbaSession.Results['ProtoProteins'].index):
            if protoprotein not in self.ProtoProteinData.Elements:
                self.ProtoProteinData.Elements.update({protoprotein: {}})
            for run in list(rbaSession.Results['ProtoProteins']):
                self.ProtoProteinData.Elements[protoprotein].update(
                    {run: rbaSession.Results['ProtoProteins'].loc[protoprotein, run]})

        for constr in list(rbaSession.Results['Constraints'].index):
            if constr in list(self.StructuralInformation.MetaboliteConstraintsInfo.Elements.keys()):
                if constr not in self.MetaboliteConstraintData.Elements:
                    self.MetaboliteConstraintData.Elements.update({constr: {}})
                for run in list(rbaSession.Results['Constraints']):
                    self.MetaboliteConstraintData.Elements[constr].update(
                        {run: rbaSession.Results['Constraints'].loc[constr, run]})
            if constr in list(self.StructuralInformation.DensityConstraintsInfo.Elements.keys()):
                if constr not in self.DensityConstraintData.Elements:
                    self.DensityConstraintData.Elements.update({constr: {}})
                for run in list(rbaSession.Results['Constraints']):
                    self.DensityConstraintData.Elements[constr].update(
                        {run: rbaSession.Results['Constraints'].loc[constr, run]})
            if constr in list(self.StructuralInformation.EnzymeConstraintsInfo.Elements.keys()):
                if constr not in self.EnzymeConstraintData.Elements:
                    self.EnzymeConstraintData.Elements.update({constr: {}})
                for run in list(rbaSession.Results['Constraints']):
                    self.EnzymeConstraintData.Elements[constr].update(
                        {run: rbaSession.Results['Constraints'].loc[constr, run]})
            if constr in list(self.StructuralInformation.ProcessConstraintsInfo.Elements.keys()):
                if constr not in self.ProcessConstraintData.Elements:
                    self.ProcessConstraintData.Elements.update({constr: {}})
                for run in list(rbaSession.Results['Constraints']):
                    self.ProcessConstraintData.Elements[constr].update(
                        {run: rbaSession.Results['Constraints'].loc[constr, run]})

    def from_json(self, inputString:str):
        """
        Imports data from JSON string object
        Parameters
        ----------
        inputString: json-string
        """

        Block = json.loads(inputString)
        self.ReactionData = DataBlock()
        self.ProteinData = DataBlock()
        self.EnzymeData = DataBlock()
        self.ProcessData = DataBlock()
        self.MetaboliteConstraintData = DataBlock()
        self.DensityConstraintData = DataBlock()
        self.EnzymeConstraintData = DataBlock()
        self.ProcessConstraintData = DataBlock()
        self.ReactionData.from_dict(Block['ReactionData'])
        self.ProteinData.from_dict(Block['ProteinData'])
        self.EnzymeData.from_dict(Block['EnzymeData'])
        self.ProcessData.from_dict(Block['ProcessData'])
        self.MetaboliteConstraintData.from_dict(Block['MetaboliteConstraintData'])
        self.DensityConstraintData.from_dict(Block['DensityConstraintData'])
        self.EnzymeConstraintData.from_dict(Block['EnzymeConstraintData'])
        self.ProcessConstraintData.from_dict(Block['ProcessConstraintData'])

    def export_xml(self):
        """
        Exports xml-file
        """

        x = _html_style(self)
        root = ET.fromstring(jxmlease.emit_xml(x, encoding='utf-8'))
        m = ET.tostring(root, 'utf-8')
        return(m)

    def export_sbtab(self, filename:str="", add_links:bool=False,return_result : bool = False):
        """
        Exports simulation data in one single sbtab file

        Parameters
        ----------
        filename : str
            Name, under which to save SBtab-file
        add_links : str
            Wheter to implement entry-format, which allows links between table-elements.
        return_result : bool
            Whether result should be returned. If false nothing is returned.
            Default: False
        Returns
        -------
        SBtab
        """
        GeneralRunInfoTable = self.GeneralRunInfo.to_sbtab(
            table_id='run_information', table_type='QuantityMatrix', table_name='Run information')
        GeneralRunInfoTable.filename = 'RunInfo.tsv'
        GeneralRunInfoTable.change_attribute(
            'Text', 'Growth rates mu and cellular objective values (by default: minimisation of total enzyme concentration).')
        # GeneralRunInfoTable.unset_attribute('Date')
        GeneralRunInfoTable.unset_attribute('SBtabVersion')

        ObjectiveFunctionDataTable = self.ObjectiveFunctionInfo.to_sbtab(
            table_id='objective_coefficients', table_type='QuantityMatrix', table_name='Linear objective')
        ObjectiveFunctionDataTable.filename = 'ObjectiveFunctionData.tsv'
        ObjectiveFunctionDataTable.change_attribute('Unit', '')
        ObjectiveFunctionDataTable.change_attribute('QuantityType', 'objective_coefficient')
        ObjectiveFunctionDataTable.change_attribute(
            'Text', 'Coefficients in objective function (<0 : maximisation , >0 : minimisation)')
        # ObjectiveFunctionDataTable.unset_attribute('Date')
        ObjectiveFunctionDataTable.unset_attribute('SBtabVersion')

        ReactionDataTable = self.ReactionData.to_sbtab(table_id='reaction_flux', table_type='QuantityMatrix', table_name='Reaction fluxes')
        ReactionDataTable.filename = 'ReactionData.tsv'
        ReactionDataTable.change_attribute('Unit', 'mmol/(h*gDW)')
        ReactionDataTable.change_attribute('QuantityType', 'reaction_flux')
        ReactionDataTable.change_attribute(
            'Text', 'Reaction fluxes obtained in the simulation runs (table columns).')
        # ReactionDataTable.unset_attribute('Date')
        ReactionDataTable.unset_attribute('SBtabVersion')

        EnzymeDataTable = self.EnzymeData.to_sbtab(
            table_id='enzyme_concentration', table_type='QuantityMatrix', table_name='Enzyme concentrations')
        EnzymeDataTable.filename = 'EnzymeData.tsv'
        EnzymeDataTable.change_attribute('Unit', 'mmol/gDW')
        EnzymeDataTable.change_attribute('QuantityType', 'enzyme_concentration')
        EnzymeDataTable.change_attribute(
            'Text', 'Enzyme concentrations obtained in the simulation runs (table columns).')
        # EnzymeDataTable.unset_attribute('Date')
        EnzymeDataTable.unset_attribute('SBtabVersion')

        ProcessDataTable = self.ProcessData.to_sbtab(
            table_id='machine_concentration', table_type='QuantityMatrix', table_name='Machine concentrations')
        ProcessDataTable.filename = 'ProcessData.tsv'
        ProcessDataTable.change_attribute('Unit', 'mmol/gDW')
        ProcessDataTable.change_attribute('QuantityType', 'machine_concentration')
        ProcessDataTable.change_attribute(
            'Text', 'Macromolecular machine concentrations obtained in the simulation runs (table columns).')
        # ProcessDataTable.unset_attribute('Date')
        ProcessDataTable.unset_attribute('SBtabVersion')

        ProteinDataTable = self.ProteinData.to_sbtab(
            table_id='protein_concentration', table_type='QuantityMatrix', table_name='Protein concentrations')
        ProteinDataTable.filename = 'ProteinData.tsv'
        ProteinDataTable.change_attribute('Unit', 'mmol/gDW')
        ProteinDataTable.change_attribute('QuantityType', 'protein_concentration')
        ProteinDataTable.change_attribute(
            'Text', 'Protein concentrations obtained in the simulation runs (table columns).')
        # ProteinDataTable.unset_attribute('Date')
        ProteinDataTable.unset_attribute('SBtabVersion')

        MetaboliteConstraintDataTable = self.MetaboliteConstraintData.to_sbtab(
            table_id='metabolite_mass_balance_dual', table_type='QuantityMatrix', table_name='Metabolite mass-balance dual values')
        MetaboliteConstraintDataTable.filename = 'MetaboliteConstraintData.tsv'
        MetaboliteConstraintDataTable.change_attribute('Unit', '')
        MetaboliteConstraintDataTable.change_attribute('QuantityType', 'lagrange_multiplier')
        MetaboliteConstraintDataTable.change_attribute(
            'Text', 'Shadow prices of the metabolite mass-balance constraints obtained in the simulation runs (table columns). The measurement units of shadow prices are given by the measurement unit of objective function, divided by the measurement units of the respective constraints')
        # MetaboliteConstraintDataTable.unset_attribute('Date')
        MetaboliteConstraintDataTable.unset_attribute('SBtabVersion')

        DensityConstraintDataTable = self.DensityConstraintData.to_sbtab(
            table_id='density_constraint_dual', table_type='QuantityMatrix', table_name='Compartment density dual values')
        DensityConstraintDataTable.filename = 'DensityConstraintData.tsv'
        DensityConstraintDataTable.change_attribute('QuantityType', 'lagrange_multiplier')
        DensityConstraintDataTable.change_attribute('Unit', '')
        DensityConstraintDataTable.change_attribute(
            'Text', 'Shadow prices of the density constraints obtained in the simulation runs (table columns). The measurement units of shadow prices are given by the measurement unit of objective function, divided by the measurement units of the respective constraints')
        # DensityConstraintDataTable.unset_attribute('Date')
        DensityConstraintDataTable.unset_attribute('SBtabVersion')

        EnzymeConstraintDataTable = self.EnzymeConstraintData.to_sbtab(table_id='enzyme_capacity_dual', table_type='QuantityMatrix', table_name='Enzyme capacity dual values')
        EnzymeConstraintDataTable.filename = 'EnzymeConstraintData.tsv'
        EnzymeConstraintDataTable.change_attribute('QuantityType', 'lagrange_multiplier')
        EnzymeConstraintDataTable.change_attribute('Unit', '')
        EnzymeConstraintDataTable.change_attribute(
            'Text', 'Shadow prices of the enzyme-capacity constraints obtained in the simulation runs (table columns). The measurement units of shadow prices are given by the measurement unit of objective function, divided by the measurement units of the respective constraints')
        # EnzymeConstraintDataTable.unset_attribute('Date')
        EnzymeConstraintDataTable.unset_attribute('SBtabVersion')

        ProcessConstraintDataTable = self.ProcessConstraintData.to_sbtab(
            table_id='machine_capacity_dual', table_type='QuantityMatrix', table_name='Machine capacity dual values')
        ProcessConstraintDataTable.filename = 'ProcessConstraintData.tsv'
        ProcessConstraintDataTable.change_attribute('Unit', '')
        ProcessConstraintDataTable.change_attribute('QuantityType', 'lagrange_multiplier')
        ProcessConstraintDataTable.change_attribute(
            'Text', 'Shadow prices of the machine-capacity constraints obtained in the simulation runs (table columns). The measurement units of shadow prices are given by the measurement unit of objective function, divided by the measurement units of the respective constraints')
        # ProcessConstraintDataTable.unset_attribute('Date')
        ProcessConstraintDataTable.unset_attribute('SBtabVersion')

        if filename !="":
            filename_SBtab = filename
        else:
            filename_SBtab = 'RBA_results'

        reaction_variables=[i[0] for i in ReactionDataTable.value_rows]
        #reaction_variables=list(ReactionDataTable.to_data_frame()['VariableID'])
        process_variables=[i[0] for i in ProcessDataTable.value_rows]
        #process_variables=list(ProcessDataTable.to_data_frame()['VariableID'])
        enzyme_variables=[i[0] for i in EnzymeDataTable.value_rows]
        #enzyme_variables=list(EnzymeDataTable.to_data_frame()['VariableID'])
        protein_variables=[i[0] for i in ProteinDataTable.value_rows]
        #protein_variables=list(ProteinDataTable.to_data_frame()['VariableID'])
        metabolite_constraint_variables=[i[0] for i in MetaboliteConstraintDataTable.value_rows]
        #metabolite_constraint_variables=list(MetaboliteConstraintDataTable.to_data_frame()['VariableID'])
        density_constraint_variables=[i[0] for i in DensityConstraintDataTable.value_rows]
        #density_constraint_variables=list(DensityConstraintDataTable.to_data_frame()['VariableID'])
        process_constraint_variables=[i[0] for i in ProcessConstraintDataTable.value_rows]
        #process_constraint_variables=list(ProcessConstraintDataTable.to_data_frame()['VariableID'])
        enzyme_constraint_variables=[i[0] for i in EnzymeConstraintDataTable.value_rows]
        #enzyem_constraint_variables=list(EnzymeConstraintDataTable.to_data_frame()['VariableID'])

        if add_links:
            ReactionDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Reaction/'+entry+'!)') for entry in reaction_variables], position=1)
            ProcessDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Process/'+entry+'!)')for entry in process_variables], position=1)
            EnzymeDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Enzyme/'+entry+'!)')for entry in enzyme_variables], position=1)
            ProteinDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Protein/'+entry+'!)')for entry in protein_variables], position=1)
            MetaboliteConstraintDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Compound/'+entry+'!)') for entry in metabolite_constraint_variables], position=1)
            DensityConstraintDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Compartment/'+entry+'!)') for entry in density_constraint_variables], position=1)
            ProcessConstraintDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Process/'+entry+'!)') for entry in process_constraint_variables], position=1)
            EnzymeConstraintDataTable.add_column(column_list=['!ElementID']+[str('(!'+'Enzyme/'+entry+'!)') for entry in enzyme_constraint_variables], position=1)
            filename_SBtab += '_HTML'
        else:
            ReactionDataTable.add_column(column_list=['!ElementID']+reaction_variables, position=1)
            ProcessDataTable.add_column(column_list=['!ElementID']+process_variables, position=1)
            EnzymeDataTable.add_column(column_list=['!ElementID']+enzyme_variables, position=1)
            ProteinDataTable.add_column(column_list=['!ElementID']+protein_variables, position=1)
            MetaboliteConstraintDataTable.add_column(column_list=['!ElementID']+metabolite_constraint_variables, position=1)
            DensityConstraintDataTable.add_column(column_list=['!ElementID']+density_constraint_variables, position=1)
            ProcessConstraintDataTable.add_column(column_list=['!ElementID']+process_constraint_variables, position=1)
            EnzymeConstraintDataTable.add_column(column_list=['!ElementID']+enzyme_constraint_variables, position=1)

        ReactionDataTable.remove_column(position=2)
        ProcessDataTable.remove_column(position=2)
        EnzymeDataTable.remove_column(position=2)
        ProteinDataTable.remove_column(position=2)
        MetaboliteConstraintDataTable.remove_column(position=2)
        DensityConstraintDataTable.remove_column(position=2)
        ProcessConstraintDataTable.remove_column(position=2)
        EnzymeConstraintDataTable.remove_column(position=2)

        self.sbtab_doc = SBtab.SBtabDocument(name='rbatools_SimulationData_withLinks',
                                       sbtab_init=None, filename=str(filename_SBtab+'.tsv'))
        self.sbtab_doc.add_sbtab(GeneralRunInfoTable)
        self.sbtab_doc.add_sbtab(ObjectiveFunctionDataTable)
        self.sbtab_doc.add_sbtab(ReactionDataTable)
        self.sbtab_doc.add_sbtab(EnzymeDataTable)
        self.sbtab_doc.add_sbtab(ProcessDataTable)
        self.sbtab_doc.add_sbtab(ProteinDataTable)
        self.sbtab_doc.add_sbtab(MetaboliteConstraintDataTable)
        self.sbtab_doc.add_sbtab(DensityConstraintDataTable)
        self.sbtab_doc.add_sbtab(EnzymeConstraintDataTable)
        self.sbtab_doc.add_sbtab(ProcessConstraintDataTable)

        self.sbtab_doc.change_attribute('DocumentName', 'RBA data')
        self.sbtab_doc.name = filename
        self.sbtab_doc.change_attribute('DocumentType', 'rba-simulation-data')
        self.sbtab_doc.write()
        if return_result:
            return(self.sbtab_doc)

    def export_json(self,return_result : bool = False):
        """
        Returns simulation data as JSON string
        Parameters
        ----------
        return_result : bool
            Whether result should be returned. If false nothing is returned.
            Default: False
        Returns
        -------
        JSON string
        """

        Block = {'ReactionData': self.ReactionData.Elements,
                 'ProteinData': self.ProteinData.Elements,
                 'EnzymeData': self.EnzymeData.Elements,
                 'ProcessData': self.ProcessData.Elements,
                 'MetaboliteConstraintData': self.MetaboliteConstraintData.Elements,
                 'DensityConstraintData': self.DensityConstraintData.Elements,
                 'EnzymeConstraintData': self.EnzymeConstraintData.Elements,
                 'ProcessConstraintData': self.ProcessConstraintData.Elements}
        if return_result:
            return(json.dumps(Block))

    def export_csv(self, output_directory:str="", deleteZerosRows:bool=True,return_result : bool = False):
        """
        Exports simulation data as csv files
        Parameters
        ----------
        output_directory: str
            Path to location, where csvs should be stored.
            Default: "" --> current directory is used
        deleteZerosRows: bool
            Boolean wheter to remove rows which have only zero entries.
            (e.g. reactions which never carry flux in all runs)
            Default: True
        return_result : bool
            Whether result should be returned. If false nothing is returned.
            Default: False
        Returns
        -------
        Dictionaries with csvs
        """
        if output_directory =="":
            output_directory_prefix=""
        else:
            if output_directory.endswith("/"):
                output_directory_prefix=output_directory
            else:
                output_directory_prefix=str(output_directory+"/")

        self.csvs = {}
        exchange_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ExchangeData.csv'
        else:
            filename = output_directory_prefix+'ExchangeData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ExchangeData.Elements.keys())
            exchange_data_csv += ','.join(['']+list(self.ExchangeData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ExchangeData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ExchangeData.Elements[i].values())
                valuerow2 = []
                for j in valuerow:
                    if pandas.isna(j):
                        valuerow2.append(0)
                    else:
                        valuerow2.append(j)
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow2):
                        row = [i]+valuerow2
                        exchange_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow2
                    exchange_data_csv += ','.join(map(row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ExchangeData.csv'] = exchange_data_csv

        general_info_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_GeneralInfo.csv'
        else:
            filename = output_directory_prefix+'GeneralInfo.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.GeneralRunInfo.Elements.keys())
            general_info_csv += ','.join([''] +
                                         list(self.GeneralRunInfo.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.GeneralRunInfo.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.GeneralRunInfo.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        general_info_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    general_info_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['GeneralInfo.csv'] = general_info_csv

        reaction_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ReactionData.csv'
        else:
            filename = output_directory_prefix+'ReactionData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ReactionData.Elements.keys())
            reaction_data_csv += ','.join(['']+list(self.ReactionData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ReactionData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ReactionData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        reaction_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    reaction_data_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ReactionData.csv'] = reaction_data_csv

        enzyme_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_EnzymeData.csv'
        else:
            filename = output_directory_prefix+'EnzymeData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.EnzymeData.Elements.keys())
            enzyme_data_csv += ','.join(['']+list(self.EnzymeData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.EnzymeData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.EnzymeData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        enzyme_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    enzyme_data_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['EnzymeData.csv'] = enzyme_data_csv

        protein_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ProteinData.csv'
        else:
            filename = output_directory_prefix+'ProteinData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ProteinData.Elements.keys())
            protein_data_csv += ','.join(['']+list(self.ProteinData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ProteinData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ProteinData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        protein_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    protein_data_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ProteinData.csv'] = protein_data_csv

        proto_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ProtoProteinData.csv'
        else:
            filename = output_directory_prefix+'ProtoProteinData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ProtoProteinData.Elements.keys())
            proto_data_csv += ','.join([''] +
                                       list(self.ProtoProteinData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ProtoProteinData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ProtoProteinData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        proto_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    proto_data_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ProtoData.csv'] = proto_data_csv

        process_data_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ProcessData.csv'
        else:
            filename = output_directory_prefix+'ProcessData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ProcessData.Elements.keys())
            process_data_csv += ','.join(['']+list(self.ProcessData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ProcessData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ProcessData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        process_data_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    process_data_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ProcessData.csv'] = process_data_csv

        metabolite_constraint_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_MetaboliteConstraintData.csv'
        else:
            filename = output_directory_prefix+'MetaboliteConstraintData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.MetaboliteConstraintData.Elements.keys())
            metabolite_constraint_csv += ','.join(
                ['']+list(self.MetaboliteConstraintData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.MetaboliteConstraintData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.MetaboliteConstraintData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        metabolite_constraint_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    metabolite_constraint_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['MetaboliteConstraint.csv'] = metabolite_constraint_csv

        density_constraint_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_DensityConstraintData.csv'
        else:
            filename = output_directory_prefix+'DensityConstraintData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.DensityConstraintData.Elements.keys())
            density_constraint_csv += ','.join([''] +
                                               list(self.DensityConstraintData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.DensityConstraintData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.DensityConstraintData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        density_constraint_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    density_constraint_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['DensityConstraint.csv'] = density_constraint_csv

        process_constraint_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_ProcessConstraintData.csv'
        else:
            filename = output_directory_prefix+'ProcessConstraintData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.ProcessConstraintData.Elements.keys())
            process_constraint_csv += ','.join([''] +
                                               list(self.ProcessConstraintData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.ProcessConstraintData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.ProcessConstraintData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        process_constraint_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    process_constraint_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['ProcessConstraint.csv'] = process_constraint_csv

        enzyme_constraint_csv = ''
        if len(self.SessionName) > 0:
            filename = output_directory_prefix+self.SessionName+'_EnzymeConstraintData.csv'
        else:
            filename = output_directory_prefix+'EnzymeConstraintData.csv'
        with open(filename, "w", newline='') as fp:
            wr = csv.writer(fp, dialect='excel')
            IDs = list(self.EnzymeConstraintData.Elements.keys())
            enzyme_constraint_csv += ','.join([''] +
                                              list(self.EnzymeConstraintData.Elements[IDs[0]].keys()))+'\n'
            wr.writerow([None]+list(self.EnzymeConstraintData.Elements[IDs[0]].keys()))
            for i in list(IDs):
                valuerow = list(self.EnzymeConstraintData.Elements[i].values())
                if deleteZerosRows:
                    if not all(v == 0 for v in valuerow):
                        row = [i]+valuerow
                        enzyme_constraint_csv += ','.join(map(str, row))+'\n'
                        wr.writerow(row)
                else:
                    row = [i]+valuerow
                    enzyme_constraint_csv += ','.join(map(str, row))+'\n'
                    wr.writerow(row)
        fp.close()
        self.csvs['EnzymeConstraint.csv'] = enzyme_constraint_csv
        if return_result:
            return(self.csvs)

    def export_escher_map(self, type:str='fluxes',output_directory:str="",return_result : bool = False):
        """
        Exports input file for generation of Escher maps.
        https://escher.github.io
        If argument type is 'fluxes' the method return an Eschermap for the
        visualisation of reaction-fluxes
        If argument type is 'investment' the method return an Eschermap for the
        visualisation of the amount of amino-acids invested in enzyme machinery
        for reactions.

        Parameters
        ----------
        type: str ('fluxes' or 'investment')
            Default: 'fluxes'
        output_directory: str
            Path to location, where csvs should be stored.
            Default: "" --> current directory is used
        return_result : bool
            Whether result should be returned. If false nothing is returned.
            Default: False
        Returns
        -------
        JSON file
        """
        if output_directory =="":
            output_directory_prefix=""
        else:
            if output_directory.endswith("/"):
                output_directory_prefix=output_directory
            else:
                output_directory_prefix=str(output_directory+"/")

        if type is 'fluxes':
            IDs = [self.StructuralInformation.ReactionInfo.Elements[id]["OtherIDs"]["ProtoID"] for id in list(self.uniqueReactionData.Elements.keys())]
            for run in self.uniqueReactionData.Elements[list(self.uniqueReactionData.Elements.keys())[0]]:
                Values = [rxn[run] for rxn in list(self.uniqueReactionData.Elements.values())]
                intermediateReactionFluxes = dict(zip(IDs, Values))
                ReactionFluxes = {id: val for id,
                                  val in intermediateReactionFluxes.items() if val != 0.}
                if len(self.SessionName) > 0:
                    filename = output_directory_prefix+self.SessionName+'_RBA_Eschermap_fluxes_'+run+'.json'
                else:
                    filename = output_directory_prefix+'RBA_Eschermap_fluxes_'+run+'.json'
                self.eschermap = json.dumps(ReactionFluxes, indent=4)
                with open(filename, 'w') as fout:
                    fout.write(json.dumps(ReactionFluxes, indent=4))
        if type is 'investment':
            IDs = list(self.uniqueReactionData.Elements.keys())
            for run in self.uniqueReactionData.Elements[list(self.uniqueReactionData.Elements.keys())[0]]:
                Fluxes = [rxn[run] for rxn in list(self.uniqueReactionData.Elements.values())]
                Intermediate = zip(IDs, Fluxes)
                RxnsToTest = [i[0] for i in Intermediate if i[1] != 0.]
                Values = [_determine_investment(self, rxn, run) for rxn in RxnsToTest]
                ReactionInvestments = {self.StructuralInformation.ReactionInfo.Elements[id]["OtherIDs"]["ProtoID"]: val for id,
                                       val in dict(zip(RxnsToTest, Values)).items()}
                if len(self.SessionName) > 0:
                    filename = output_directory_prefix+self.SessionName+'_RBA_Eschermap_investment_'+run+'.json'
                else:
                    filename = output_directory_prefix+'RBA_Eschermap_investment_'+run+'.json'
                self.eschermap = json.dumps(ReactionInvestments, indent=4)
                with open(filename, 'w') as fout:
                    fout.write(json.dumps(ReactionInvestments, indent=4))
        if return_result:
            return(self.eschermap)

    def export_proteo_map(self, type:str='genes',output_directory:str="",return_result : bool = False):
        """
        Exports input file for the generation of Proteo maps from
        simulation data.
        If argument type is 'proto' the method exports a proteo map with location independent protein IDs.
        If argument type is 'isoforms' the method exports a proteo map with location specific protein IDs.
        https://www.proteomaps.net

        Parameters
        ----------
        type: str ('genes' or 'isoforms')
            Default: 'genes'
        output_directory: str
            Path to location, where csvs should be stored.
            Default: "" --> current directory is used
        return_result : bool
            Whether result should be returned. If false nothing is returned.
            Default: False
        Returns
        -------
        Proteo map text file
        """
        if output_directory =="":
            output_directory_prefix=""
        else:
            if output_directory.endswith("/"):
                output_directory_prefix=output_directory
            else:
                output_directory_prefix=str(output_directory+"/")

        if type == 'isoforms':
            IDs = list(self.ProteinData.Elements.keys())
            for run in self.ProteinData.Elements[list(self.ProteinData.Elements.keys())[0]]:
                Values = [protein[run] for protein in list(self.ProteinData.Elements.values())]
                intermediateProteinLevels = dict(zip(IDs, Values))
                ProteinLevels = {id: val for id,
                                 val in intermediateProteinLevels.items() if val != 0.}
                if len(self.SessionName) > 0:
                    filename = output_directory_prefix+self.SessionName+'_RBA_Proteomap_isoforms_'+run+'.tsv'
                else:
                    filename = output_directory_prefix+'RBA_Proteomap_isoforms_'+run+'.tsv'
                self.proteomap = '\n'.join(['{}\t{}'.format(p, l)
                                            for p, l in ProteinLevels.items()])
                with open(filename, 'w') as fout:
                    fout.write('\n'.join(['{}\t{}'.format(p, l) for p, l in ProteinLevels.items()]))
        if type == 'genes':
            IDs = list(self.ProtoProteinData.Elements.keys())
            for run in self.ProtoProteinData.Elements[list(self.ProtoProteinData.Elements.keys())[0]]:
                Values = [protein[run] for protein in list(self.ProtoProteinData.Elements.values())]
                intermediateProteinLevels = dict(zip(IDs, Values))
                ProteinLevels = {id: val for id,
                                 val in intermediateProteinLevels.items() if val != 0.}
                if len(self.SessionName) > 0:
                    filename = output_directory_prefix+self.SessionName+'_RBA_Proteomap_genes_'+run+'.tsv'
                else:
                    filename = output_directory_prefix+'RBA_Proteomap_genes_'+run+'.tsv'
                self.proteomap = '\n'.join(['{}\t{}'.format(p, l)
                                            for p, l in ProteinLevels.items()])
                with open(filename, 'w') as fout:
                    fout.write('\n'.join(['{}\t{}'.format(p, l) for p, l in ProteinLevels.items()]))
        if return_result:
            return(self.proteomap)

def _html_style(structOriginal):
    struct = copy.deepcopy(structOriginal)
    for i in list(struct.ReactionData.Elements.keys()):
        struct.ReactionData.Elements['ID_' + i + '_Data'] = struct.ReactionData.Elements.pop(i)
    for i in list(struct.EnzymeData.Elements.keys()):
        struct.EnzymeData.Elements['ID_' + i + '_Data'] = struct.EnzymeData.Elements.pop(i)
    for i in list(struct.ProcessData.Elements.keys()):
        struct.ProcessData.Elements['ID_' + i + '_Data'] = struct.ProcessData.Elements.pop(i)
    for i in list(struct.ProteinData.Elements.keys()):
        struct.ProteinData.Elements['ID_' + i + '_Data'] = struct.ProteinData.Elements.pop(i)
    for i in list(struct.MetaboliteConstraintData.Elements.keys()):
        struct.MetaboliteConstraintData.Elements['ID_' + i +
                                                 '_Data'] = struct.MetaboliteConstraintData.Elements.pop(i)
    for i in list(struct.DensityConstraintData.Elements.keys()):
        struct.DensityConstraintData.Elements['ID_' + i +
                                              '_Data'] = struct.DensityConstraintData.Elements.pop(i)
    for i in list(struct.EnzymeConstraintData.Elements.keys()):
        struct.EnzymeConstraintData.Elements['ID_' + i +
                                             '_Data'] = struct.EnzymeConstraintData.Elements.pop(i)
    for i in list(struct.ProcessConstraintData.Elements.keys()):
        struct.ProcessConstraintData.Elements['ID_' + i +
                                              '_Data'] = struct.ProcessConstraintData.Elements.pop(i)
    Block = {'RunInfo': struct.GeneralRunInfo.jsonize(),
             'ReactionData': struct.ReactionData.jsonize(),
             'ProteinData': struct.ProteinData.jsonize(),
             'EnzymeData': struct.EnzymeData.jsonize(),
             'ProcessData': struct.ProcessData.jsonize(),
             'MetaboliteConstraintData': struct.MetaboliteConstraintData.jsonize(),
             'DensityConstraintData': struct.DensityConstraintData.jsonize(),
             'EnzymeConstraintData': struct.EnzymeConstraintData.jsonize(),
             'ProcessConstraintData': struct.ProcessConstraintData.jsonize()}
    return({'RBA_ModelData': {'SimulationData': Block}})

def _determine_investment(SimData, rxn, run):
    investment = 0
    isoReactions = SimData.StructuralInformation.ReactionInfo.Elements[rxn]['Twins']+[rxn]
    enzymesToCheck = [SimData.StructuralInformation.ReactionInfo.Elements[r]['Enzyme'] for r in isoReactions if len(
        SimData.StructuralInformation.ReactionInfo.Elements[r]['Enzyme']) > 0]
    for enzy in enzymesToCheck:
        prots = SimData.StructuralInformation.EnzymeInfo.Elements[enzy]['Subunits']
        enzymeCost = 0
        for pr in list(prots.items()):
            SF = int(pr[1])
            AAnum = SimData.StructuralInformation.ProteinInfo.Elements[pr[0]]['AAnumber']
            if numpy.isnan(AAnum):
                continue
            enzymeCost += SF*int(AAnum)
        investment += enzymeCost*SimData.EnzymeData.Elements[enzy][run]
    return(investment)
