## Copyright (C) 2001  Glen Wilder <gwilder@best.com>

## This file is part of PyProlog.
## PyProlog 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

import sys, types, string, tempfile

import Term
from pyprolog import swipl

"""
1). load a module from filesystem.
2). retreive a predicate from a module.
3). get next solution from query.

# TDB: 
signals
exceptions
catch prolog errors
suppressing prolog console I/O
callback
prolog engine configure and query
leak testing
class module string
parsing example
threading
database use
predicate properties
operators
arithmetic
list operations
set operations
findall, etc.
maplist, apply, etc
protocol
debug

"""

class ProgramFailure(Exception): 
	pass

_term_type = type(swipl.new_term())

def use_module(module_name, as_name=None, names=()): # factory
	g = swipl.chars_to_term("use_module('%s')." % module_name)
	g.call()
	if not as_name:
		as_name = module_name
	m = swipl.new_module(swipl.new_atom(as_name))
	return Module(m)

def module_from_string(name, program): # factory
	# prolog modules defined in python strings.
	fn = tempfile.mktemp('.pl')
	f = open(fn, 'w+b')
	f.write(program)
	f.close()
	module = use_module(fn, name)
	return module

class Predicate:
	def __init__(self, pred):
		self.pred = pred
	
	def info(self):
		functor, arity, module = self.pred.predicate_info()
		term = swipl.new_term()
		term.put_atom(functor)
		return Term.Atom(term), arity, Module(module)
	
	def name_string(self):
		functor, arity, module = self.pred.predicate_info()
		r = functor.atom_nchars()[1]
		return r
	
	def module(self):
		return self.info()[2]
	
	def arity(self):
		return self.info()[1]
	
	def functor(self):
		return self.info()[0]
	
	def is_fact(self):
		return self.arity() == 0
	
	def clause_list(self):
		pass
	
	def __call__(self, *args): # run
		arguments = Term._make_prolog_args(args)
		if not self.pred.call_predicate(swipl.PL_Q_NORMAL, arguments):
			raise ProgramFailure, 'call failed'
	
	def start_query(self, *args):
		arguments = Term._make_prolog_args(args)
		module_atom = self.pred.predicate_info()[2]
		query = swipl.open_query(self.pred, arguments, swipl.PL_Q_NORMAL, module_atom)
		return Query(query)
	
	def __repr__(self):
		return 'Predicate::' + repr(self.pred)

# TBD: should I allow predicates to be constructed using control
# predicates?  The control predicates are: fail, true, cut, comma,
# semicolon, if, ifelse and not.  I am thinking about how one creates
# compound queries, rather than asserting new predicates dynmically.

class Module:
	def __init__(self, module):
		self.module = module
		
	def module_name(self):
		a = self.module.module_name()
		return a.atom_nchars()[1]
	
	def get_predicate(self, name, arity):
		p = swipl.new_predicate(name, arity, self.module_name())
		return Predicate(p)
	
	def predicate_list(self):
		pass

	def __repr__(self):
		return "Module::%s" % self.module_name()

class Query:
	def __init__(self, query):
		self.query = query
		
	def next_solution(self):
		return self.query.next_solution()
		
	def close(self):
		self.query.close_query()
		
	def cut(self):
		self.query.cut_query()
	
	def __repr__(self):
		return repr(self.query)

def _test1():
	a = Term.new_atom('a')
	a1 = Term.term_from_string('a1')
	i = Term.new_integer(42)
	f = Term.new_float(-3.14)
	s = Term.new_string("prolog in python")
	v = Term.new_variable()
	print '-' * 30
	print a.term.term_type(), a.get_value()
	print a1.term.term_type(), a1.get_value()
	print i.get_value()
	print f.get_value()
	print s.get_value()
	print v.get_value()
	print '-' * 30
	print 'atom:', Term.term_from_string('a')
	print 'integer:', Term.term_from_string('1')
	print 'float:', Term.term_from_string('3.2')
	print 'string:', Term.term_from_string('"a b c"')
	print 'variable:', Term.term_from_string('X')
	print 'compound:', Term.term_from_string('g(a, f(_X, 1), -2.1)')
	print '-' * 30
	tree = use_module('tree')
	print tree
	print tree.module_name()
	
	e__2 = tree.get_predicate('e', 2)
	print e__2.info()
	L = Term.new_variable()
	e__2(1, L)
	
	R = Term.new_variable()
	e__2(7, R)
	
	f__4 = tree.get_predicate('f', 4)
	Tree = Term.new_variable()
	f__4(L, 'm', R, Tree)
	print L, R, Tree
	
	List = Term.new_variable()
	postfix__2 = tree.get_predicate('postfix', 2)
	postfix__2(Tree, List)
	print List

def _test2():
	buf = """%  from 'Clause and Effect.'
	:- module(graph, []).
	a(g,h).
	a(g,d).
	a(e,d).
	a(h,f).
	a(e,f).
	a(a,e).
	a(a,b).
	a(b,f).
	a(b,c).
	a(f,c).

	path(X,X) :- 
		!.
	path(X,Y) :- 
		a(X,Z), path(Z,Y).
    """
	graph = module_from_string('graph', buf)
	print graph
	a__2 = graph.get_predicate('a', 2)
	print a__2
	A = Term.new_variable()
	B = Term.new_variable()
	a__2(A, B)
	print A, B

def _test3():
	buf = """%  from 'Clause and Effect.'
	:- module(graph, []).
	a(g,h).
	a(g,d).
	a(e,d).
	a(h,f).
	a(e,f).
	a(a,e).
	a(a,b).
	a(b,f).
	a(b,c).
	a(f,c).

	path(X,X).
	path(X,Y) :- 
		a(X,Z), path(Z,Y).
    """
	graph = module_from_string('graph', buf)
	path__2 = graph.get_predicate('path', 2)
	print path__2
	A = Term.new_variable()
	B = Term.new_variable()
	q = path__2.start_query('g', B)
	print q
	while q.next_solution():
		print B
	q.close()

if __name__ == '__main__':
##	_test1()
## 	_test2()
 	_test3()
		
