#!/usr/bin/env python
"""Usage: gratelpy_fragment_server mechanism_file number_of_species_in_mechanism [order of interest]"""

import sys

import networkx as nx

import os
import sys
import errno
import cPickle as pickle
import datetime
import time
import functools
import itertools as it
from multiprocessing import Pool


from gratelpy.fragments import get_valid_fragments, score_fragment, get_sensible_fragments, pretty_print
from gratelpy.subgraphs import get_all_valid_subgraphs
from gratelpy.stoich import get_graph_stoich
from gratelpy.parse_mechanism import get_network_from_mechanism
from gratelpy.gensg import gen_valid_subgraphs, gen_valid_subgraphs_mp, gen_valid_subgraphs_mps
from gratelpy.pyin import resource_path

def get_valid_frags_with_caching(base_name, G, stoich_rank):

    fn_vfrags = base_name + '.vfrags'

    try:
        valid_fragments = pickle.load(open(fn_vfrags))
    except IOError, e:
        if e.errno == errno.ENOENT:
            valid_fragments = get_valid_fragments(G, stoich_rank)
            pickle.dump(valid_fragments, open(fn_vfrags, 'wb'))
        else: raise

    return valid_fragments

# def gen_valid_subgraphs_mps_with_caching(base_name, G, valid_fragments, stoich_rank):
    
#     vsg_name = base_name + '.vsg'
    
#     try:
#         valid_subgraphs = pickle.load(open(vsg_name))
#     except IOError, e:
#         if e.errno == errno.ENOENT:
#             valid_subgraphs = gen_valid_subgraphs_mps(G, valid_fragments, stoich_rank)
#             pickle.dump(valid_subgraphs, open(vsg_name, 'wb'))
#         else: raise

#     return valid_subgraphs

def display_valid_fragments(valid_fragments):
    print 'valid fragments received from \'validate_fragments\': '+str(
        len(valid_fragments))
    for f in valid_fragments:
        print f

def score_valid_fragments_with_caching(base_name, valid_subgraphs_result):
    # valid_subgraphs_result: the list of lists (fragment, subgraph components, and valid subgraphs) generated by get_all_valid_subgraphs
    
    frag_score_name = base_name + '.fs'

    # structure of list that get_all_valid_subgraphs returns
    vs_frag_i = 0
    vs_sc_i = 1
    vs_vs_i = 2

    # structure of list that score_fragment returns
    sf_frag_i = 0
    sf_sc_i = 1
    sf_K_S_i = 2

    try:
        frag_scores_res = pickle.load(open(frag_score_name))
    except IOError, e:
        if e.errno == errno.ENOENT:
            frag_scores_res = []
            for el in valid_subgraphs_result:
                curr_res = score_fragment(el[vs_vs_i], el[vs_sc_i], el[vs_frag_i])

                frag_print = pretty_print(el[vs_vs_i])
                
                frag_scores_res.append([frag_print, curr_res[sf_sc_i], el[vs_vs_i], curr_res[sf_K_S_i]])

            pickle.dump(frag_scores_res, open(frag_score_name, 'wb'))
        else: raise

    return frag_scores_res

def get_valid_subgraphs_with_caching(base_name, G, valid_fragments, stoich_rank):

    fn_vsg = base_name + '.vsg'

    try:
        valid_subgraphs = pickle.load(open(fn_vsg))
    except IOError, e:
        if e.errno == errno.ENOENT:
            valid_subgraphs = gen_valid_subgraphs_mps(G, valid_fragments, 
                stoich_rank)
            pickle.dump(valid_subgraphs, open(fn_vsg, 'wb'))
        else: raise

    return valid_subgraphs

def main():

    try:
        mechanism_file = sys.argv[1]
        num_complexes = int(sys.argv[2])
    except IndexError, e:
        print __doc__
        sys.exit(2)

    try:
            stoich_rank_user = int(sys.argv[3])
            stoich_rank_set = True
    except IndexError, e:
            stoich_rank_set = False

    base_name, ext = os.path.splitext(os.path.basename(mechanism_file))

    alpha, beta, dict_complexes, dict_constants, dict_complexes_reverse, \
    dict_constants_reverse = get_network_from_mechanism(resource_path('', mechanism_file), 
                                                        num_complexes)

    # save dictionaries for later changes in visualization / naming of complexes and reactions
    dict_name = base_name + '.dict'
    pickle.dump({'constants_dict': dict_constants, 'complexes_dict': dict_complexes, 'constants_dict_reverse': dict_constants_reverse, 'complexes_dict_reverse': dict_complexes_reverse}, open(dict_name, 'wb'))

    # presently, the subgraph scoring function (score_subgraph in subgraphs.py) assumes that all stoichiometric coefficients equal 1!
    if max(max(alpha)) > 1 or max(max(beta)) > 1:
        raise Exception('presently, the subgraph scoring function (score_subgraph in subgraphs.py) assumes that all stoichiometric coefficients equal 1!')
    
    G, stoich, stoich_rank = get_graph_stoich(alpha, beta)
    full_stoich_rank = stoich_rank

    if stoich_rank_set:
            stoich_rank = stoich_rank_user

    # save more data about current system
    system_data = base_name + '.sys'
    pickle.dump({'alpha': alpha, 'beta': beta, 'stoich': stoich, 'graph': G}, open(system_data, 'wb'))

    print 'Rank of stoichiometry matrix:', str(full_stoich_rank)
    print 'You are looking for critical fragments of order', str(stoich_rank)

    #valid_fragments = get_valid_frags_with_caching(base_name, G, stoich_rank)
    print 'Enumerating fragments of order', str(stoich_rank), '...'

    frag_start = time.time()
    valid_fragments = get_sensible_fragments(G, stoich_rank)
    frag_duration = time.time() - frag_start

    print ('Fragment server: Number of fragments of order %d '
           'placed in queue: %d' % (stoich_rank, len(valid_fragments))) 

    #display_valid_fragments(valid_fragments)

    sg_start = time.time()
    valid_subgraphs_result = get_valid_subgraphs_with_caching(base_name, G, valid_fragments, stoich_rank)
    sg_duration = time.time() - sg_start
    
    print 'Fragment queue exhausted!'
    print ('Enumerating fragments took %.2f seconds.' % (frag_duration))
    print ('Enumerating all subgraphs for all fragments took %.2f seconds. '
           '(This includes waiting for clients!)' % (sg_duration))
    print ('Total run time %.2f seconds.' % (time.time() - frag_start))

    print ('Number of fragments with non-zero contribution to coefficient '
           'a_%d of characteristic polynomial: %d' % (stoich_rank, 
                                                      len(valid_subgraphs_result)))

    print ('Computing how many of these fragments are critical ... ')

    no_critical = 0
    for frag in valid_subgraphs_result:
        if frag[-1] < 0.:
            no_critical += 1

    assert no_critical >= 0

    print ('Your mechanism contains %d critical fragment(s) of order %d.' \
               % (no_critical,
                  stoich_rank))

    print ('\n')
    if full_stoich_rank == stoich_rank:
        print ('You tested for multistability because '
               'you looked for critical fragments of order equal '
               'to the rank of the stoichiometric matrix.')
    if full_stoich_rank > stoich_rank:
        print ('You tested for oscillations because '
               'you looked for critical fragments of order less than '
               'the rank of the stoichiometric matrix.')
    if stoich_rank < num_complexes:
        print ('You tested for patterns (Turing instability) '
               'because you looked for critical fragments of order '
               'less than the number of species in your system.')

    print ('The aforementioned number of discovered critical fragments '
           'means the following for your mechanism and the instabilities '
           'you tested for.')
    print ('\n')

    if full_stoich_rank == stoich_rank:
        if no_critical == 0:
            print ('The stoichiometric matrix of your mechanism has '
                   'rank %d and we did not discover any critical '
                   'fragments of order %d.\n'
                   'Hence your mechanism does not meet a necessary '
                   'condition for multistability.' % (full_stoich_rank,
                                                      stoich_rank))
        elif no_critical > 0:
            print ('The stoichiometric matrix of your mechanism has '
                   'rank %d and discovered %d critical fragment(s) of '
                   'order %d.\n'
                   'Hence your mechanism meets a necessary condition '
                   'for multistability.' % (full_stoich_rank,
                                            no_critical,
                                            stoich_rank))

    elif full_stoich_rank > stoich_rank:
        if no_critical == 0:
            print ('The stoichiometric matrix of your mechanism has '
                   'rank %d and we did not discover any critical '
                   'fragments of order %d.\n'
                   'With this result, we cannot exclude that your '
                   'mechanism has the potential for oscillations '
                   '(Andronov-Hopf bifurcation).' % (full_stoich_rank,
                                                     stoich_rank))
        elif no_critical > 0:
            print ('The stoichiometric matrix of your mechanism has '
                   'rank %d and we discovered %d critical fragment(s) of '
                   'order %d.\n'
                   'Hence your mechanism meets a necessary condition '
                   'for oscillations ' 
                   '(Andronov-Hopf bifurcation).' % (full_stoich_rank,
                                                     no_critical,
                                                     stoich_rank))

    if stoich_rank < num_complexes:
        if no_critical == 0:
            print ('Your mechanism has %d species '
                   'and we did not discover any critical '
                   'fragments of order %d.\n'
                   'Hence your mechanism does not meet a necessary '
                   'condition for pattern formation ' 
                   '(Turing bifurcation).' % (num_complexes,
                                              stoich_rank))
        elif no_critical > 0:
            print ('Your mechanism has %d species '
                   'and we discovered %d critical '
                   'fragment(s) of order %d.\n'
                   'Hence your mechanism meets a necessary '
                   'condition for pattern formation ' 
                   '(Turing bifurcation).' % (num_complexes,
                                              no_critical,
                                              stoich_rank))

#    frag_scores_result = score_valid_fragments_with_caching(base_name, valid_subgraphs_result)

    return alpha, beta, dict_complexes, dict_constants, dict_complexes_reverse, dict_constants_reverse

if __name__ == '__main__':
    print('\nGraTeLPy Copyright (C) 2013  Georg R Walther, Matthew Hartley.\n'
          'This program comes with ABSOLUTELY NO WARRANTY.\n'
          'This is free software, and you are welcome to redistribute it '
          'under certain conditions.\n'
          'For details visit https://github.com/gratelpy/gratelpy and '
          'read our LICENSE or contact the author at gratelpy@gmail.com.\n')

    py_v = sys.version_info
    py_v_str = '.'.join('%s' % i for i in py_v[:2])

    print('GraTeLPy Fragment Server.')
    print('Your Python version is %s.' % py_v_str)
    print('Your NetworkX version is %s.' % str(nx.__version__))
    print('')

    alpha, beta, dict_complexes, dict_constants, dict_complexes_reverse, dict_constants_reverse = main()
