package ws.prova.calc;

/*
 * Copyright (C) 2002-2007 <a href="mailto:a.kozlenkov@city.ac.uk">Alex Kozlenkov</a>
 *
 * 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 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 org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;
import org.mandarax.kernel.*;
import org.mandarax.util.logging.LogCategories;
import ws.prova.Calc;
import ws.prova.RList;
import ws.prova.RListUtils;
import ws.prova.parser.ParsingException;
import ws.prova.reference.ExceptionHandler;
import ws.prova.util.DefaultLogicFactorySupport;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;


/**
 * Prova Java invocation
 * <p>Title: Prova</p>
 * <p>Description: Prova Java invocation</p>
 * <p>Copyright: Copyright (C) 2002-2007</p>
 * <p>Company: City University, London, United Kingdom</p>
 *
 * @author Alex Kozlenkov
 * @version 2.0 <25 March 2007>
 */

public class CalcClauseSet extends AbstractPropertiesSupport implements ClauseSet, LogCategories {

	private CalcPredicate predicate = null;

	/**
	 * Constructor.
	 */
	public CalcClauseSet() {
		super();
	}

	/**
	 * Add a clause set listener.
	 *
	 * @param l the listener
	 */
	public void addClauseSetChangeListener(ClauseSetChangeListener l) {
	}

	/**
	 * Should not be called
	 *
	 * @return
	 * @throws ClauseSetException
	 */
	public org.mandarax.util.ClauseIterator clauses() throws ClauseSetException {
		throw new ClauseSetException("This method should not have been called");
	}

	/**
	 * Iterate over all clauses.
	 *
	 * @return a clause iterator
	 */
	public org.mandarax.util.ClauseIterator clauses(Clause goal, Object additionalParameter) throws ParsingException {
        ExceptionHandler exhandler = (ExceptionHandler) additionalParameter;
        Fact f = (Fact) goal.getNegativeLiterals().get(0);        
        CalcPredicate predicate = (CalcPredicate) f.getPredicate();
        ComplexTerm ct = (ComplexTerm) f.getTerms()[0];
        List list = new ArrayList();
        List head = new ArrayList();
        Term rest = RListUtils.rlist2List(ct, list, 0);        
        Object target = list.get(0);
        Class cl = null;
        head.add(target);
        if (predicate.getName().equals("pcalc")) {
            // Non-deterministic instance predicate
            cl = target.getClass();
        } else {
            // Non-deterministic static predicate
            String var = (String) target;
            cl = ws.prova.Calc.findClass(var);
            target = null;
        }
        list.remove(0);
        Object omethod = list.get(0);
        if (!(omethod instanceof String)) {
            list.remove(0);
            int arity = list.size();
            Object[] tuple = list.toArray();
            List results = new ArrayList();
            try {
                if (processException(exhandler, new InferenceException("Method name is a free variable."), goal, "clauses"))
                    results.add(tuple);
                return new CalcClauseIterator(f, predicate, arity, head, tuple, results);
            } catch (ParsingException e) {
                // Cannot really happen
            }
        }
        String method = (String) omethod;
        head.add(method);
        list.remove(0);
        // Whether it is a non-deterministic call while being a non-functional call
        boolean anotherAttempt = false;
        while (true) {
            if (anotherAttempt) {
//            System.out.println("******************************"+method);
                Object[] tuple = list.toArray();
                Object ret = null;
                int arity = list.size();
                Object[] data = new Object[arity];
                Class[] par = new Class[arity];
                int i = 0;
                for (ListIterator li = list.listIterator(); li.hasNext(); i++) {
                    Object o = li.next();
                    data[i] = o;
                    par[i] = o.getClass();
                    if (par[i] == Double.class) {
                        par[i] = double.class;
                    } else if (par[i] == Boolean.class) {
                        par[i] = boolean.class;
                    } else if (par[i] == Integer.class) {
                        par[i] = int.class;
                    } else if (o instanceof List) {
                        par[i] = List.class;
                    } else if (o instanceof ComplexTerm) {
                        par[i] = Object.class;
                    }
                }
                List results = new ArrayList();
                try {
                    ret = cl.getMethod(method, par).invoke(target, data);
                } catch (InvocationTargetException ex) {
                    try {
                        if (processException(exhandler, ex, goal, "clauses"))
                            results.add(tuple);
                        return new CalcClauseIterator(f, predicate, arity, head, tuple, results);
                    } catch (ParsingException pex) {
                    }
                } catch (Exception ex) {
                    // To speed up we could use caching
                    String clname = cl.getName();
                    if (clname.endsWith("$Itr") || clname.endsWith("Iterator")) {
                        // !!!!!!!!!!!!!! A horrible workaround the current reflection bug in Java
                        cl = Iterator.class;
                    }
                    Method[] ms = cl.getMethods();
                    for (int k = 0; k < ms.length; k++) {
                        if (ms[k].getName().equals(method)) {
                            Class[] par2 = ms[k].getParameterTypes();
                            if (par2.length != arity) {
                                continue;
                            }
                            // Name and number of parameters is the same
                            boolean matching = true;
                            for (int m = 0; m < arity; m++) {
                                if (data[m] instanceof Number && par2[m].isPrimitive()) {
                                    data[m] = adaptNumber(par2[m], data[m]);
                                    if (data[m] != null)
                                        continue;
                                    matching = false;
                                    break;
                                } else if (java.util.Collection.class.isAssignableFrom(par2[m]) && data[m] instanceof ComplexTerm) {
                                    List l = new ArrayList();
                                    RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
                                    data[m] = l;
                                } else if (!par2[m].isAssignableFrom(data[m].getClass()) && !par2[m].isAssignableFrom(par[m])) {
                                    matching = false;
                                    break;
                                }
                            }
                            if (matching) {
                                try {
                                    ret = ms[k].invoke(target, data);
                                    if (!(ret instanceof Boolean) || ((Boolean) ret).equals(Boolean.TRUE)) {
                                        results.add(tuple);
                                    }
                                    return new CalcClauseIterator(f, predicate, arity, head, tuple, results);
                                } catch (InvocationTargetException ex2) {
                                    try {
                                        if (processException(exhandler, ex2, goal, "clauses"))
                                            results.add(tuple);
                                    } catch (ParsingException pex) {
                                    }
                                    return new CalcClauseIterator(f, predicate, arity, head, tuple, results);
                                } catch (Exception ex2) {
                                    break;
                                }
                            }
                        }
                    }
                    return new CalcClauseIterator(f, predicate, arity + 1, head, tuple, results);
                }
                if (!(ret instanceof Boolean) || ((Boolean) ret).equals(Boolean.TRUE)) {
                    results.add(tuple);
                }
                return new CalcClauseIterator(f, predicate, arity, head, tuple, results);
            }
            int arity = list.size();
            Object[] data = null;
            if (method.equals("rcvMsg") || method.equals("rcvMult") || method.equals("rcvMsgP")) {
                Class[] par = new Class[4];
                data = new Object[4];
                data[0] = list.toArray();
                par[0] = Object[].class;
                data[1] = new ArrayList();
                par[1] = List.class;
                data[2] = rest;
                par[2] = Term.class;
                data[3] = goal;
                par[3] = Clause.class;
                try {
                    Method m = cl.getMethod(method, par);
                    Class rettype = m.getReturnType();
                    if (rettype.getName().equals("Boolean") || rettype == boolean.class) {
                        Boolean rc = (Boolean) m.invoke(target, data);
                    } else {
                        Object rc = (Object) m.invoke(target, data);
                    }
                } catch (Exception ex) {
                    anotherAttempt = true;
                    continue;
                }
            } else if (method.equals("on_exception") || method.equals("exception")) {
                Class[] par = new Class[3];
                data = new Object[3];
                data[0] = list.toArray();
                par[0] = Object[].class;
                data[1] = new ArrayList();
                par[1] = List.class;
                data[2] = exhandler;
                par[2] = ExceptionHandler.class;
                try {
                    Method m = cl.getMethod(method, par);
                    Class rettype = m.getReturnType();
                    if (rettype.getName().equals("Boolean") || rettype == boolean.class) {
                        Boolean rc = (Boolean) m.invoke(target, data);
                    } else {
                        Object rc = (Object) m.invoke(target, data);
                    }
                } catch (Exception ex) {
                    anotherAttempt = true;
                    continue;
                }
            } else {
                data = new Object[2];
                Class[] par = new Class[2];
                data[0] = list.toArray();
                par[0] = Object[].class;
                data[1] = new ArrayList();
                par[1] = List.class;
                try {
                    if (method.equals("assert")) {
                        // A fix around the reserved name "assert" in Java 1.4
                        //    coinciding with the Prolog and Prova "assert".
                        method = "prova_assert";
                    }
                    if (method.equals("sparql_select")) {                                                 
                        if (rest.containsVariables()) {
                        	Object[] inputs = (Object[]) data[0];
                        	Object[] inputs2 = new Object[inputs.length+1];                        
	                        for(int i=0; i<inputs.length; i++ )                         	
	                            inputs2[i] = inputs[i];
	                        inputs2[inputs.length] = rest;
	                        data[0] = inputs2;
	                        arity+=1;
                        } 	                    
                    }
                    Method m = cl.getMethod(method, par);
                    Class rettype = m.getReturnType();
                    if (rettype.getName().equals("Boolean") || rettype == boolean.class) {
                        Boolean rc = (Boolean) m.invoke(target, data);
                    } else {
                        Object rc = (Object) m.invoke(target, data);
                    }
                } catch (InvocationTargetException ex) {
                    try {
                        if (processException(exhandler, ex, goal, "clauses"))
                            ((List) data[1]).add(data[0]);
                        return new CalcClauseIterator(f, predicate, arity, head, (Object[]) data[0], (List) data[1]);
                    } catch (ParsingException pex) {
                        throw pex;
                    }
                } catch (Exception ex) {
                    anotherAttempt = true;
                    continue;
                }
            }
            return new CalcClauseIterator(f, predicate, arity, head, (Object[]) data[0], (List) data[1]);
        }
    }

	/**
	 * Process an <code>InvocationTargetException</code> by inserting the matching handler predicate call
	 * after the current goal. Return false if there is no matching handler.
	 * Pre-condition: <code>ex instanceof InvocationTargetException</code>
	 *
	 * @param exhandler collection of handlers and the current exception
	 * @param ex
	 * @param goal
	 * @param s
	 * @return <code>true</code> if the matching handler was found and inserted, <code>false</code>, otherwise.
	 */
	protected static boolean processException(ExceptionHandler exhandler, Exception ex, Clause goal, String s) throws ParsingException {
		if (exhandler == null || exhandler.isEmpty()) {
			// There are no handlers registered
			if (ex instanceof InvocationTargetException) {
				Throwable extarget = ((InvocationTargetException) ex).getCause();
				if (extarget instanceof ParsingException)
					throw(ParsingException) extarget;
			}
			return false;
		}
		Throwable extarget;
		// (1) the real exception inside the method OR an invocation problem
		if (ex instanceof InvocationTargetException) {
			extarget = ((InvocationTargetException) ex).getCause();
		} else {
			extarget = ex;
		}
		// (2) set it as the last exception.
		exhandler.setException(extarget);
		// (3) find the predicate handler appropriate for this exception
		ComplexTerm ct = exhandler.handler(extarget);
		if (ct == null) {
			// There is no matching handler for this exception
			if (extarget instanceof ParsingException)
				throw(ParsingException) extarget;
			return false;
		}
		// (4) insert the handler predicate call as a Prerequisite
		if (s.equals("")/*((Fact)goal.getNegativeLiterals().get(0)).getPredicate() instanceof CalcPredicate*/) {
			// It is a Java call from outside of the main loop in the inference engine
//			goal.getNegativeLiterals().remove(0);
			goal.getNegativeLiterals().set(0, exhandler.prova().ct2prereq(ct));
		} else
			goal.getNegativeLiterals().add(1, exhandler.prova().ct2prereq(ct));
		return true;
	}

	/**
	 * Process any exceptionin a Java call by inserting the matching handler predicate call
	 * after the current goal. Return false if there is no matching handler.
	 * Insert a bogus <code>Fact</code> as the first goal because in Java calls in the resolution engine,
	 * the goal is consumed when the call is complete.
	 *
	 * @param exhandler
	 * @param ex
	 * @param goal
	 * @param s
	 * @return
	 * @throws ParsingException
	 */
	protected static boolean processExceptionInJavaCall(ExceptionHandler exhandler, Exception ex, Clause goal, String s) throws ParsingException {
		boolean rc = processException(exhandler, ex, goal, s);
		goal.getNegativeLiterals().add(0, DefaultLogicFactorySupport.cut());
		return rc;
	}

	public static Term array2rlist(Object[] a) {
		Term ct = (Term) RListUtils.rlist("");
		for (int i = a.length; i-- > 0;) {
			ct = DefaultLogicFactorySupport.cplx(ws.prova.reagent.flist, ct, DefaultLogicFactorySupport.cons(a[i], Object.class));
		}
		return ct;

	}

	/**
	 * Invoke a boolean fully instatiated deterministic Java call with minimal overhead.
	 *
	 * @return true if the test succeeds
	 */
	public static Clause functionalGoalProcessing(ExceptionHandler exhandler, Prerequisite f, Clause goal, UnificationAlgorithm ua) {
		CalcPredicate predicate = (CalcPredicate) f.getPredicate();
		ComplexTerm ct = (ComplexTerm) f.getTerms()[0];
		List list = new ArrayList();
		RListUtils.rlist2List(ct, list, 0);
		Object target = list.get(0);
		Method cachedMethod = (Method) predicate.getMethod();

		if (cachedMethod != null) {
			Object ret0 = list.get(2);
			Object[] data = (list.size() > 3) ? list.subList(3, list.size()).toArray() : new Object[]{};
			// RA 20060718: move this below to the stage when it is confirmed we could use the cached method
//			if (Modifier.isStatic(cachedMethod.getModifiers())) {
//				target = null;
//			}
			for (int m = 0; m < data.length; m++) {
				if (data[m] instanceof Number && cachedMethod.getParameterTypes()[m].isPrimitive()) {
					data[m] = adaptNumber(cachedMethod.getParameterTypes()[m], data[m]);
				} else if (java.util.Collection.class.isAssignableFrom(cachedMethod.getParameterTypes()[m]) && data[m] instanceof ComplexTerm) {
					List l = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
					data[m] = l;
				} else if (Object[].class.isAssignableFrom(cachedMethod.getParameterTypes()[m]) && data[m] instanceof ComplexTerm) {
					List l = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
					Object array = java.lang.reflect.Array.newInstance(cachedMethod.getParameterTypes()[m].getComponentType(), l.size());
					int n = 0;
					try {
						for (Iterator it = l.iterator(); it.hasNext(); n++) {
							Object o = it.next();
							java.lang.reflect.Array.set(array, n, o);
						}
					} catch (Exception exx) {
						cachedMethod = null;
						// Try again to find the correct method
					}
					data[m] = array;
				// RA 20060718
				} else if (!cachedMethod.getParameterTypes()[m].isAssignableFrom(data[m].getClass())) {
					cachedMethod = null;
					break;
				// end RA 20060718
				}
			}
			if (cachedMethod != null) {
				// RA 20060718 
				if (Modifier.isStatic(cachedMethod.getModifiers())) {
					target = null;
				}
				// end RA 20060718
				try {
					Object ret = cachedMethod.invoke(target, data);
					goal = unifyResult(ua, goal, ret, ret0);
					return goal;
				} catch (InvocationTargetException ex) {
					try {
						return processException(exhandler, ex, goal, "") ? goal : null;
					} catch (ParsingException pex) {
					}
				} catch (Exception ex) {
					// This can happen if the target object is different from
					//    the one cached during the previous invokation at this line
					//    in the rule base.
					// In this case, we just proceed to the non-cached processing below.
				}
			}
		}

		Class cl = target.getClass();
		if (target instanceof Class) {
			cl = (Class) target;
		} else if (predicate.getName().equals("fcalc")) {
			// Functional instance predicate
			cl = target.getClass();
		} else {
			// Functional static predicate
			String var = (String) target;
			cl = ws.prova.Calc.findClass(var);
			target = null;
		}
		list.remove(0);
		Object omethod = list.get(0);
		if (!(omethod instanceof String)) {
			try {
				return processException(exhandler, new InferenceException("Method name is a free variable."), goal, "") ? goal : null;
			} catch (ParsingException pex) {
			}
		}
		String method = (String) omethod;
		list.remove(0);

//		Object[] tuple = list.toArray();
		Object ret = null;
		Object ret0 = list.remove(0);
		int arity = list.size();
		Object[] data = new Object[arity];
		Class[] par = new Class[arity];
		int i = 0;
		for (ListIterator li = list.listIterator(); li.hasNext(); i++) {
			Object o = li.next();
			data[i] = o;
			par[i] = o.getClass();
			if (par[i] == Double.class) {
				par[i] = double.class;
			} else if (par[i] == Boolean.class) {
				par[i] = boolean.class;
			} else if (par[i] == Integer.class) {
				par[i] = int.class;
			} else if (o instanceof List) {
				par[i] = List.class;
			} else if (o instanceof ComplexTerm) {
				par[i] = Object.class;
			}
		}
//		List results = new ArrayList();
		try {
			Method m = cl.getMethod(method, par);
			ret = m.invoke(target, data);
			goal = unifyResult(ua, goal, ret, ret0);
			// RA 20060718
			Class[] ts = m.getParameterTypes();
			int tsz = ts.length;
			if (tsz > 0) {
				Method[] ms = cl.getMethods();
				for (int k=0, ksz=ms.length; m != null && k<ksz; k++) {
					Class[] kts = ms[k].getParameterTypes();
					if (ms[k].getName().equals(method) && kts.length == tsz)
					for (int t=0; t<tsz; t++)
					if (kts[t] != ts[t] && (kts[t].isAssignableFrom(ts[t]) || ts[t].isAssignableFrom(kts[t]))) {
						m = null;
						break;
					}
				}
			}
			if (m != null)
			// end RA 20060718
				predicate.setMethod(m);
			return goal;
		} catch (InvocationTargetException ex) {
			try {
				return processException(exhandler, ex, goal, "") ? goal : null;
			} catch (ParsingException pex) {
			}
		} catch (Exception ex) {
			// To speed up we could use caching
			Method[] ms = cl.getMethods();
			for (int k = 0; k < ms.length; k++) {
				if (ms[k].getName().equals(method)) {
					Class[] par2 = ms[k].getParameterTypes();
					if (par2.length != arity) {
						continue;
					}
					// Name and number of parameters is the same
					boolean matching = true;
					for (int m = 0; m < arity; m++) {
						if (data[m] instanceof Number && par2[m].isPrimitive()) {
							data[m] = adaptNumber(par2[m], data[m]);
							if (data[m] != null)
								continue;
							matching = false;
							break;
						} else if (java.util.Collection.class.isAssignableFrom(par2[m]) && data[m] instanceof ComplexTerm) {
							List l = new ArrayList();
							RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
							data[m] = l;
						} else if (Object[].class.isAssignableFrom(par2[m]) && data[m] instanceof ComplexTerm) {
							List l = new ArrayList();
							RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
							Object array = java.lang.reflect.Array.newInstance(par2[m].getComponentType(), l.size());
							int n = 0;
							try {
								for (Iterator it = l.iterator(); it.hasNext(); n++) {
									Object o = it.next();
									java.lang.reflect.Array.set(array, n, o);
								}
							} catch (Exception exx) {
								matching = false;
								break;
							}
							data[m] = array;
						} else if (!par2[m].isAssignableFrom(data[m].getClass()) && !par2[m].isAssignableFrom(par[m])) {
							matching = false;
							break;
						}
					}
					if (matching) {
						try {
							Method publicMethod = getPublicMethod(cl, ms[k].getName(), ms[k].getParameterTypes());
							ret = publicMethod.invoke(target, data);
							goal = unifyResult(ua, goal, ret, ret0);
							predicate.setMethod(ms[k]);
							return goal;
						} catch (InvocationTargetException ex2) {
							try {
								return processException(exhandler, ex2, goal, "") ? goal : null;
							} catch (ParsingException pex) {
							}
						} catch (Exception ex2) {
							try {
								return processException(exhandler, new CalcException(ex2), goal, "") ? goal : null;
							} catch (ParsingException pex) {
							}
						}
					}
				}
			}
			try {
				return processException(exhandler, new CalcException(ex), goal, "") ? goal : null;
			} catch (ParsingException pex) {
			}
		}
		return null;
	}

	private static Method getPublicMethod(Class clazz, String methodName, Class parameterTypes[])
			throws SecurityException, NoSuchMethodException {

		Method declaredMethod = clazz.getMethod(methodName, parameterTypes);

		if (Modifier.isPublic(declaredMethod.getDeclaringClass().getModifiers())) {
			return declaredMethod;
		}

		List candidateClasses = new ArrayList();
		candidateClasses.addAll(ClassUtils.getAllInterfaces(clazz));
		candidateClasses.addAll(ClassUtils.getAllSuperclasses(clazz));

		for (Iterator iter = candidateClasses.iterator(); iter.hasNext();) {
			Class candidateClass = (Class) iter.next();
			if (!Modifier.isPublic(candidateClass.getModifiers())) {
				continue;
			}
			Method candidateMethod;
			try {
				candidateMethod = candidateClass.getMethod(methodName, parameterTypes);
			} catch (NoSuchMethodException e) {
				continue;
			}
			if (Modifier.isPublic(candidateMethod.getDeclaringClass().getModifiers())) {
				return candidateMethod;
			}
		}

		String message = "Can't find an public method for " + methodName + " " + ArrayUtils.toString(parameterTypes);
		throw new NoSuchMethodException(message);
	}

	/**
	 * Unify the result of a functional Java call with the supplied Object or Term.
	 * If either ret or ret0 are Object, they are first converted to Term.
	 *
	 * @param ua   UnificationAlgorithm
	 * @param goal outstanding goals
	 * @param ret  object returned from the call
	 * @param ret0 the supplied Object or Term.
	 * @return Clause
	 */
	private static Clause unifyResult(UnificationAlgorithm ua, Clause goal, Object ret, Object ret0) {
		if (ret == null) {
			return null;
		}
		if (ret instanceof Object[]) {
			// The method returns Object[]
			if( (ret0 instanceof ComplexTerm)
					|| (ret0 instanceof VariableTerm && RList.class.isAssignableFrom( ((VariableTerm) ret0).getType() )) ) {
				// Since version 1.9
				// A variable of type ws.prova.RList on the LHS forces the return of a Prova list
				ret = CalcClauseSet.array2rlist((Object[]) ret);
			} else {
				// Object[] returned from a functional Java call is converted to a List
				final Object[] objects = ((Object[]) ret);
				List list = new ArrayList(objects.length);
				for (int i = 0; i < objects.length; i++)
					list.add(objects[i]);
				ret = DefaultLogicFactorySupport.cons(list, List.class);
			}
		} else if( ret instanceof List ) {
			// The method returns a Java List
			if( (ret0 instanceof ComplexTerm)
					|| (ret0 instanceof VariableTerm && RList.class.isAssignableFrom( ((VariableTerm) ret0).getType() )) ) {
				// Since version 1.9
				// A variable of type ws.prova.RList on the LHS forces the return of a Prova list
				ret = CalcClauseSet.array2rlist(((List) ret).toArray());
			} else {
				// Return a List a ConstantTerm
				ret = DefaultLogicFactorySupport.cons((List) ret, List.class);
			}
		} else if (!(ret instanceof Term)) {
			// The method returns some other Object, so wrap it in a ConstantTerm
			ret = DefaultLogicFactorySupport.cons(ret, Object.class);
		}
//		if (ret0 instanceof Object[]) {
//			ret0 = CalcClauseSet.array2rlist((Object[]) ret0);
//		} else
        if (!(ret0 instanceof Term)) {
			ret0 = DefaultLogicFactorySupport.cons(ret0, Object.class);
		}
		if (ret0 instanceof ConstantTerm && ((ConstantTerm) ret0).getObject() == null) {
			// The return is not expected at all
			goal.getNegativeLiterals().remove(0);
			((ConstantTerm) ret0).setObject(((ConstantTerm) ret).getObject());
			return goal;
		}
		Term[] oret0 = new Term[]{(Term) ret0};
		Term[] oret = new Term[]{(Term) ret};
		// Note that goal must be the first argument
		Unification u = ua.unify2(oret0, oret, true);
		if (u.equals(Unification.noUnificationPossible))
			return null;
		goal.getNegativeLiterals().remove(0);
		goal = goal.apply(u.replacements);
		return goal;
	}

	/**
	 * Invoke a boolean fully instatiated deterministic Java call with minimal overhead.
	 *
	 * @return true if the test succeeds
	 */
	public static boolean consumeGoalProcessing(ExceptionHandler exhandler, Prerequisite f, Clause goal, List rets) throws ParsingException {
		CalcPredicate predicate = (CalcPredicate) f.getPredicate();
		ComplexTerm ct = (ComplexTerm) f.getTerms()[0];
		List list = new ArrayList();
		Term rest = RListUtils.rlist2List(ct, list, 0);
		Object target = list.get(0);
		Method cachedMethod = predicate.getMethod();
		if (cachedMethod != null) {
//			System.out.println("Called again: "+cachedMethod);
			Object[] data = (list.size() > 2) ? list.subList(2, list.size()).toArray() : new Object[]{};
			if (Modifier.isStatic(cachedMethod.getModifiers())) {
				target = null;
			}
			for (int m = 0; m < data.length; m++) {
				if (data[m] instanceof Number && cachedMethod.getParameterTypes()[m].isPrimitive()) {
					data[m] = adaptNumber(cachedMethod.getParameterTypes()[m], data[m]);
				} else if (java.util.Collection.class.isAssignableFrom(cachedMethod.getParameterTypes()[m]) && data[m] instanceof ComplexTerm) {
					List l = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
					data[m] = l;
				} else if (Object[].class.isAssignableFrom(cachedMethod.getParameterTypes()[m]) && data[m] instanceof ComplexTerm) {
					List l = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) data[m], l, 0);
					Object array = java.lang.reflect.Array.newInstance(cachedMethod.getParameterTypes()[m].getComponentType(), l.size());
					int n = 0;
					try {
						for (Iterator it = l.iterator(); it.hasNext(); n++) {
							Object o = it.next();
							java.lang.reflect.Array.set(array, n, o);
						}
					} catch (Exception exx) {
						cachedMethod = null;
						// Try again to find the correct method
					}
					data[m] = array;
				}
			}
			if (cachedMethod != null) {
				try {
					Object ret = cachedMethod.invoke(target, data);
					// Return the result as the only element in 'rets'
					if (rets != null)
						rets.add(ret);
					if (ret != null) {
						Class rettype = ret.getClass();
						if (rettype == Boolean.class) {
							return ((Boolean) ret).booleanValue();
						}
					}
					return true;
				} catch (InvocationTargetException ex) {
					goal.getNegativeLiterals().add(0, new Integer(0));
					return processExceptionInJavaCall(exhandler, ex, goal, "");
				} catch (Exception ex) {
					// A different method must be used: continue to the method discovery below
				}
			}
		}
		Class cl = null;
		if (target instanceof Class) {
			cl = (Class) target;
		} else if (predicate.getName().equals("pcalc")) {
			// Non-functional instance predicate
			cl = target.getClass();
		} else {
			// Static predicate
			String var = (String) target;
			cl = ws.prova.Calc.findClass(var);
			target = null;
		}
		list.remove(0);
		String method = (String) list.get(0);
		list.remove(0);

		Object ret = null;
		int arity = list.size();
		Object[] data = new Object[2];
		Class[] par = new Class[2];
		data[0] = list.toArray();
		par[0] = Object[].class;
		data[1] = new ArrayList();
		par[1] = List.class;
		try {
			Method m = null;
			if (method.equals("rcvMsg") || method.equals("rcvMult") || method.equals("rcvMsgP")) {
				Object[] data0 = new Object[4];
				Class[] par0 = new Class[4];
				data0[0] = list.toArray();
				par0[0] = Object[].class;
				data0[1] = new ArrayList();
				par0[1] = List.class;
				data0[2] = rest;
				par0[2] = Term.class;
				data0[3] = goal;
				par0[3] = Clause.class;
				m = cl.getMethod(method, par0);
				ret = (Object) m.invoke(target, data0);
				// Return the result as the only element in 'rets'
				if (rets != null)
					rets.add(ret);
				return (((List) data0[1]).size() > 0);
			} else if (method.equals("on_exception") || method.equals("exception")) {
				Object[] data0 = new Object[3];
				Class[] par0 = new Class[3];
				data0[0] = list.toArray();
				par0[0] = Object[].class;
				data0[1] = new ArrayList();
				par0[1] = List.class;
				data0[2] = exhandler;
				par0[2] = ExceptionHandler.class;
				m = cl.getMethod(method, par0);
				ret = (Object) m.invoke(target, data0);
				// Return the result as the only element in 'rets'
				if (rets != null)
					rets.add(ret);
				return (((List) data0[1]).size() > 0);
			} else {
				if (method.equals("assert")) {
					// A fix around the reserved name "assert" in Java 1.4
					//    coinciding with the Prolog and Prova "assert".
					method = "prova_assert";
				}
				if (method.equals("sparql_select")) {
					Object[] inputs = (Object[]) data[0];
					Object[] inputs2 = new Object[inputs.length+1];
					for(int i=0; i<inputs.length; i++ )
						inputs2[i] = inputs[i];
					inputs2[inputs.length] = rest;
					data[0] = inputs2;
				}
				m = cl.getMethod(method, par);
				ret = (Object) m.invoke(target, data);
				// Return the result as the only element in 'rets'
				if (rets != null)
					rets.add(ret);
				return (((List) data[1]).size() > 0);
			}
		} catch (InvocationTargetException ex) {
			return processExceptionInJavaCall(exhandler, ex, goal, "");
		} catch (Exception ex) {
			Object[] dataa = new Object[arity];
			Class[] para = new Class[arity];
			int i = 0;
			for (ListIterator li = list.listIterator(); li.hasNext(); i++) {
				Object o = li.next();
				dataa[i] = o;
				para[i] = o.getClass();
				if (para[i] == Double.class) {
					para[i] = double.class;
				} else if (para[i] == Boolean.class) {
					para[i] = boolean.class;
				} else if (para[i] == Integer.class) {
					para[i] = int.class;
				} else if (o instanceof List) {
					para[i] = List.class;
				} else if (o instanceof ComplexTerm) {
					para[i] = Object.class;
				}
			}
			try {
				Method m = cl.getMethod(method, para);
				ret = m.invoke(target, dataa);
				// Return the result as the only element in 'rets'
				if (rets != null)
					rets.add(ret);
				predicate.setMethod(m);
			} catch (InvocationTargetException exa) {
				goal.getNegativeLiterals().add(0, new Integer(0));
				return processExceptionInJavaCall(exhandler, exa, goal, "");
			} catch (Exception exa) {
				// To speed up we could use caching
				String clname = cl.getName();
				if (clname.endsWith("$Itr") || clname.endsWith("Iterator")) {
					// !!!!!!!!!!!!!! A horrible workaround the current reflection bug in Java
					cl = Iterator.class;
				}
				Method[] ms = cl.getMethods();
				boolean methodFound = false;
				for (int k = 0; k < ms.length; k++) {
					if (ms[k].getName().equals(method)) {
						Class[] par2 = ms[k].getParameterTypes();
						if (par2.length != arity) {
							continue;
						}
						// Name and number of parameters is the same
						boolean matching = true;
						for (int m = 0; m < arity; m++) {
							if (dataa[m] instanceof Number && par2[m].isPrimitive()) {
								dataa[m] = adaptNumber(par2[m], dataa[m]);
								if (dataa[m] != null)
									continue;
								matching = false;
								break;
							} else if (java.util.Collection.class.isAssignableFrom(par2[m]) && dataa[m] instanceof ComplexTerm) {
								List l = new ArrayList();
								RListUtils.rlist2List((ComplexTerm) dataa[m], l, 0);
								dataa[m] = l;
							} else if (Object[].class.isAssignableFrom(par2[m]) && dataa[m] instanceof ComplexTerm) {
								List l = new ArrayList();
								RListUtils.rlist2List((ComplexTerm) dataa[m], l, 0);
								Object array = java.lang.reflect.Array.newInstance(par2[m].getComponentType(), l.size());
								int n = 0;
								try {
									for (Iterator it = l.iterator(); it.hasNext(); n++) {
										Object o = it.next();
										java.lang.reflect.Array.set(array, n, o);
									}
								} catch (Exception exx) {
									matching = false;
									break;
								}
								dataa[m] = array;
							} else if (!par2[m].isAssignableFrom(dataa[m].getClass()) && !par2[m].isAssignableFrom(para[m])) {
								matching = false;
								break;
							}
						}
						if (matching) {
							methodFound = true;
							try {
								ret = ms[k].invoke(target, dataa);
								// Return the result as the only element in 'rets'
								if (rets != null)
									rets.add(ret);
								predicate.setMethod(ms[k]);
								break;
							} catch (InvocationTargetException ex2) {
								goal.getNegativeLiterals().add(0, new Integer(0));
								return processExceptionInJavaCall(exhandler, ex2, goal, "");
							} catch (Exception ex2) {
								// If the name and the arity are correct and the call fails we do not
								//    explore other methods
								return false;
							}
						}
					}
				}
				// Important correction: 12/12/03
				if (!methodFound) {
					return processExceptionInJavaCall(exhandler, exa, goal, "");
				}
			}
		}
		if (ret != null) {
			Class rettype = ret.getClass();
			if (rettype == Boolean.class) {
				return ((Boolean) ret).booleanValue();
			}
		}
//		System.out.println("method"+method);
		// Method returns null but since its result is unchecked, we allow it to proceed
		return true;
	}

	/**
	 * @param type Class
	 * @param data Object
	 * @return Object
	 */
	public static Object adaptNumber(Class type, Object data) {
		if (type == int.class)
			return new Integer(((Number) data).intValue());
		else if (type == long.class)
			return new Long(((Number) data).longValue());
		else if (type == double.class)
			return new Double(((Number) data).doubleValue());
		else if (type == short.class)
			return new Short(((Number) data).shortValue());
		else if (type == float.class)
			return new Float(((Number) data).floatValue());
		else if (type == byte.class)
			return new Byte(((Number) data).byteValue());
		return null;
	}

	/**
	 * Get a key for indexing. The knowledge base is supposed to
	 * contain only clauses having the same key object. This method
	 * returns this key object. Note that this behavior is not enforced!
	 *
	 * @return the key object
	 * @see org.mandarax.kernel.Fact#getKey
	 * @see org.mandarax.kernel.Rule#getKey
	 * @see org.mandarax.kernel.KnowledgeBase
	 */
	public Object getKey() {
		return predicate;
	}

	/**
	 * Convert the object to a string.
	 *
	 * @return a string
	 */
	public String toString() {
		return predicate.toString();
	}

	/**
	 * Get the predicate.
	 *
	 * @return a predicate
	 */
	public Predicate getPredicate() {
		return predicate;
	}

	/**
	 * Set a Calc predicate.
	 *
	 * @param p a predicate
	 */
	public void setPredicate(CalcPredicate p) {
		predicate = p;
	}

	/**
	 * Remove a clause set listener.
	 *
	 * @param l the listener
	 */
	public void removeClauseSetChangeListener(ClauseSetChangeListener l) {
	}

	/**
	 * predicates
	 *
	 * @return Iterator
	 */
	public Iterator predicates() {
		return null;
	}

	/**
	 * Call a built-in constant deterministic predicate
	 *
	 * @param exhandler
	 * @param f
	 * @param goal
	 * @return return code from a built-in
	 */
	public static boolean consumeBPred(ExceptionHandler exhandler, Prerequisite f, Clause goal) throws ParsingException {
		try {
			final ComplexTerm ct = (ComplexTerm) f.getTerms()[0];
			final Calc calcInstance = (Calc) ((ConstantTerm) ct.getTerms()[1]).getObject();
			final ComplexTerm ct2 = (ComplexTerm) ct.getTerms()[0];
			final int method = ((Integer) ((ConstantTerm) ct2.getTerms()[1]).getObject()).intValue();
			boolean rc = Calc.callBPred(calcInstance, method, (ComplexTerm) ct2.getTerms()[0]);
			if (!rc)
				return false;
		} catch (Exception ex) {
			return processException(exhandler, ex, goal, "");
		}
		return true;
	}
}
