#!/usr/bin/python
# -*- coding: utf-8 -*-

################################################################################
#
#   RMG - Reaction Mechanism Generator
#
#   Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the
#   RMG Team (rmg_dev@mit.edu)
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the 'Software'),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#   DEALINGS IN THE SOFTWARE.
#
################################################################################


import os.path
import logging
from copy import deepcopy
import numpy

import rmgpy.constants as constants
from rmgpy.kinetics import Arrhenius, ArrheniusEP, ThirdBody, Lindemann, Troe, \
                           PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, \
                           Chebyshev, KineticsData
from rmgpy.molecule import Molecule, Group
from rmgpy.species import Species
from rmgpy.reaction import Reaction
from rmgpy.data.base import LogicNode, DatabaseError

from .family import  KineticsFamily
from .library import LibraryReaction, KineticsLibrary
from .common import filterReactions

################################################################################

class KineticsDatabase(object):
    """
    A class for working with the RMG kinetics database.
    """

    def __init__(self):
        self.recommendedFamilies = {}
        self.families = {}
        self.libraries = {}
        self.libraryOrder = []     # a list of tuples in the format ('library_label', LibraryType),
                                   # where LibraryType is set to either 'Reaction Library' or 'Seed'.  
        self.local_context = {
            'KineticsData': KineticsData,
            'Arrhenius': Arrhenius,
            'ArrheniusEP': ArrheniusEP,
            'MultiArrhenius': MultiArrhenius,
            'MultiPDepArrhenius': MultiPDepArrhenius,
            'PDepArrhenius': PDepArrhenius,
            'Chebyshev': Chebyshev,
            'ThirdBody': ThirdBody,
            'Lindemann': Lindemann,
            'Troe': Troe,
            'R': constants.R,
        }
        self.global_context = {}

    def __reduce__(self):
        """
        A helper function used when pickling a KineticsDatabase object.
        """
        d = {
            'families': self.families,
            'libraries': self.libraries,
            'libraryOrder': self.libraryOrder,
        }
        return (KineticsDatabase, (), d)

    def __setstate__(self, d):
        """
        A helper function used when unpickling a KineticsDatabase object.
        """
        self.families = d['families']
        self.libraries = d['libraries']
        self.libraryOrder = d['libraryOrder']

    def load(self, path, families=None, libraries=None, depositories=None):
        """
        Load the kinetics database from the given `path` on disk, where `path`
        points to the top-level folder of the families database.
        """
        self.loadRecommendedFamiliesList(os.path.join(path, 'families', 'recommended.py')),
        self.loadFamilies(os.path.join(path, 'families'), families, depositories)
        self.loadLibraries(os.path.join(path, 'libraries'), libraries)

    def loadRecommendedFamiliesList(self, filepath):
        """
        Load the list of recommended families from the given file
        
        The file is usually 'kinetics/families/recommended.py'.
        This is stored as a dictionary of True or False values (checked here),
        and should contain entries for every available family (checked in loadFamilies).
        """
        try:
            global_context = {}
            global_context['__builtins__'] = None
            global_context['True'] = True
            global_context['False'] = False
            local_context = {}
            local_context['__builtins__'] = None
            f = open(filepath, 'r')
            exec f in global_context, local_context
            f.close()
            self.recommendedFamilies = local_context['recommendedFamilies']
        except:
            raise DatabaseError('Error while reading list of recommended families from {0}/recommended.py.'.format(filepath))
        for recommended in self.recommendedFamilies.values():
            if not isinstance(recommended, bool):
                raise DatabaseError("recommendedFamilies dictionary should contain only True or False values")

    def loadFamilies(self, path, families=None, depositories=None):
        """
        Load the kinetics families from the given `path` on disk, where `path`
        points to the top-level folder of the kinetics families.
        """
        
        familiesToLoad = []
        for (root, dirs, files) in os.walk(os.path.join(path)):
            if root == path:
                break  # all we wanted was the list of dirs in the base path

        if families == 'default':
            logging.info('Loading default kinetics families from {0}'.format(path))
            for d in dirs:  # load them in directory listing order, like other methods (better than a random dict order)
                try:
                    recommended = self.recommendedFamilies[d]
                except KeyError:
                    raise DatabaseError('Family {0} not found in recommendation list (probably at {1}/recommended.py)'.format(d, path))
                if recommended:
                    familiesToLoad.append(d)
            for label, value in self.recommendedFamilies.iteritems():
                if label not in dirs:
                    raise DatabaseError('Family {0} found (in {1}/recommended.py) not found on disk.'.format(label, path))

        elif families == 'all':
            # All families are loaded
            logging.info('Loading all of the kinetics families from {0}'.format(path))
            for d in dirs:
                familiesToLoad.append(d)
        elif families == 'none':
            logging.info('Not loading any of the kinetics families from {0}'.format(path))
            # Don't load any of the families
            familiesToLoad = []
        elif isinstance(families, list):
            logging.info('Loading the user-specified kinetics families from {0}'.format(path))
            # If all items in the list start with !, all families will be loaded except these
            if len(families) == 0:
                raise DatabaseError('Kinetics families should be a non-empty list, or set to `default`, `all`, or `none`.')
            elif all([label.startswith('!') for label in families]):
                for d in dirs:
                    if '!{0}'.format(d) not in families:
                        familiesToLoad.append(d)
            elif any([label.startswith('!') for label in families]):
                raise DatabaseError('Families list must either all or none have prefix "!", but not a mix.')
            else:  # only the families given will be loaded
                for d in dirs:
                    if d in families:
                        familiesToLoad.append(d)
                for label in families:
                    if label not in dirs:
                        raise DatabaseError('Family {0} not found on disk.'.format(label))
        else:
            raise DatabaseError('Kinetics families was not specified properly.  Should be set to `default`,`all`,`none`, or a list.')
        
        # Now we know what families to load, so let's load them
        self.families = {}
        for label in familiesToLoad:
            familyPath = os.path.join(path, label)
            family = KineticsFamily(label=label)
            family.load(familyPath, self.local_context, self.global_context, depositoryLabels=depositories)
            self.families[label] = family

    def loadLibraries(self, path, libraries=None):
        """
        Load the listed kinetics libraries from the given `path` on disk.
        
        Loads them all if `libraries` list is not specified or `None`.
        The `path` points to the folder of kinetics libraries in the database,
        and the libraries should be in files like :file:`<path>/<library>.py`.
        """
        self.libraries = {}
        
        if libraries is not None:
            for library_name in libraries:
                library_file = os.path.join(path, library_name,'reactions.py')
                if os.path.exists(library_file):
                    logging.info('Loading kinetics library {0} from {1}...'.format(library_name, library_file))
                    library = KineticsLibrary(label=library_name)