import warnings
import logging

from cobra.util.solver import linear_reaction_coefficients



def get_objectives(model):
    
    
    objs = list(linear_reaction_coefficients(model).keys())
    obj_ids = [obj.id for obj in objs]
    return obj_ids



def get_optthr():
    
    return 0.001   # optimization threshold



def fba_no_warnings(model): 
    
    # Ignore eventual "UserWarning: Solver status is 'infeasible'."
    with warnings.catch_warnings():  # temporarily suppress warnings for this block
        warnings.simplefilter("ignore")  # ignore all warnings
        
        # disable warnings
        cobra_logger = logging.getLogger("cobra.util.solver")
        old_level = cobra_logger.level
        cobra_logger.setLevel(logging.ERROR)   

        # perform FBA: 
        res = model.optimize()
        obj_value = res.objective_value
        status = res.status

        # restore original behaviour: 
        cobra_logger.setLevel(old_level)   

        return res, obj_value, status
    
    
    
def verify_growth(model, boolean=True):
            
    
    res, obj_value, status = fba_no_warnings(model)
    if boolean:
        if obj_value < get_optthr() or status=='infeasible':
            return False
        else: return True
    else:
        if status =='infeasible':
            return 'infeasible'
        elif obj_value < get_optthr():
            return 0.0
        else:
            return round(obj_value, 3)
        
        

def sensitivity_analysis(model, scaled=False, top=3, mid=None):
    """Perform a sensitivity analysis (or reduced costs analysis) focused on the EX_change reaction.
    
    It is based on the current model's objective. The returned dictionary is sorted from most negative to most positive values.
    
    Args:
        model (cobra.Model): target model.
        scaled (bool): whether to scale to the current objective value.
        top (int): get just the first and last `top` EX_change reactions. If ``None``, all EX_change reactions will be returned.
        mid (str): instead of optimizing for the current objective reaction, do the analysis on the biosynthesis of a specific metabolite having ID `mid`. If `None` it will be ignored.
    
    Returns:
        dict: reduced costs keyd by EX_change reaction ID. 
    """
    
    
    # temporary chenges:
    with model:
        
      
        # focus on a specific metbaolite
        if mid != None: 
            model.objective = add_demand(model, mid)


        res = model.optimize()
        obj = res.objective_value
        flx = res.fluxes.to_dict()
        rcs = res.reduced_costs.to_dict()


        # manage 0 flux exception:
        if obj == 0 and scaled == True:
            raise Exception("Cannot scale reduced costs id the objective value is 0")


        # get the reduced costs of the EXR only:
        rcsex = {} 
        for key in rcs:
            if key.startswith("EX_"):
                if not scaled : rcsex[key] = rcs[key]
                else: rcsex[key] = rcs[key] * flx[key] / obj


        # get the most impactful (lowest and highest)
        rcsex = sorted(rcsex.items(), key=lambda x: x[1], reverse=True)
        rcsex = {i[0]: i[1] for i in rcsex}  # convert list to dictionary
    
        
        # get only the top N and bottom N exchanges
        if top != None: 
            rcsex_filt = {}
            for i in range(top):
                rcsex_filt[ list(rcsex.keys())[i] ] = list(rcsex.values())[i]
            for i in range(top):
                rcsex_filt[ list(rcsex.keys())[-top +i] ] = list(rcsex.values())[-top +i]
            rcsex = rcsex_filt
        
        return rcsex
        
        
        






