import numpy as np
import os
import pyMSpec
def d_update(d, u):
    import collections
    #http://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
    for k, v in u.iteritems():
        if isinstance(v, collections.Mapping):
            r = d_update(d.get(k, {}), v)
            d[k] = r
        else:
            d[k] = u[k]
    return d


def get_variables(json_filename):
    import json
    config = json.loads(open(json_filename).read())
    # maintain compatibility with previous versions
    # defaults are be how everything *should* be -> update makes sure that whatever is loaded conforms to this
    compatability_defaults = {'image_generation':{'smooth': "medfilt", 'smooth_params':{"size": 3}}, "isotope_generation": {"instrument":"Instrument", 'resolving_power':"140000", "at_mz":"200"} ,}
    config = d_update(compatability_defaults,config)
    # old style config provided a single database as a string, now we accept a list of databases
    if isinstance(config['file_inputs']['database_file'], basestring):
        config['file_inputs']['database_file'] = [config['file_inputs']['database_file'],]
    return compatability_defaults


### We simulate a mass spectrum for each sum formula/adduct combination. This generates a set of isotope patterns (see http://www.mi.fu-berlin.de/wiki/pub/ABI/QuantProtP4/isotope-distribution.pdf) which can provide additional informaiton on the molecule detected. This gives us a list of m/z centres for the molecule
def calculate_isotope_patterns(sum_formulae, adduct='', instrument=[], isocalc_sig=0.01, isocalc_resolution=200000.,
                                   isocalc_do_centroid=True, charge=1,verbose=True):
    from pyMSpec.pyisocalc import pyisocalc
    ### Generate a mz list of peak centroids for each sum formula with the given adduct
    mz_list = {}
    for n, sum_formula in enumerate(sum_formulae):
        try:
            if verbose:
                print n/float(len(sum_formulae)), sum_formula, adduct
            try:
                isotope_ms = instrument.get_isotope_pattern(sum_formula+adduct, charge=charge)
                sf = pyisocalc.parseSumFormula("{}{}".format(sum_formula,adduct))
            except pyMSpec.pyisocalc.canopy.sum_formula.ParseError as e:
                print "error->", str(e), sum_formula, adduct
                continue
        except KeyError as e:
            if str(e).startswith("KeyError: "):
                print str(e)
                continue
        except ValueError as e:
            if str(e).startswith("Element not recognised"):
                print str(e)
                continue
            else:
                print sum_formula, adduct
                raise
        except pyisocalc.InvalidFormulaError as e:
            print str(e)
            continue
        #try:
            #isotope_ms = pyisocalc.complete_isodist(sf,sigma=isocalc_sig,charge=charge, pts_per_mz=isocalc_resolution)
            #isotope_ms = pyisocalc.isodist(sf, plot=False, sigma=isocalc_sig, charges=charge,
            #                           resolution=isocalc_resolution, do_centroid=isocalc_do_centroid)
        except MemoryError as e:
            #todo: print -> logging.debug
            print "Memory error: {}{}".format(sf, str(e))
            continue
        except KeyError as e:
            print "KeyError: {}".format(str(e))
            continue

        if not sum_formula in mz_list:
            mz_list[sum_formula] = {}
        mz_list[sum_formula][adduct] = isotope_ms.get_spectrum(source='centroids')
    return mz_list


def save_pattern(load_file, db_dump_folder, mz_list_tmp):
    import pickle
    if db_dump_folder != "":
        if not os.path.exists(db_dump_folder):
            os.makedirs(db_dump_folder)
        pickle.dump(mz_list_tmp, open(load_file, 'w'))


def get_instrument(instrument_class_name, resolving_power, at_mz):
    from pyMSpec import instrument
    m = getattr(instrument, instrument_class_name)
    return m(float(resolving_power), float(at_mz))

def generate_isotope_patterns(config,verbose=True):
    #todo: verbose -> logging.debug
    from pySM.parse_databases import parse_databases
    from pySM.spatial_metabolomics import get_save_isotope_patterns
    from pyMSpec import instrument
    # Extract variables from config dict
    db_filenames = config['file_inputs']['database_file']
    db_dump_folder = config['file_inputs']['database_load_folder']
    #isocalc_sig = float(config['isotope_generation']['isocalc_sig'])
    #isocalc_resolution = float(config['isotope_generation']['isocalc_resolution'])
    if len(config['isotope_generation']['charge']) > 1:
        print 'Warning: only first charge state currently accepted'
    charge = int('{}{}'.format(config['isotope_generation']['charge'][0]['polarity'],
                               config['isotope_generation']['charge'][0][
                                   'n_charges']))  # currently only supports first charge!!
    adducts = set([a['adduct'] for a in config['isotope_generation']['adducts']])
    instrument = get_instrument(config["isotope_generation"]["instrument"], config["isotope_generation"]["resolving_power"], config["isotope_generation"]["at_mz"])