JLogConverter.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.PrologTermType.ATOM_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.CLASS_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.CUT_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.DOUBLE_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.FAIL_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.FALSE_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.FIELD_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.FLOAT_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.INTEGER_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.LIST_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.LONG_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.MAP_ENTRY_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.MAP_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.MIXIN_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.NIL_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.OBJECT_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.PARAMETER_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.RESULT_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.STRUCTURE_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.TRUE_TYPE;
import static io.github.prolobjectlink.prolog.PrologTermType.VARIABLE_TYPE;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

import io.github.prolobjectlink.prolog.AbstractConverter;
import io.github.prolobjectlink.prolog.PrologAtom;
import io.github.prolobjectlink.prolog.PrologConverter;
import io.github.prolobjectlink.prolog.PrologDouble;
import io.github.prolobjectlink.prolog.PrologFloat;
import io.github.prolobjectlink.prolog.PrologInteger;
import io.github.prolobjectlink.prolog.PrologList;
import io.github.prolobjectlink.prolog.PrologLogger;
import io.github.prolobjectlink.prolog.PrologLong;
import io.github.prolobjectlink.prolog.PrologProvider;
import io.github.prolobjectlink.prolog.PrologStructure;
import io.github.prolobjectlink.prolog.PrologTerm;
import io.github.prolobjectlink.prolog.PrologVariable;
import io.github.prolobjectlink.prolog.UnknownTermError;
import ubc.cs.JLog.Foundation.jEquivalenceMapping;
import ubc.cs.JLog.Terms.jAtom;
import ubc.cs.JLog.Terms.jBinaryBuiltinPredicate;
import ubc.cs.JLog.Terms.jBuiltinPredicate;
import ubc.cs.JLog.Terms.jCompoundTerm;
import ubc.cs.JLog.Terms.jFail;
import ubc.cs.JLog.Terms.jInteger;
import ubc.cs.JLog.Terms.jList;
import ubc.cs.JLog.Terms.jListPair;
import ubc.cs.JLog.Terms.jNullList;
import ubc.cs.JLog.Terms.jObject;
import ubc.cs.JLog.Terms.jPredicate;
import ubc.cs.JLog.Terms.jPredicateTerms;
import ubc.cs.JLog.Terms.jReal;
import ubc.cs.JLog.Terms.jTerm;
import ubc.cs.JLog.Terms.jTrue;
import ubc.cs.JLog.Terms.jUnaryBuiltinPredicate;
import ubc.cs.JLog.Terms.jVariable;

/**
 * 
 * @author Jose Zalacain
 * @since 1.0
 */
class JLogConverter extends AbstractConverter<jTerm> implements PrologConverter<jTerm> {

	private final jPredicateTerms emptyBody = new jPredicateTerms();
	private final jEquivalenceMapping equivalence = new jEquivalenceMapping();

	private jList adaptList(PrologTerm[] arguments) {
		jList pList = jNullList.NULL_LIST;
		for (int i = arguments.length - 1; i >= 0; --i) {
			pList = new jListPair(fromTerm(arguments[i]), pList);
		}
		return pList;
	}

	private jCompoundTerm adaptCompound(PrologTerm[] arguments) {
		jCompoundTerm compound = new jCompoundTerm(arguments.length);
		for (PrologTerm iPrologTerm : arguments) {
			compound.addTerm(fromTerm(iPrologTerm));
		}
		return compound;
	}

	public PrologTerm toTerm(jTerm prologTerm) {
		switch (prologTerm.type) {
		case jTerm.TYPE_NULLLIST:
			return new JLogEmpty(provider);
		case jTerm.TYPE_ATOM:
			String value = prologTerm.getName();
			if (value.equals(JLogNil.NIL_STR)) {
				return new JLogNil(provider);
			} else if (value.equals(JLogFalse.FALSE_STR)) {
				return new JLogFalse(provider);
			} else if (!value.matches(SIMPLE_ATOM_REGEX)) {
				return new JLogAtom(provider, "'" + value + "'");
			}
			return new JLogAtom(provider, value);
		case jTerm.TYPE_INTEGER:
			return new JLogInteger(provider, ((jInteger) prologTerm).getIntegerValue());
		case jTerm.TYPE_REAL:
			JLogTerm number = null;
			try {
				if (prologTerm instanceof jFloat) {
					number = new JLogFloat(provider, ((jFloat) prologTerm).getRealValue());
				} else {
					number = new JLogDouble(provider, ((jDouble) prologTerm).getRealValue());
				}
			} catch (ClassCastException e) {
				// the parsed number is a jReal number we need convert in double or float
				if (e.getMessage().contains(jFloat.class.getName())) {
					number = new JLogFloat(provider, ((jReal) prologTerm).getRealValue());
				} else if (e.getMessage().contains(jDouble.class.getName())) {
					number = new JLogDouble(provider, ((jReal) prologTerm).getRealValue());
				} else {
					getLogger().error(getClass(), PrologLogger.CLASS_CAST, e);
				}
			}
			return number;
		case jTerm.TYPE_VARIABLE:
			String name = ((jVariable) prologTerm).getName();
			PrologVariable variable = sharedVariables.get(name);
			if (variable == null) {
				variable = new JLogVariable(provider, name);
				sharedVariables.put(variable.getName(), variable);
			}
			return variable;
		case jTerm.TYPE_LIST:
			jTerm[] array = new jTerm[0];
			jList list = (jList) prologTerm;
			ArrayList<jTerm> arguments = new ArrayList<jTerm>();
			Iterator<jTerm> i = new JLogIterator(list);
			while (i.hasNext()) {
				arguments.add(i.next());
			}
			return new JLogList(provider, arguments.toArray(array));
		case jTerm.TYPE_OBJECT:
			return new JLogReference(provider, prologTerm);
		case jTerm.TYPE_TYPE:
			jUnaryBuiltinPredicate unary = (jUnaryBuiltinPredicate) prologTerm;
			return new JLogStructure(provider, unary.getName(), unary.getRHS());
		case jTerm.TYPE_COMPARE:
		case jTerm.TYPE_OPERATOR:
		case jTerm.TYPE_ARITHMETIC:
		case jTerm.TYPE_UNARYOPERATOR:
		case jTerm.TYPE_NUMERICCOMPARE:
		case jTerm.TYPE_UNARYARITHMETIC:
			jBinaryBuiltinPredicate binary = (jBinaryBuiltinPredicate) prologTerm;
			return new JLogStructure(provider, binary.getLHS(), binary.getName(), binary.getRHS());
		case jTerm.TYPE_BUILTINPREDICATE:
			jBuiltinPredicate builtIn = (jBuiltinPredicate) prologTerm;
			if (builtIn.equivalence(jTrue.TRUE, equivalence)) {
				return new JLogTrue(provider);
			} else if (builtIn.equivalence(jFail.FAIL, equivalence)) {
				return new JLogFail(provider);
			} else if (builtIn.equivalence(JLogCut.JCUT, equivalence)) {
				return new JLogCut(provider);
			} else if (builtIn.getArity() == 2) {
				jBinaryBuiltinPredicate b = (jBinaryBuiltinPredicate) prologTerm;
				return new JLogStructure(provider, b.getLHS(), b.getName(), b.getRHS());
			}
			throw new UnknownTermError(prologTerm);
		case jTerm.TYPE_PREDICATE:
			jPredicate predicate = (jPredicate) prologTerm;
			jCompoundTerm compound = predicate.getArguments();
			String functor = predicate.getName();
			return new JLogStructure(provider, functor, compound);
		case jTerm.TYPE_PREDICATETERMS:
			jPredicateTerms terms = (jPredicateTerms) prologTerm;
			Enumeration<?> k = terms.enumTerms();
			if (k.hasMoreElements()) {
				PrologTerm body = null;
				while (k.hasMoreElements()) {
					jTerm term = (jTerm) k.nextElement();
					if (body != null) {
						body = new JLogStructure(provider, ",", body, toTerm(term));
					} else {
						body = toTerm(term);
					}
				}
				return body;
			}
			return new JLogTrue(provider);

		default:
			throw new UnknownTermError(prologTerm);
		}
	}

	public jTerm fromTerm(PrologTerm term) {
		switch (term.getType()) {
		case NIL_TYPE:
			return new jAtom("nil");
		case CUT_TYPE:
			return new jAtom("!");
		case FAIL_TYPE:
			return jFail.FAIL;
		case TRUE_TYPE:
			return jTrue.TRUE;
		case FALSE_TYPE:
			return new jAtom("false");
		case ATOM_TYPE:
			String value = ((PrologAtom) term).getStringValue();
			return new jAtom(value);
		case FLOAT_TYPE:
			return new jFloat(((PrologFloat) term).getFloatValue());
		case INTEGER_TYPE:
			return new jInteger(((PrologInteger) term).getIntegerValue());
		case DOUBLE_TYPE:
			return new jDouble(((PrologDouble) term).getDoubleValue());
		case LONG_TYPE:
			return new jInteger(((PrologLong) term).getIntegerValue());
		case VARIABLE_TYPE:
			String name = ((PrologVariable) term).getName();
			jTerm variable = sharedPrologVariables.get(name);
			if (variable == null) {
				variable = new jVariable(name);
				sharedPrologVariables.put(name, variable);
			}
			return variable;
		case LIST_TYPE:
		case MAP_TYPE:
			PrologTerm[] arguments = ((PrologList) term).getArguments();
			return adaptList(arguments);
		case STRUCTURE_TYPE:
		case MAP_ENTRY_TYPE:
			String functor = term.getFunctor();
			arguments = ((PrologStructure) term).getArguments();
			return new jPredicate(functor, adaptCompound(arguments));
		case OBJECT_TYPE:
			return new jObject(term.getObject());
		case PARAMETER_TYPE:
		case RESULT_TYPE:
		case FIELD_TYPE:
			name = ((PrologVariable) term).getName();
			return new jVariable(name);
		case MIXIN_TYPE:
		case CLASS_TYPE:
			arguments = term.getArguments();
			functor = removeQuoted(term.getFunctor());
			return new jPredicate(functor, adaptCompound(arguments));
		default:
			throw new UnknownTermError(term);
		}
	}

	public jTerm[] fromTermArray(PrologTerm[] terms) {
		jTerm[] prologTerms = new jTerm[terms.length];
		for (int i = 0; i < terms.length; i++) {
			prologTerms[i] = fromTerm(terms[i]);
		}
		return prologTerms;
	}

	public jTerm fromTerm(PrologTerm head, PrologTerm[] body) {
		jTerm clauseHead = fromTerm(head);
		if (body != null && body.length > 0) {
			jTerm clauseBody = fromTerm(body[body.length - 1]);
			for (int i = body.length - 2; i >= 0; --i) {
				jCompoundTerm args = new jCompoundTerm(2);
				args.addTerm(fromTerm(body[i]));
				args.addTerm(clauseBody);
				clauseBody = new jPredicate(",", args);
			}
			jCompoundTerm args = new jCompoundTerm(2);
			args.addTerm(clauseHead);
			args.addTerm(clauseBody);
			return new jPredicate(":-", args);
		}
		return clauseHead;
	}

	public PrologProvider createProvider() {
		return new JLog(this);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + emptyBody.hashCode();
		result = prime * result + equivalence.hashCode();
		return result;
	}

	@Override
	public boolean equals(Object object) {
		if (this == object)
			return true;
		if (!super.equals(object))
			return false;
		if (getClass() != object.getClass())
			return false;
		JLogConverter other = (JLogConverter) object;
		if (!emptyBody.equals(other.emptyBody))
			return false;
		return equivalence.equals(other.equivalence);
	}

	@Override
	public String toString() {
		return "JLogConverter";
	}

}