# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# Copyright (c) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany).
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""a PyLog based knowledge database

PyLog home: http://christophe.delord.free.fr/en/pylog/

:version: $Revision:$  
:author: Logilab

:copyright:
  2004 LOGILAB S.A. (Paris, FRANCE)
  
  2004 DoCoMo Euro-Labs GmbH (Munich, Germany)
  
:contact:
  http://www.logilab.fr/ -- mailto:contact@logilab.fr
  
  http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""

__revision__ = '$Id: PyLogDB $'

import re

from pylog import Var, compile as pylog_compile

from narval.public import AL_NS, url_to_file
from narval.interfaces.rdf import IRDFStatement, IRDFRule
from narval.elements import create_error
from narval.elements.rdf import RDFStatementElement

# TODO - stories
# o ask narval to create a foaf (using foaf-a-matic?)
#   --> http://www.ldodds.com/foaf/foaf-a-matic.html


DEFAULT_KB_FILE_URL = 'file:$NARVAL_HOME/data/kb.pl'
DEFAULT_INPUT_RDF_KB_FILE_URL = 'file:$NARVAL_HOME/data/kb_in.rdf'

FORBIDDEN_PREDICATES = ['is', 'in']

class KBError(Exception): pass

def var_repr(var):
    """use to have a nice representation for PyLog's variables"""
    return var.name
Var.__repr__ = var_repr

 
def kb_file(obj=None):
    """return the path to the knowledge file and it's encoding

    :param obj: object adaptable to IURL

    :rtype: tuple(str, str)
    """
    return url_to_file(obj, DEFAULT_KB_FILE_URL)
 

def extract_predicates(context_src):
    """extract predicate from python code generated by pylog"""
    return [class_name for index, class_name 
            in enumerate(re.split('class\ (.*)\(', context_src))
            if index % 2]

def complete(context, predicate, subject, p_object):
    """return the list of triplet matching given arguments (None may be
    used as wildcard for subject or/and object)
    """
    subject = subject or Var('X')
    p_object = p_object or Var('Y')
    try:
        query = context[predicate](subject, p_object)
    except KeyError:
        raise KBError('No such predicate %s' % predicate)
    context['query'] = query
    exec 'iter = query()' in context
    for _ in context['iter']:
        yield str(subject), predicate, str(p_object)

def add_statements(path, to_add):
    """add a list of IRDFStatement elements to the PyLOG kb file
    
    return a list of error messages if any
    """
    stream = open(path, 'a+')
    reply = []
    for stmt in to_add:
        stmt = IRDFStatement(stmt)
        if stmt.predicate in FORBIDDEN_PREDICATES:
            reply.append('"%s" is a forbidden predicate for kb' % stmt.predicate)
        else:
            stream.write('\n%s( \'%s\', \'%s\' ).\n' % (
                stmt.predicate, stmt.subject, stmt.object))
    stream.close()
    return reply


# actions definitions start here ##############################################

MOD_XML = '''<?xml version="1.0" encoding="ISO-8859-1"?>
<module name="PyLogDB" xmlns:al='%s'>''' % AL_NS


def act_add_statements(inputs):
    """add a list of `IRDFStatement` elements to the knowledge base"""
    # check the statement is not already in the kb
    # act_unify return an error if not elements have been found
    if act_unify(inputs).has_key('error'):
        path, encoding = kb_file(inputs['kb'])
        errors = add_statements(path, inputs['stmts'])
        if errors:
            raise KBError('\n'.join(errors))
    return {}

MOD_XML = MOD_XML + """
<al:action name='add-stmts' func='act_add_statements'>
  <al:description lang='en'>%s</al:description>

  <al:input id='stmts' list='yes' use='yes'>
    <al:match>IRDFStatement(elmt)</al:match>
  </al:input>
  <al:input id='kb' optional='yes'>
    <al:match>elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:kb'</al:match>
    <al:match>IURL(elmt)</al:match>
  </al:input>
</al:action>""" % act_add_statements.__doc__


def act_add_rule(inputs):
    """add an inference rule to the knowledge base"""
    # TODO find a way to test if rule exists - (then don't add it)
    path, encoding = kb_file(inputs['kb'])
    rawdata = open(path).read()
    stream = open(path, 'a+')
    rule = IRDFRule(inputs['rule'])
    stmt = rule.statement
    # FIXME: the rule formating won't handle constants properly
    lhs = '%s( %s, %s )' % (
        stmt.predicate, stmt.subject, stmt.object)
    rhs = ', '.join(['%s( %s, %s )' % (stmt.predicate, stmt.subject, stmt.object)
                     for stmt in rule.implies])
    rule_str = '\n%s :-\n\t%s.\n' % (lhs, rhs)
    if rule_str not in rawdata :
        stream.write(rule_str)
    stream.close()
    return {}

MOD_XML = MOD_XML + """
<al:action name='add-rule' func='act_add_rule'>
  <al:description lang='en'>%s</al:description>

  <al:input id='rule' use='yes'>
    <al:match>IRDFRule(elmt)</al:match>
  </al:input>
  <al:input id='kb' optional='yes'>
    <al:match>elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:kb'</al:match>
    <al:match>IURL(elmt)</al:match>
  </al:input>
</al:action>""" % act_add_rule.__doc__


def act_unify(inputs):
    """search for matching statements in the knowledge base"""
    path, encoding = kb_file(inputs['kb'])
    stream = open(path)
    context = {}
    exec "from pylog import *" in context
    exec pylog_compile(stream.read()) in context
    stream.close()
    result = []
    for stmt in inputs['stmts']:
        stmt = IRDFStatement(stmt)
        try:
            for stmt_def in complete(context,
                                     stmt.predicate, stmt.subject, stmt.object):
                result.append(RDFStatementElement(*stmt_def))
        except KBError:
            continue
    if not result:
        return {'error': create_error('no statements matching %s' % stmt, type='kb')}
    return {'stmts': result}
        
MOD_XML = MOD_XML + """
<al:action name='search-stmts' func='act_unify'>
  <al:description lang='en'>%s</al:description>

  <al:input id='stmts' use='yes' list='yes'>
    <al:match>IRDFStatement(elmt)</al:match>
  </al:input>
  <al:input id='kb' optional='yes'>
    <al:match>elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:kb'</al:match>
    <al:match>IURL(elmt)</al:match>
  </al:input>

  <al:output id='stmts' list='yes'>
    <al:match>IRDFStatement(elmt)</al:match>
  </al:output>
</al:action>""" % act_unify.__doc__


def act_export(inputs):
    """export all statements in the knowledge base"""
    path, encoding = kb_file(inputs['kb'])
    stream = open(path)
    context = {}
    context_src = pylog_compile(stream.read())
    stream.close()
    exec "from pylog import *" in context
    exec context_src in context
    result = []
    for x in extract_predicates(context_src):
        for stmt_def in complete(context, x, None, None):
            result.append(RDFStatementElement(*stmt_def))
    return {'stmts': result}
        
MOD_XML = MOD_XML + """
<al:action name='export-stmts' func='act_export'>
  <al:description lang='en'>%s</al:description>

  <al:input id='kb' optional='yes'>
    <al:match>elmt.getattr((TYPE_NS, 'name')) == 'uri:memory:kb'</al:match>
    <al:match>IURL(elmt)</al:match>
  </al:input>

  <al:output id='stmts' list='yes'>
    <al:match>IRDFStatement(elmt)</al:match>
  </al:output>
</al:action>""" % act_export.__doc__



MOD_XML +=  "</module>"
        
