#! /usr/bin/python
"""
Module which handles the creation of a Caravan Scenario, an object representing a 
Caravan input event. Scenario stems from the name used in the database to indicate a seismic input event.

The module uses globals.py (glb in the module) to cast and check each new value added to a Scenario, for 
which its key matches a particular parameter defined in globals.py

Moreover, a Scenario can write to a database its values (using a special function to calculate an unique hash
from its values) 

(c) 2014, GFZ Potsdam

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any later
version. For more information, see http://www.gnu.org/

"""

__author__="Riccardo Zaccarelli, PhD (<riccardo(at)gfz-potsdam.de>, <riccardo.zaccarelli(at)gmail.com>)" 
__date__ ="$Sep 26, 2014 3:18:42 PM$"


from __builtin__ import hash as builtin_hash
import hashlib
import caravan.settings.globals as glb
from gmpes.gmpes import getgmpe
import caravan.settings.globalkeys as gk
import mcerp
import caravan.parser as prs

def hash(value):
    """
        returns the hash of value BUT accepts lists (which are converted to tuples in case) and dicts
        So hash([[1,2], "a" , {}, (4,)]) works
    """
    def is_mutable(value, opt_value_type=None): #is just a hint, returns True if value is dict, list, or tuple containing (recursive search) a dict or list
        t = type(value) if opt_value_type is None else opt_value_type
        if t == list or t==dict:
            return True
        elif t == tuple:
            has_mutable_children = False
            len_ = len(value)
            i=0
            while i<len_ and not has_mutable_children:
                has_mutable_children = is_mutable(value[i])
                i+=1
            return has_mutable_children
        else:
            return False
        
    def convert(value):
        t = type(value)
        if t == list or (t == tuple and is_mutable(value, t)):
            len_ = len(value)
            k = [None]*len_ #initialize a list
            for i in xrange(len_):
                k[i] = convert(value[i])
            return tuple(k)
        elif t == dict:
            k = [None]*2*len(value) #initialize a list
            i=0
            for key in value:
                k[i] = convert(key)
                k[i+1] = convert(value[key])
                i+=2
            return tuple(k)
        return value
    
    return builtin_hash(convert(value))
 
_scenario_name = 'scenario_name' #FIXME: is public. Good choice?

def scenario_hash(dict_):
    """
        Returns the hash of dict_ (a dict) according to s=glb.scenario_db_cols
        Builds a list L of N None values, where N = len(s). Then, for any key of dict_ 
        which is found in s, takes the relative index and puts L[index] = dict_[key]
        Returns the hash of L (as tuple, which is hashable)
    """
    params = glb.params
    
    dbindices = glb.scenario_db_cols
        
    dblen = len(dbindices)
    dbarray = [None]*dblen
    
    for k in dict_:
        if not k in params:
            continue
        param = params[k]
        if _scenario_name in param:
            column_name = param[_scenario_name]
            if column_name in dbindices:
                col_index = dbindices[column_name]
                dbarray[col_index] = dict_[k]
    
    return hash(dbarray)

def val_sc(pkey, p, value):
    """
        Returns the scenario value of the value argument, already casted according to p=glb.params[pkey]
    """
    
    if pkey == gk.IPE:
        return glb.def_gmpes[value]
    
    if pkey == gk.SOF:
        return glb.sof[value]
    
    isdistrib = 'distrib' in p
    if isdistrib:
        dist_name = p['distrib'].lower().title()
        if not prs.isscalar(value): #note that UncertainFunction is NOT scalar, 
            #but here we should have only numbers or array of numbers (according to params in glb)
            
            #try build a distribution with the given value(s)
            return mcerp.__dict__[dist_name](*value)
        
    return value

def val_db(pkey, p, value):
    """
        Returns the scenario value of the value argument, already casted according to p=glb.params[pkey]
    """