# 
#
# (C) 2011-2015 by Daniel Nyga (nyga@cs.uni-bremen.de)
# 
# 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.
from dnutils import logs

from pracmln.mln.mrfvars import BinaryVariable, FuzzyVariable, SoftMutexVariable,\
    MutexVariable


logger = logs.getlogger(__name__)


class Predicate(object):
    '''
    Represents a logical predicate and its properties.
    
    :param predname:    the name of the predicate.
    :param argdoms:     the list of domains of the predicate's arguments.
    '''

    
    def __init__(self, name, argdoms):
        self.argdoms = argdoms
        self.name = name
            
            
    def varname(self, gndatom):
        '''
        Takes an instance of a ground atom and generates the name
        of the corresponding variable.
        '''
        return str(gndatom)
    
    
    def tovariable(self, mrf, gndatom):
        '''
        Creates a new instance of an atomic ground block instance
        depending on the type of the predicate
        '''
        return BinaryVariable(mrf, name=self.varname(gndatom), predicate=self)
    
    
    def groundatoms(self, mln, domains):
        '''
        Iterates over all ground atoms that can be generated by this predicate
        given the domains and the MLN.
        
        :param domains:    dict mapping the domain names to their values.
        '''
        for gndatom in self._groundatoms(mln, domains, [], self.argdoms):
            yield gndatom
    
    
    def _groundatoms(self, mln, domains, values, argdoms):
        # if there are no more parameters to ground, we're done
        # and we cann add the ground atom to the MRF
        if not argdoms:
            yield  mln.logic.gnd_atom(self.name, values, mln)
            return
        # create ground atoms for each way of grounding the first of the 
        # remaining variables whose domains are given in domNames
        dom = domains.get(argdoms[0])
        if dom is None or len(dom) == 0:
            logger.info("Ground Atoms for predicate %s could not be generated, since the domain '%s' is empty" % (str(self), argdoms[0]))
            return
        for value in dom:
            for gndatom in self._groundatoms(mln, domains, values + [value], argdoms[1:]):
                yield gndatom
    
    
    def __eq__(self, other):
        return type(other) == type(self) and other.name == self.name and other.argdoms == self.argdoms
    
    
    def __ne__(self, other):
        return not self == other
    
    
    def __str__(self):
        return '%s(%s)' % (self.name, self.argstr())
    
    
    def __repr__(self):
        return '<Predicate: %s>' % str(self)
    
    
    def argstr(self):
        return ','.join(map(str, self.argdoms))


class FuzzyPredicate(Predicate):
    '''
    Represents a predicate whose atom can take fuzzy degrees of truth in [0,1].
    '''
    
    def __init__(self, name, argdoms):
        Predicate.__init__(self, name, argdoms)
        
        
    def __repr__(self):
        return '<FuzzyPredicate: %s>' % str(self)
    
    
    def tovariable(self, mrf, gndatom):
        return FuzzyVariable(mrf, name=self.varname(gndatom), predicate=self)
    

class FunctionalPredicate(Predicate):
    '''
    Represents a predicate declaration for a functional constraint.
 
    :param mutex:    (int) the index of the mutex argument
    
    .. seealso:: :class:`mln.base.Predicate` 
    
    '''
    
    
    def __init__(self, name, argdoms, mutex):
        Predicate.__init__(self, name, argdoms)
        self.mutex = mutex


    def varname(self, gndatom):
        nonfuncargs = [p if i != self.mutex else '_' for i, p in enumerate(gndatom.args)]
        return '%s(%s)' % (gndatom.predname, ','.join(nonfuncargs))
    

    def tovariable(self, mrf, name):
        return MutexVariable(mrf, name, self)
    
    
    def __eq__(self, other):
        return Predicate.__eq__(self, other) and self.mutex == other.mutex
        
        
    def __str__(self):
        return '%s(%s)' % (self.name, self.argstr())
    
    
    def __repr__(self):
        return '<FunctionalPredicate: %s>' % str(self)
    
    def argstr(self):
        return ','.join([arg if i != self.mutex else '%s!' % arg for i, arg in enumerate(self.argdoms)])
    

class SoftFunctionalPredicate(FunctionalPredicate):
    '''
    Represents a predicate declaration for soft function constraint.
    '''
    
    def tovariable(self, mrf, name):
        return SoftMutexVariable(mrf, name, self)


    def __str__(self):
        return '%s(%s)' % (self.name, self.argstr())
    

    def argstr(self):
        return ','.join([arg if i != self.mutex else '%s?' % arg for i, arg in enumerate(self.argdoms)])
    
    
    def __repr__(self):
        return '<SoftFunctionalPredicate: %s>' % str(self)
