import os
import pickle
from importlib import resources
import shutil

import cobra

from .introduce import introduce_metabolites
from .introduce import introduce_reactions
from .introduce import introduce_transporters
from .introduce import introduce_sinks_demands

from .annotation import translate_annotate_genes
from .annotation import set_up_groups

from .completeness import check_completeness

from .cycles import verify_egc_all

from ..runsims.simplegrowth import grow_on_media

from ..runsims.precursors import precursors_on_media

from ..runsims.biosynth import biosynthesis_on_media

from ..commons.sbmlutils import force_id_on_sbml

from ..commons.metrics import log_metrics
from ..commons.metrics import log_unbalances
from ..commons.metrics import show_contributions

from ..commons.downloads import get_databases
from ..commons.downloads import eggnog_format_expansion
from ..commons.downloads import check_taxon
from ..commons.downloads import get_eschermap

from ..commons.biomass import introduce_universal_biomass
from ..commons.biomass import adjust_biomass_precursors

from ..commons.excelhub import write_excel_model

from ..commons.escherutils import count_undrawn_rids_focus
from ..commons.escherutils import parse_zipped_escher

from ..commons.keggutils import download_keggorg

from ..commons.medium import initialize_model
from ..commons.medium import reset_growth_env

from ..commons.memoteutils import get_memote_results_dict



def main(args, logger):
    

    
    ###### PRE-PARSING
    
    # adjust out folder path                      
    while args.outdir.endswith('/'):              
        args.outdir = args.outdir[:-1]
    os.makedirs(f'{args.outdir}/', exist_ok=True)
    
    
    # prepare empty logs folder
    shutil.rmtree(f'{args.outdir}/logs', ignore_errors=True)
    os.makedirs(f'{args.outdir}/logs', exist_ok=True)
    
    
    # check compatibility of input parameters
    if args.progress==False and args.module==True: 
        logger.error(f"You cannot ask --module without --progress (see --help).")
        return 1
    if args.progress==False and args.focus!='-':
        logger.error(f"You cannot ask --focus without --progress (see --help).")
        return 1
    
    
    # check 'goodbefore' and 'onlycurator' params
    if args.goodbefore == '-' and args.onlycurator != '-':
        logger.error(f"--onlycurator must be used in conjunction with --goodbefore.")
        return 1
    if   args.goodbefore == '-': args.goodbefore = [None, None, None]
    elif len(args.goodbefore.split('-')) != 3: 
        logger.error(f"Invalid syntax detected for --goodbefore.")
        return 1
    else:
        args.goodbefore = args.goodbefore.split('-')
        if args.goodbefore[0] == 'None': args.goodbefore[0] = None
        if args.goodbefore[1] == 'None': args.goodbefore[1] = None
        if args.goodbefore[2] == 'None': args.goodbefore[2] = None
    if args.onlycurator == '-': args.onlycurator = None
    
    
    # check and extract the required 'gsrap.maps' file
    if os.path.exists(f'{args.inmaps}') == False:
        logger.error(f"File 'gsrap.maps' not found at {args.inmaps}.")
        return 1
    try: 
        with open(f'{args.inmaps}', 'rb') as f:
            inmaps = pickle.load(f)  
    except: 
        logger.error(f"Provided file {args.inmaps} has an incorrect format.")
        return 1
    idcollection_dict = inmaps['idcollection_dict']
    summary_dict = inmaps['summary_dict']
    
    
    # load internal resources
    with resources.path("gsrap.assets", f"kegg_compound_to_others.pickle") as asset_path: 
        with open(asset_path, 'rb') as handle:
            kegg_compound_to_others = pickle.load(handle)
    with resources.path("gsrap.assets", f"kegg_reaction_to_others.pickle") as asset_path: 
        with open(asset_path, 'rb') as handle:
            kegg_reaction_to_others = pickle.load(handle) 
            
                        
    # format the --eggnog param
    args.eggnog = eggnog_format_expansion(logger, args.eggnog)  # now 'args.eggnog' could still be '-'
    
    
    # check the --taxon param
    if args.taxon != '-':
        response = check_taxon(logger, args.taxon, idcollection_dict)
        if response == 1: return 1
    
    
    # get the kegg organism if requested
    if args.keggorg != '-':
        response = download_keggorg(logger, args.keggorg, args.outdir)
        if response == 1: return 1
    
    
    # download dbuni, dbexp:
    logger.info("Downloading updated gsrap assets...")
    response = get_databases(logger)
    if type(response)==int: return 1
    else: dbuni, dbexp = response
    
    
    
    ###### PARSING

    # show simple statistics of contributions
    response = show_contributions(logger, dbuni, args.goodbefore)
    if response == 1: return 1
    
    
    # create the model
    universe = cobra.Model('universe')   
    
    
    # introduce M 
    universe = introduce_metabolites(logger, dbuni, universe, idcollection_dict, kegg_compound_to_others, args.outdir, args.goodbefore[0], args.onlycurator)
    if type(universe)==int: return 1

    
    # introduce R
    universe = introduce_reactions(logger, dbuni, universe, idcollection_dict, kegg_reaction_to_others, args.outdir, args.goodbefore[1], args.onlycurator)
    if type(universe)==int: return 1


    # introduce T
    universe = introduce_transporters(logger, dbuni, universe, idcollection_dict, kegg_reaction_to_others, args.outdir, args.goodbefore[2], args.onlycurator)
    if type(universe)==int: return 1
    

    # introduce sinks / demands (exchanges where included during T)
    universe = introduce_sinks_demands(logger, universe, dbuni)
    if type(universe)==int: return 1


    # introducce universal biomass
    universe = introduce_universal_biomass(logger, universe, dbuni)
    if type(universe)==int: return 1


    # translate Gs to symbols and annotate them (EC, COG, GO, ...)
    universe = translate_annotate_genes(logger, universe, idcollection_dict)
    if type(universe)==int: return 1


    # introduce collections (groups of Rs as maps/modules)
    universe = set_up_groups(logger, universe, idcollection_dict, args.goodbefore, dbuni)
    if type(universe)==int: return 1


    # introduce hand-drwn escher map annotation
    if args.escherzip != '-':
        universe = parse_zipped_escher(logger, universe, args.escherzip, args.outdir)
        if type(universe) == int: return 1


    
    ###### POST-PARSING
    
    # log metrics
    log_metrics(logger, universe)
    
    
    # check absence of unbalancing above the threshold
    log_unbalances(logger, universe)

    
    # check universe completness
    df_C = check_completeness(logger, universe, args.progress, args.module, args.focus, args.taxon, args.eggnog, args.keggorg, idcollection_dict, summary_dict, args.outdir)
    if type(df_C)==int: return 1

    
    # avoid time-consuming activities
    if not args.justparse:
    
    
        # check erroneous EGCs
        verify_egc_all(logger, universe, args.outdir)

        
        # check growth on minmal media
        df_G = grow_on_media(logger, universe, dbexp, args.media, fva=False, universe_in_parsedb=True)
        if type(df_G)==int: return 1

    
        # check blocked biomass precursors
        cond_col_dict = adjust_biomass_precursors(logger, universe, universe, 1.0, dbuni)
        df_E = precursors_on_media(logger, universe, universe, dbuni, dbexp, args.media, cond_col_dict, args.precursors)
        if type(df_E)==int: return 1

    
        # check blocked metabolites / dead-ends
        df_S = biosynthesis_on_media(logger, universe, dbexp, args.media, args.biosynth)
        if type(df_S)==int: return 1


        # reset growth environment befor saving the model (changed during growth sims)
        reset_growth_env(universe)

        
        # initialize model
        response = initialize_model(logger, universe, dbexp, args.initialize, args.media)
        if response==1: return 1


        # compute Memote metrics
        memote_results_dict = get_memote_results_dict(logger, universe)


    # write JSON 
    logger.info("Writing universal model...")
    cobra.io.save_json_model(universe, f'{args.outdir}/universe.json')
    logger.info(f"'{args.outdir}/universe.json' created!")
    
    
    # avoid time-consuming activities
    if not args.justparse:
        
        
        # write XML
        cobra.io.write_sbml_model(universe, f'{args.outdir}/universe.xml')   # groups are saved only to SBML 
        logger.info(f"'{args.outdir}/universe.xml' created!")
        force_id_on_sbml(f'{args.outdir}/universe.xml', 'universe')   # force introduction of the 'id=""' field
        
        
        # write XLSX
        sheets_dict = write_excel_model(universe, f'{args.outdir}/universe.parsedb.xlsx', args.nofigs, memote_results_dict, df_E, None, None, df_S, df_C)  
        logger.info(f"'{args.outdir}/universe.parsedb.xlsx' created!")

    
    # check if escher map is updated:
    if args.focus != '-':
        lastmap = get_eschermap(logger, dbuni, args.focus)
        count_undrawn_rids_focus(logger, universe, lastmap, args.focus, args.outdir)
    
    
    return 0