/*
 * JaamSim Discrete Event Simulation
 * Copyright (C) 2014 Ausenco Engineering Canada Inc.
 *
 * 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.
 */
package com.jaamsim.input;

import com.jaamsim.basicsim.Entity;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.Unit;

/**
 * Utility class to validate expressions at parse time
 * @author Matt Chudleigh
 *
 */
public class ExpValidator {

	private static Entity validateEntity(String[] names, Entity thisEnt) throws ExpError {
		if (names.length == 1) {
			throw new ExpError(null, 0, "You must specify an output or attribute for entity: %s", names[0]);
		}
		Entity ent;
		if (names[0] == "this")
			ent = thisEnt;
		else
			ent = Entity.getNamedEntity(names[0]);

		if (ent == null) {
			throw new ExpError(null, 0, "Could not find entity: %s", names[0]);
		}
		return ent;
	}

	private static class EntityValidateContext implements ExpParser.EvalContext {

		private Entity thisEnt;
		public boolean undecidable = false;

		public EntityValidateContext(Entity thisEnt) {
			this.thisEnt = thisEnt;
		}

		@Override
		public ExpResult getVariableValue(String[] names) throws ExpError {

			Entity ent = validateEntity(names, thisEnt);

			if (names.length == 2) {
				String outputName = names[1];
				OutputHandle oh = ent.getOutputHandleInterned(outputName);
				if (oh == null) {
					throw new ExpError(null, 0, String.format("Could not find output '%s' on entity '%s'", outputName, ent.getName()));
				}
				if (!OutputHandle.isNumericType(oh.getReturnType())) {
					throw new ExpError(null, 0, "Output: %s does not return a numeric type", names[1]);
				}
				return new ExpResult(0, oh.unitType);
			}

			// We have a 'chained' output, so now we must do best effort evaluation
			// The only failures we can detect from here out are unit errors and using an existing output in an invalid
			// way. Typos are not detectable because entities may have outputs from descendant classes or attributes

			Class<?> klass = ent.getClass();
			for (int i = 1; i < names.length; ++i) {
				Class<? extends Unit> unitType = OutputHandle.getStaticOutputUnitType(klass, names[i]);
				klass = OutputHandle.getStaticOutputType(klass, names[i]);
				if (klass == null) {
					// Undecidable
					undecidable = true;
					return new ExpResult(0, DimensionlessUnit.class);
				}

				if (i == names.length - 1) {
					// Last name in the chain, check that the type is numeric
					if (!OutputHandle.isNumericType(klass)) {
						throw new ExpError(null, 0, "Output: '%s' does not return a numeric type", names[i]);
					}
					return new ExpResult(0, unitType);
				} else {
					if (!Entity.class.isAssignableFrom(klass)) {
						throw new ExpError(null, 0, "Output: '%s' must output an entity type", names[i]);
					}
				}
			}
			// We should never get here
			throw new ExpError(null, 0, "Validator logic error");
		}

		@Override
		public boolean eagerEval() {
			return true;
		}
	}

	public static void validateAssignment(ExpParser.Assignment assign, Entity thisEnt) throws ExpError {
		String[] dest = assign.destination;
		Entity ent = validateEntity(dest, thisEnt);

		if (dest.length == 2) {
			if (!ent.hasAttribute(dest[1])) {
				throw new ExpError(null, 0, String.format("Could not find attribute '%s' on entity '%s'", dest[1], dest[0]));
			}
		}
		// Otherwise we do not validate assignment destination yet

		validateExpression(assign.value, thisEnt);
	}

	// Validate an expression, if the expression is invalid, this will throw an ExpError detailing the problem
	public static void validateExpression(ExpParser.Expression exp, Entity thisEnt) throws ExpError
	{
		EntityValidateContext valContext = new EntityValidateContext(thisEnt);
		try {
			exp.evaluate(valContext);
		} catch (ExpError ex) {
			if (valContext.undecidable) {
				// We got an error, but can not be sure it is a validation problem
				return;
			}
			throw ex;
		}
	}
}

@@ -1937,59 +1937,175 @@
 %09%09%09%09
-if (!OutputHandle.isNumericType(oh.getReturnType())
+Class%3C?%3E retType = oh.getReturnType();%0A%09%09%09%09if (    !OutputHandle.isNumericType(retType) &&%0A%09%09%09%09        retType != boolean.class &&%0A%09%09%09%09        retType != Boolean.class
 ) %7B%0A

Fix false positive expression validator error on boolean outputs