# Copyright 2014 Novo Nordisk Foundation Center for Biosustainability, DTU.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, print_function

try:
    import cPickle as pickle
except ImportError:
    import pickle

import os
import optlang
from cobra.io import read_sbml_model, load_json_model

from cobra import Model
from cameo.config import solvers

import logging

logger = logging.getLogger(__name__)


def load_model(path_or_handle, solver_interface=optlang, sanitize=True):
    """Read a metabolic model .

    Parameters
    ----------
    path_or_handle : path, fhandle or name.
        One of:
            * file path of a model file;
            * file handle to a SBML or pickled model; or
            * the identifier of a model in a web database (optflux.org/models)
    solver_interface : solver_interface, optional
        E.g. optlang.glpk_interface or any other optlang interface.
    sanitize : boolean, optional
        If reaction and metabolite IDs should be sanitized (works only for SBML models).
    """
    solver_interface = solvers.get(solver_interface, solver_interface)

    if isinstance(path_or_handle, str) and not os.path.isfile(path_or_handle):
        from cameo.models.webmodels import load_webmodel
        logger.debug("Given path is not a file. Trying to load from webmodels")
        model = load_webmodel(path_or_handle, solver_interface)
    else:
        if isinstance(path_or_handle, str):
            # Open the given file
            path = path_or_handle
            handle = open(path_or_handle, 'rb')
        elif hasattr(path_or_handle, 'read'):
            # Argument is already an open file
            path = path_or_handle.name
            handle = path_or_handle
        else:
            raise ValueError('Provided argument %s has to be either a string or a file handle' % path_or_handle)
        model = _load_model_from_file(path, handle)  # Parse model from the file

    if sanitize:
        sanitize_ids(model)

    if solver_interface is not None and not isinstance(model.solver, solver_interface.Model):
        logger.debug("Changing solver interface to %s" % solver_interface)
        model.solver = solver_interface

    return model


def _load_model_from_file(path, handle):
    """Try to parse a model from a file handle using different encodings."""
    logger.debug('Reading file from %s assuming pickled model.' % path)
    try:
        model = pickle.load(handle)
    except (TypeError, pickle.UnpicklingError):
        logger.debug('Cannot unpickle %s. Assuming json model next.' % path)
        try:
            model = load_json_model(path)
        except ValueError:
            logger.debug("Cannot import %s as json model. Assuming sbml model next." % path)
            try:
                model = read_sbml_model(path)
            except AttributeError as e:
                logger.error("cobrapy doesn't raise a proper exception if a file does not contain an SBML model")
                raise e
            except Exception as e:
                logger.error(
                    "Looks like something blow up while trying to import {} as a SBML model."
                    "Try validating the model at http://sbml.org/Facilities/Validator/ to get more information.".format(
                        path))
                raise e
    return model


ID_SANITIZE_RULES_SIMPHENY = [('_DASH_', '-'), ('_FSLASH_', '/'), ('_BSLASH_', "\\"), ('_LPAREN_', '('),
                              ('_LSQBKT_', '['),
                              ('_RSQBKT_', ']'), ('_RPAREN_', ')'), ('_COMMA_', ','), ('_PERIOD_', '.'),
                              ('_APOS_', "'"),
                              ('&amp;', '&'), ('&lt;', '<'), ('&gt;', '>'), ('&quot;', '"')]

ID_SANITIZE_RULES_TAB_COMPLETION = [('_DASH_', '_dsh_'), ('_FSLASH_', '_fsh_'), ('_BSLASH_', "_bsh_"),
                                    ('_LPAREN_', '_lp_'), ('_LSQBKT_', '_lb_'),
                                    ('_RSQBKT_', '_rb_'), ('_RPAREN_', '_rp_'), ('_COMMA_', '_cm_'),
                                    ('_PERIOD_', '_prd_'), ('_APOS_', "_apo_"),
                                    ('&amp;', '_amp_'), ('&lt;', '_lt_'), ('&gt;', '_gt_'), ('&quot;', '_qot_')]


def _apply_sanitize_rules(id, rules):
    for rule in rules:
        id = id.replace(*rule)
    return id


def sanitize_ids(model):
    """Makes IDs crippled by the XML specification less annoying.

    For example, EX_glc_LPAREN_e_RPAREN_ will be converted to EX_glc_lp_e_rp_.
    Furthermore, reactions and metabolites will be equipped with a nice_id attribute
    that provides the original ID, i.e., EX_glc(d).

    Parameters
    ----------
    model : model

    Notes
    -----

    Will add a nice_id attribute.

    """

    for metabolite in model.metabolites:
        met_id = metabolite.id
        metabolite.id = _apply_sanitize_rules(met_id, ID_SANITIZE_RULES_TAB_COMPLETION)
        metabolite.nice_id = _apply_sanitize_rules(met_id, ID_SANITIZE_RULES_SIMPHENY)
        metabolite.name = _apply_sanitize_rules(metabolite.name, ID_SANITIZE_RULES_SIMPHENY)

    for reaction in model.reactions:
        if isinstance(model, Model):
            forward_variable = reaction.forward_variable
            reverse_variable = reaction.reverse_variable
        rxn_id = reaction.id
        reaction.id = _apply_sanitize_rules(rxn_id, ID_SANITIZE_RULES_TAB_COMPLETION)
        reaction.nice_id = _apply_sanitize_rules(rxn_id, ID_SANITIZE_RULES_SIMPHENY)
        if reaction.name is not None:
            reaction.name = _apply_sanitize_rules(reaction.name, ID_SANITIZE_RULES_SIMPHENY)
        if isinstance(model, Model):
            forward_variable.name = reaction.id
            if reverse_variable is not None:
                reverse_variable.name = reaction.reverse_id

    if isinstance(model, Model):
        for constraint in model.solver.constraints:
            constraint.name = _apply_sanitize_rules(constraint.name, ID_SANITIZE_RULES_TAB_COMPLETION)

    model.repair()
