content: /**
   * Copyright (c) 2017-2019, the Alpha Team.
   * All rights reserved.
   * 
   * Additional changes made by Siemens.
   * 
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   * 
   * 1) Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   * 
   * 2) Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  package at.ac.tuwien.kr.alpha.commons.programs.terms;
  
  import java.util.LinkedHashSet;
  import java.util.Set;
  
  import at.ac.tuwien.kr.alpha.api.grounder.Substitution;
  import at.ac.tuwien.kr.alpha.api.programs.terms.ArithmeticOperator;
  import at.ac.tuwien.kr.alpha.api.programs.terms.ArithmeticTerm;
  import at.ac.tuwien.kr.alpha.api.programs.terms.Term;
  import at.ac.tuwien.kr.alpha.api.programs.terms.VariableTerm;
  import at.ac.tuwien.kr.alpha.commons.util.Interner;
  
  /**
   * This class represents an arithmetic expression occurring as a term.
   * Copyright (c) 2017-2019, the Alpha Team.
   */
  class ArithmeticTermImpl extends AbstractTerm implements ArithmeticTerm {
  	private static final Interner<ArithmeticTermImpl> INTERNER = new Interner<>();
  	protected final Term left;
  	private final ArithmeticOperator arithmeticOperator;
  	private final Term right;
  
  	private ArithmeticTermImpl(Term left, ArithmeticOperator arithmeticOperator, Term right) {
  		this.left = left;
  		this.arithmeticOperator = arithmeticOperator;
  		this.right = right;
  	}
  
  	static Term getInstance(Term left, ArithmeticOperator arithmeticOperator, Term right) {
  		// Evaluate ground arithmetic terms immediately and return result.
  		if (left.isGround() && right.isGround()) {
  			Integer result = new ArithmeticTermImpl(left, arithmeticOperator, right).evaluateExpression();
  			return ConstantTermImpl.getInstance(result);
  		}
  		return INTERNER.intern(new ArithmeticTermImpl(left, arithmeticOperator, right));
  	}
  
  	@Override
  	public boolean isGround() {
  		return left.isGround() && right.isGround();
  	}
  
  	@Override
  	public Set<VariableTerm> getOccurringVariables() {
  		LinkedHashSet<VariableTerm> occurringVariables = new LinkedHashSet<>(left.getOccurringVariables());
  		occurringVariables.addAll(right.getOccurringVariables());
  		return occurringVariables;
  	}
  
  	@Override
  	public Term substitute(Substitution substitution) {
  		return getInstance(left.substitute(substitution), arithmeticOperator, right.substitute(substitution));
  	}
  
  	@Override
  	public Term renameVariables(String renamePrefix) {
  		return getInstance(left.renameVariables(renamePrefix), arithmeticOperator, right.renameVariables(renamePrefix));
  	}
  
  	@Override
  	public Term normalizeVariables(String renamePrefix, Term.RenameCounter counter) {
  		Term normalizedLeft = left.normalizeVariables(renamePrefix, counter);
  		Term normalizedRight = right.normalizeVariables(renamePrefix, counter);
  		return ArithmeticTermImpl.getInstance(normalizedLeft, arithmeticOperator, normalizedRight);
  
  	}
  
  	@Override
  	public String toString() {
  		return left + " " + arithmeticOperator + " " + right;
  	}
  
  	@Override
  	public boolean equals(Object o) {
  		if (this == o) {
  			return true;
  		}
  		if (o == null || getClass() != o.getClass()) {
  			return false;
  		}
  
  		ArithmeticTermImpl that = (ArithmeticTermImpl) o;
  
  		if (left != that.left) {
  			return false;
  		}
  		if (arithmeticOperator != that.arithmeticOperator) {
  			return false;
  		}
  		return right == that.right;
  	}
  
  	@Override
  	public int hashCode() {
  		return 31 * (31 * left.hashCode() + arithmeticOperator.hashCode()) + right.hashCode();
  	}
  	
  	@Override
  	public ArithmeticOperator getOperator() {
  		return arithmeticOperator;
  	}
  	
  	@Override
  	public Term getLeftOperand() {
  		return left;
  	}
  	
  	@Override
  	public Term getRightOperand() {
  		return right;
  	}
  
  	@Override
  	public Integer evaluateExpression() {
  		Integer leftInt = Terms.evaluateGroundTermHelper(left);
  		Integer rightInt = Terms.evaluateGroundTermHelper(right);
  		if (leftInt == null || rightInt == null) {
  			return null;
  		}
  		return arithmeticOperator.eval(leftInt, rightInt);
  	}
  	
  	// Note: It doesn't seem like this class is really needed, could be handled by an if in ArithmeticTermImpl#getInstance.
  	public static class MinusTerm extends ArithmeticTermImpl {
  
  		private MinusTerm(Term term) {
  			super(term, null, null);
  		}
  
  		static Term getInstance(Term term) {
  			// Evaluate ground arithmetic terms immediately and return result.
  			if (term.isGround()) {
  				Integer result = Terms.evaluateGroundTerm(term) * -1;
  				return ConstantTermImpl.getInstance(result);
  			}
  			return INTERNER.intern(new MinusTerm(term));
  		}
  
  		@Override
  		public Integer evaluateExpression() {
  			return Terms.evaluateGroundTermHelper(left) * -1;
  		}
  
  		@Override
  		public boolean isGround() {
  			return left.isGround();
  		}
  
  		@Override
  		public Set<VariableTerm> getOccurringVariables() {
  			return left.getOccurringVariables();
  		}
  
  		@Override
  		public Term substitute(Substitution substitution) {
  			return getInstance(left.substitute(substitution));
  		}
  
  		@Override
  		public Term renameVariables(String renamePrefix) {
  			return getInstance(left.renameVariables(renamePrefix));
  		}
  
  		@Override
  		public Term normalizeVariables(String renamePrefix, Term.RenameCounter counter) {
  			Term normalizedLeft = left.normalizeVariables(renamePrefix, counter);
  			return MinusTerm.getInstance(normalizedLeft);
  		}
  
  		@Override
  		public String toString() {
  			return "-" + left;
  		}
  
  		@Override
  		public boolean equals(Object o) {
  			if (this == o) {
  				return true;
  			}
  			if (o == null || getClass() != o.getClass()) {
  				return false;
  			}
  
  			MinusTerm that = (MinusTerm) o;
  
  			return left == that.left;
  		}
  
  		@Override
  		public int hashCode() {
  			return 31 * left.hashCode();
  		}
  	}
  
  }
  