JLogEngine.java

/*
 * #%L
 * prolobjectlink-jpi-jlog
 * %%
 * Copyright (C) 2019 Prolobjectlink Project
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package io.github.prolobjectlink.prolog.jlog;

import static io.github.prolobjectlink.prolog.PrologLogger.ERROR_LOADING_BUILT_INS;
import static io.github.prolobjectlink.prolog.PrologLogger.FILE_NOT_FOUND;
import static io.github.prolobjectlink.prolog.PrologLogger.IO;
import static ubc.cs.JLog.Parser.pOperatorEntry.FX;
import static ubc.cs.JLog.Parser.pOperatorEntry.FY;
import static ubc.cs.JLog.Parser.pOperatorEntry.XF;
import static ubc.cs.JLog.Parser.pOperatorEntry.XFX;
import static ubc.cs.JLog.Parser.pOperatorEntry.XFY;
import static ubc.cs.JLog.Parser.pOperatorEntry.YF;
import static ubc.cs.JLog.Parser.pOperatorEntry.YFX;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import io.github.prolobjectlink.prolog.AbstractEngine;
import io.github.prolobjectlink.prolog.Licenses;
import io.github.prolobjectlink.prolog.PrologClause;
import io.github.prolobjectlink.prolog.PrologEngine;
import io.github.prolobjectlink.prolog.PrologIndicator;
import io.github.prolobjectlink.prolog.PrologOperator;
import io.github.prolobjectlink.prolog.PrologProgram;
import io.github.prolobjectlink.prolog.PrologProvider;
import io.github.prolobjectlink.prolog.PrologQuery;
import io.github.prolobjectlink.prolog.PrologTerm;
import ubc.cs.JLog.Foundation.iNameArityStub;
import ubc.cs.JLog.Foundation.jKnowledgeBase;
import ubc.cs.JLog.Foundation.jPrologFileServices;
import ubc.cs.JLog.Foundation.jPrologServices;
import ubc.cs.JLog.Foundation.jRule;
import ubc.cs.JLog.Foundation.jRuleDefinitions;
import ubc.cs.JLog.Foundation.jUnifiedVector;
import ubc.cs.JLog.Parser.pGenericOperatorEntry;
import ubc.cs.JLog.Parser.pGenericPredicateEntry;
import ubc.cs.JLog.Parser.pOperatorEntry;
import ubc.cs.JLog.Parser.pOperatorRegistry;
import ubc.cs.JLog.Parser.pParseStream;
import ubc.cs.JLog.Parser.pPredicateOperatorEntry;
import ubc.cs.JLog.Parser.pPredicateRegistry;
import ubc.cs.JLog.Terms.iNameArity;
import ubc.cs.JLog.Terms.jBuiltinRule;
import ubc.cs.JLog.Terms.jPredicate;
import ubc.cs.JLog.Terms.jPredicateTerms;

/**
 * 
 * @author Jose Zalacain
 * @since 1.0
 */
public class JLogEngine extends AbstractEngine implements PrologEngine {

	final jPrologServices engine;
	private final jKnowledgeBase kb;
	private final pOperatorRegistry or;
	private final pPredicateRegistry pr;

	private static final String BUILT_INS = "builtins";

	private final pGenericPredicateEntry multifile = new pGenericPredicateEntry("multifile", 1, jMultifile.class);
//	private final pGenericOperatorEntry m = new pGenericOperatorEntry("multifile", type, priority, jMultifile.class);

	protected JLogEngine(PrologProvider provider) {
		super(provider);
		kb = new jKnowledgeBase();
		or = new pOperatorRegistry();
		pr = new pPredicateRegistry();
		pr.addPredicate(multifile);
		engine = new jPrologServices(kb, pr, or);
		engine.setFileServices(new jPrologFileServices());
		try {
			engine.loadLibrary(BUILT_INS);
		} catch (IOException e) {
			getLogger().error(getClass(), ERROR_LOADING_BUILT_INS, e);
		}
	}

	/**
	 * Find or Remove a give rule depending of boolean flag. If flag is true the
	 * rule will be removed. If flag is false just find the given rule. Return true
	 * if the rule was found, false otherwise.
	 * 
	 * @param rule       Rule to be found or removed
	 * @param toBeRemove Flag to indicate removal action
	 * @return true if the rule was found, false otherwise.
	 */
	private boolean clauseOrRetract(jRule rule, boolean toBeRemove) {
		String name = rule.getName();
		int arity = rule.getArity();
		jPredicate head = rule.getHead();
		jPredicateTerms body = rule.getBase();
		if (name.startsWith("'") && name.endsWith("'")) {
			name = name.substring(1, name.length() - 1);
		}
		iNameArity na = new iNameArityStub(name, arity);
		jRuleDefinitions rds = kb.getRuleDefinitionsMatch(na);
		if (rds != null && rds.size() > 0) {
			Enumeration<?> e = rds.enumRules();
			while (e.hasMoreElements()) {
				Object object = e.nextElement();
				if (object instanceof jRule) {
					jRule jRule = (jRule) object;
					jPredicate ruleHead = jRule.getHead();
					jPredicateTerms ruleBody = jRule.getBase();
					jUnifiedVector v = new jUnifiedVector();
					if (ruleHead.unify(head, v) && ruleBody.unify(body, v)) {
						if (toBeRemove) {
							rds.removeRule(jRule);
						}
						return true;
					}
				}
			}
		}
		return false;
	}

	public void consult(String path) {
		try {
			kb.clearRules();
			FileReader fileReader = new FileReader(path);
			new pParseStream(fileReader, kb, pr, or).parseSource();
		} catch (FileNotFoundException e) {
			getLogger().error(getClass(), FILE_NOT_FOUND + path, e);
		}
	}

	public void consult(Reader reader) {
		kb.clearRules();
		new pParseStream(reader, kb, pr, or).parseSource();
	}

	public void include(String path) {
		try {
			FileReader fileReader = new FileReader(path);
			new pParseStream(fileReader, kb, pr, or).parseSource();
		} catch (FileNotFoundException e) {
			getLogger().error(getClass(), FILE_NOT_FOUND + path, e);
		}
	}

	public void include(Reader reader) {
		new pParseStream(reader, kb, pr, or).parseSource();
	}

	public void persist(String path) {
		PrintWriter writer = null;
		try {
			writer = new PrintWriter(new FileWriter(path));
			writer.print(JLogUtil.toString(engine));
			writer.flush();
		} catch (FileNotFoundException e) {
			getLogger().error(getClass(), FILE_NOT_FOUND + path, e);
		} catch (IOException e) {
			getLogger().error(getClass(), IO + path, e);
		} finally {
			assert writer != null;
			writer.close();
		}
	}

	public void abolish(String functor, int arity) {
		functor = JLogUtil.removeQuotesIfNeed(functor);
		iNameArityStub na = new iNameArityStub(functor, arity);
		jRuleDefinitions definitions = kb.getRuleDefinitionsMatch(na);
		if (definitions != null) {
			definitions.clearRules();
		}
	}

	public void asserta(String stringClause) {
		asserta(JLogUtil.toRule(stringClause, engine));
	}

	@Override
	public void asserta(PrologTerm term) {
		asserta(JLogUtil.toRule(provider, term));
	}

	public void asserta(PrologTerm head, PrologTerm... body) {
		asserta(JLogUtil.toRule(provider, head, body));
	}

	private void asserta(jRule rule) {
		if (!clause(rule)) {
			kb.addRuleFirst(rule);
		}
	}

	public void assertz(String stringClause) {
		assertz(JLogUtil.toRule(stringClause, engine));
	}

	@Override
	public void assertz(PrologTerm term) {
		assertz(JLogUtil.toRule(provider, term));
	}

	public void assertz(PrologTerm head, PrologTerm... body) {
		assertz(JLogUtil.toRule(provider, head, body));
	}

	private void assertz(jRule rule) {
		if (!clause(rule)) {
			kb.addRuleLast(rule);
		}
	}

	public boolean clause(String stringClause) {
		return clause(JLogUtil.toRule(stringClause, engine));
	}

	@Override
	public boolean clause(PrologTerm term) {
		return clause(JLogUtil.toRule(provider, term));
	}

	public boolean clause(PrologTerm head, PrologTerm... body) {
		return clause(JLogUtil.toRule(provider, head, body));
	}

	private boolean clause(jRule rule) {
		return clauseOrRetract(rule, false);
	}

	public void retract(String stringClause) {
		retract(JLogUtil.toRule(stringClause, engine));
	}

	@Override
	public void retract(PrologTerm term) {
		retract(JLogUtil.toRule(provider, term));
	}

	public void retract(PrologTerm head, PrologTerm... body) {
		retract(JLogUtil.toRule(provider, head, body));
	}

	private void retract(jRule rule) {
		clauseOrRetract(rule, true);
	}

	public PrologQuery query(String stringQuery) {
		return new JLogQuery(this, stringQuery);
	}

	@Override
	public PrologQuery query(PrologTerm term) {
		return new JLogQuery(this, term);
	}

	public PrologQuery query(PrologTerm[] terms) {
		return new JLogQuery(this, terms);
	}

	public PrologQuery query(PrologTerm term, PrologTerm... terms) {
		return new JLogQuery(this, term, terms);
	}

	public void operator(int priority, String specifier, String operator) {
		pOperatorEntry op = new pPredicateOperatorEntry(operator, getType(specifier), priority);
		engine.getOperatorRegistry().addOperator(op);
	}

	public boolean currentPredicate(String functor, int arity) {
		PrologIndicator pi = new JLogIndicator(functor, arity);
		return currentPredicates().contains(pi);
	}

	public boolean currentOperator(int priority, String specifier, String operator) {
		pOperatorEntry op = engine.getOperatorRegistry().getOperator(operator, true);
		op = op == null ? engine.getOperatorRegistry().getOperator(operator, false) : op;
		return op != null && op.getPriority() == priority && op.getType() == getType(specifier);
	}

	public Set<PrologOperator> currentOperators() {
		HashSet<PrologOperator> operators = new HashSet<PrologOperator>();
		Enumeration<?> e = engine.getOperatorRegistry().enumOperators();
		while (e.hasMoreElements()) {
			Object object = e.nextElement();
			if (object instanceof pOperatorEntry) {
				pOperatorEntry entry = (pOperatorEntry) object;
				String specifier = "";
				String operator = entry.getName();
				int priority = entry.getPriority();
				switch (entry.getType()) {
				case FX:
					specifier = "fx";
					break;
				case FY:
					specifier = "fy";
					break;
				case XFX:
					specifier = "xfx";
					break;
				case XFY:
					specifier = "xfy";
					break;
				case YFX:
					specifier = "yfx";
					break;
				case XF:
					specifier = "xf";
					break;
				default:
					specifier = "yf";
					break;
				}
				PrologOperator op = new JLogOperator(priority, specifier, operator);
				operators.add(op);
			}
		}
		return operators;
	}

	private int getType(String specifier) {
		int type = -1;
		if (specifier.equals("fx")) {
			type = FX;
		} else if (specifier.equals("fy")) {
			type = FY;
		} else if (specifier.equals("xfx")) {
			type = XFX;
		} else if (specifier.equals("xfy")) {
			type = XFY;
		} else if (specifier.equals("yfx")) {
			type = YFX;
		} else if (specifier.equals("xf")) {
			type = XF;
		} else if (specifier.equals("yf")) {
			type = YF;
		}
		return type;
	}

	public Iterator<PrologClause> iterator() {
		Collection<PrologClause> cls = new LinkedList<PrologClause>();
		Enumeration<?> enumeration = kb.enumDefinitions();
		while (enumeration.hasMoreElements()) {
			jRuleDefinitions object = (jRuleDefinitions) enumeration.nextElement();
			Enumeration<?> r = object.enumRules();
			while (r.hasMoreElements()) {
				Object object2 = r.nextElement();
				if (!(object2 instanceof jBuiltinRule)) {
					jRule jRule = (jRule) object2;

					// rule head
					jPredicate ruleHead = jRule.getHead();
					PrologTerm head = toTerm(ruleHead, PrologTerm.class);

					// rule body
					jPredicateTerms ruleBody = jRule.getBase();
					PrologTerm body = toTerm(ruleBody, PrologTerm.class);

					// rule end
					if (!(body instanceof JLogTrue)) {
						cls.add(new JLogClause(provider, head, body, false, false, false));
					} else {
						cls.add(new JLogClause(provider, head, false, false, false));
					}

				}
			}
		}
		return new PrologProgramIterator(cls);
	}

	public int getProgramSize() {
		int programSize = 0;
		Enumeration<?> de = kb.enumDefinitions();
		while (de.hasMoreElements()) {
			jRuleDefinitions rules = (jRuleDefinitions) de.nextElement();
			Enumeration<?> re = rules.enumRules();
			while (re.hasMoreElements()) {
				Object rule = re.nextElement();
				if (!(rule instanceof jBuiltinRule)) {
					programSize++;
				}
			}
		}
		return programSize;
	}

	@Override
	public PrologProgram getProgram() {
		return new JLogProgram(this);
	}

	public Set<PrologIndicator> getPredicates() {
		Set<PrologIndicator> predicates = new HashSet<PrologIndicator>();
		Enumeration<?> e = kb.enumDefinitions();
		while (e.hasMoreElements()) {
			jRuleDefinitions definitions = (jRuleDefinitions) e.nextElement();
			Enumeration<?> rules = definitions.enumRules();
			while (rules.hasMoreElements()) {
				Object object2 = rules.nextElement();
				if (!(object2 instanceof jBuiltinRule)) {
					jRule jRule = (jRule) object2;
					jPredicate ruleHead = jRule.getHead();
					String functor = ruleHead.getName();
					int arity = ruleHead.getArity();
					JLogIndicator pi = new JLogIndicator(functor, arity);
					predicates.add(pi);
				}
			}
		}
		return predicates;
	}

	public Set<PrologIndicator> getBuiltIns() {
		Set<PrologIndicator> builtins = new HashSet<PrologIndicator>();
		Enumeration<?> e = engine.getPredicateRegistry().enumPredicates();
		while (e.hasMoreElements()) {
			Object object = e.nextElement();
			if (object instanceof pGenericPredicateEntry) {
				pGenericPredicateEntry entry = (pGenericPredicateEntry) object;
				String functor = entry.getName();
				int arity = entry.getArity();
				JLogIndicator pi = new JLogIndicator(functor, arity);
				builtins.add(pi);
			}
		}
		return builtins;
	}

	public String getLicense() {
		return Licenses.GPL_V2;
	}

	public String getVersion() {
		String credits = jPrologServices.getRequiredCreditInfo();
		StringTokenizer tokenizer = new StringTokenizer(credits);
		/* String name = */tokenizer.nextToken();
		return tokenizer.nextToken();
	}

	public String getVendor() {
		String credits = jPrologServices.getRequiredCreditInfo();
		StringTokenizer tokenizer = new StringTokenizer(credits);
		return tokenizer.nextToken();
	}

	public String getName() {
		String credits = jPrologServices.getRequiredCreditInfo();
		StringTokenizer tokenizer = new StringTokenizer(credits);
		return tokenizer.nextToken();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + engine.hashCode();
		result = prime * result + kb.hashCode();
		result = prime * result + or.hashCode();
		result = prime * result + pr.hashCode();
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		JLogEngine other = (JLogEngine) obj;
		if (!engine.equals(other.engine))
			return false;
		if (!kb.equals(other.kb))
			return false;
		if (!or.equals(other.or))
			return false;
		return pr.equals(other.pr);
	}

	public void dispose() {
		engine.release();
		kb.clearRules();
	}

	public final List<String> verify() {
		return Arrays.asList("OK");
	}

}