# -*- coding: utf-8 -*-

"""Test functionalities of Flux Variability Analysis."""

from __future__ import absolute_import

import numpy as np
import pytest
from six import iteritems

from cobra.exceptions import Infeasible
from cobra.flux_analysis.variability import (
    find_blocked_reactions,
    find_essential_genes,
    find_essential_reactions,
    flux_variability_analysis,
)


# FVA
def test_flux_variability_benchmark(large_model, benchmark, all_solvers):
    """Benchmark FVA."""
    large_model.solver = all_solvers
    benchmark(
        flux_variability_analysis,
        large_model,
        reaction_list=large_model.reactions[1::3],
        processes=1,
    )


def test_pfba_flux_variability(model, pfba_fva_results, fva_results, all_solvers):
    """Test FVA using pFBA."""
    model.solver = all_solvers
    with pytest.warns(UserWarning):
        flux_variability_analysis(
            model, pfba_factor=0.1, reaction_list=model.reactions[1::3], processes=1
        )
    fva_out = flux_variability_analysis(
        model, pfba_factor=1.1, reaction_list=model.reactions, processes=1
    )
    fva_out.sort_index(inplace=True)
    assert np.allclose(fva_out, pfba_fva_results)
    abs_fva_out = fva_out.dropna().abs()
    abs_fva_results = fva_results.dropna().abs()
    comparison = np.isclose(abs_fva_out, abs_fva_results) | (
        abs_fva_out < abs_fva_results
    )
    assert comparison["minimum"].all()
    assert comparison["maximum"].all()


def test_loopless_pfba_fva(model):
    loop_reactions = [model.reactions.get_by_id(rid) for rid in ("FRD7", "SUCDi")]
    fva_loopless = flux_variability_analysis(
        model, pfba_factor=1.1, reaction_list=loop_reactions, loopless=True, processes=1
    )
    assert np.allclose(fva_loopless["maximum"], fva_loopless["minimum"])


def test_flux_variability(model, fva_results, all_solvers):
    """Test FVA."""
    model.solver = all_solvers
    fva_out = flux_variability_analysis(
        model, reaction_list=model.reactions, processes=1
    )
    fva_out.sort_index(inplace=True)
    assert np.allclose(fva_out, fva_results)


def test_parallel_flux_variability(model, fva_results, all_solvers):
    """Test parallel FVA."""
    model.solver = all_solvers
    fva_out = flux_variability_analysis(model, processes=2)
    fva_out.sort_index(inplace=True)
    assert np.allclose(fva_out, fva_results)


# Loopless FVA
def test_flux_variability_loopless_benchmark(model, benchmark, all_solvers):
    """Benchmark loopless FVA."""
    model.solver = all_solvers
    benchmark(
        flux_variability_analysis,
        model,
        loopless=True,
        reaction_list=model.reactions[1::3],
    )


def test_flux_variability_loopless(model, all_solvers):
    """Test loopless FVA."""
    model.solver = all_solvers
    loop_reactions = [model.reactions.get_by_id(rid) for rid in ("FRD7", "SUCDi")]
    fva_normal = flux_variability_analysis(model, reaction_list=loop_reactions)
    fva_loopless = flux_variability_analysis(
        model, reaction_list=loop_reactions, loopless=True
    )

    assert not np.allclose(fva_normal["maximum"], fva_normal["minimum"])
    assert np.allclose(fva_loopless["maximum"], fva_loopless["minimum"])


# Internals (essentiality, blocked reactions)
def test_fva_data_frame(model):
    """Test DataFrame obtained from FVA."""
    df = flux_variability_analysis(model)
    assert np.all([df.columns.values == ["minimum", "maximum"]])


def test_fva_infeasible(model):
    """Test FVA infeasibility."""
    infeasible_model = model.copy()
    infeasible_model.reactions.get_by_id("EX_glc__D_e").lower_bound = 0
    # ensure that an infeasible model does not run FVA
    with pytest.raises(Infeasible):
        flux_variability_analysis(infeasible_model)


def test_fva_minimization(model):
    """Test minimization using FVA."""
    model.objective = model.reactions.EX_glc__D_e
    model.objective_direction = "min"
    solution = flux_variability_analysis(model, fraction_of_optimum=0.95)
    assert solution.at["EX_glc__D_e", "minimum"] == -10.0
    assert solution.at["EX_glc__D_e", "maximum"] == -9.5


def test_find_blocked_reactions_solver_none(model):
    """Test find_blocked_reactions() [no specific solver]."""
    result = find_blocked_reactions(model, model.reactions[40:46])
    assert result == ["FRUpts2"]


def test_essential_genes(model):
    """Test find_essential_genes()."""
    essential_genes = {"b2779", "b1779", "b0720", "b2416", "b2926", "b1136", "b2415"}
    observed_essential_genes = {g.id for g in find_essential_genes(model)}
    assert observed_essential_genes == essential_genes


def test_essential_reactions(model):
    """Test find_blocked_reactions()."""
    essential_reactions = {
        "GLNS",
        "Biomass_Ecoli_core",
        "PIt2r",
        "GAPD",
        "ACONTb",
        "EX_nh4_e",
        "ENO",
        "EX_h_e",
        "EX_glc__D_e",
        "ICDHyr",
        "CS",
        "NH4t",
        "GLCpts",
        "PGM",
        "EX_pi_e",
        "PGK",
        "RPI",
        "ACONTa",
    }
    observed_essential_reactions = {r.id for r in find_essential_reactions(model)}
    assert observed_essential_reactions == essential_reactions


def test_find_blocked_reactions(model, all_solvers):
    """Test find_blocked_reactions()."""
    model.solver = all_solvers
    result = find_blocked_reactions(model, model.reactions[40:46])
    assert result == ["FRUpts2"]

    result = find_blocked_reactions(model, model.reactions[42:48])
    assert set(result) == {"FUMt2_2", "FRUpts2"}

    result = find_blocked_reactions(model, model.reactions[30:50], open_exchanges=True)
    assert result == []
