/**
  *
  *                      OOAS Compiler
  *
  *       Copyright 2015, AIT Austrian Institute of Technology.
  * This code is based on the C# Version of the OOAS Compiler, which is
  * copyright 2015 by the Institute of Software Technology, Graz University
  * of Technology with portions copyright by the AIT Austrian Institute of
  * Technology. All rights reserved.
  *
  * SEE THE "LICENSE" FILE FOR THE TERMS UNDER WHICH THIS FILE IS PROVIDED.
  *
  * If you modify the file please update the list of contributors below to in-
  * clude your name. Please also stick to the coding convention of using TABs
  * to do the basic (block-level) indentation and spaces for anything after
  * that. (Enable the display of special chars and it should be pretty obvious
  * what this means.) Also, remove all trailing whitespace.
  *
  * Contributors:
  *               Willibald Krenn (AIT)
  *               Stephan Zimmerer (AIT)
  *               Markus Demetz (AIT)
  *               Christoph Czurda (AIT)
  *
  */


package org.momut.ooas.ast.expressions;

import java.util.ArrayList;
import java.util.List;

import org.momut.ooas.ast.AstNode;
import org.momut.ooas.ast.AstNodeTypeEnum;
import org.momut.ooas.ast.IAst;
import org.momut.ooas.ast.IAstVisitor;
import org.momut.ooas.ast.identifiers.ExpressionVariableIdentifier;
import org.momut.ooas.ast.identifiers.FunctionIdentifier;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.types.FloatType;
import org.momut.ooas.ast.types.IntType;
import org.momut.ooas.ast.types.TypeKind;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.ast.types.ValuedEnumType;
import org.momut.ooas.math.AbstractOperations;
import org.momut.ooas.math.AbstractRange;
import org.momut.ooas.math.DoubleRange;
import org.momut.ooas.math.IntegerRange;
import org.momut.ooas.math.SaturatedDoubleOperations;
import org.momut.ooas.math.SaturatedIntegerOperations;
import org.momut.ooas.parser.SymbolTable;
import org.momut.ooas.utils.exceptions.ArgumentException;
import org.momut.ooas.utils.exceptions.NotImplementedException;
import org.momut.ooas.visitors.OoaPrintVisitor;

///////////////////////////////////////////////
///  Expressions
///
/// Note: Since we allow expression of the form
/// myTuple(a,b) = hd x, where a and b are newly
/// introduced placeholders, the expression knows
/// about local variables.
/// Scoping: Consider following expression
///     3 > a and myTuple(b,d) = hd e and b < 5
/// which gives
///             and
///          /       \
///         /         and
///        /        /     \
///       >        =        <
///      / \      /  \     /  \
///     3   a  (b,d)  e   b    5
///  the scope of b and d is defined by the binary
///  expression that has the leaf within its left child
///  (== the second 'and').
///  Local variables may only be introduced in constructors
///  of sets, lists, and tuples. Therefore the type is
///  known.


public abstract class Expression extends AstNode implements IAst
{
	protected ExpressionKind m_kind;
	protected Type m_type;
	protected int m_line;
	protected int m_pos;
	protected SymbolTable m_freeVariables;
	protected ArrayList<FunctionIdentifier> m_callTargets;

	public ExpressionKind kind() { return m_kind; }
	public Type type() { return m_type; }
	public int line() { return m_line; }
	public int pos() { return m_pos; }
	public SymbolTable freeVariables() { return m_freeVariables; }
	public List<FunctionIdentifier> callTargets() { return m_callTargets; }

	public Expression(ExpressionKind aKind, int line, int pos)
	{
		m_kind = aKind;
		m_line = line;
		m_pos = pos;
		m_callTargets = new ArrayList<FunctionIdentifier>();
	}

	public Expression(Expression toCopy)
	{
		m_kind = toCopy.m_kind;
		m_line = toCopy.m_line;
		m_pos = toCopy.m_pos;
		m_callTargets = new ArrayList<FunctionIdentifier>(toCopy.callTargets());
		m_type = toCopy.m_type;
		m_freeVariables = new SymbolTable(toCopy.m_freeVariables);
	}

	public /*virtual*/ Expression Clone()
	{
		throw new NotImplementedException();
	}

	public void SetType(Type aType)
	{
		if (aType == null)
			throw new ArgumentException();
		m_type = aType;
	}

	public void SetFreeVariables(SymbolTable aSymTab)
	{
		m_freeVariables = aSymTab;
	}

	public ArrayList<ExpressionVariableIdentifier> GetUninitializedFreeVariables()
	{
		final ArrayList<ExpressionVariableIdentifier> result = new ArrayList<ExpressionVariableIdentifier>();
		if (m_freeVariables != null)
			for(final Identifier v: m_freeVariables.symbolList())
			{
				final ExpressionVariableIdentifier id = (ExpressionVariableIdentifier)v;
				if (id.initialized() == false)
					result.add(id);
			}
		return result;
	}


	@Override
	public AstNodeTypeEnum nodeType() { return AstNodeTypeEnum.expression; }

	@Override
	public /*virtual*/ void Accept(IAstVisitor visitor)
	{
		throw new NotImplementedException();
	}


	/// <summary>
	/// Calculates the arithmetic cover, i.e. the return type of the operation, given two types.
	/// This is different from the cover-method defined at the type level.
	/// Note: We do saturating operations on the type boundaries.
	/// </summary>
	public static Type ArithmeticCover(Type type1, Type type2, ExpressionKind op)
	{
		if (!type1.IsNumeric() || (type2 != null && !type2.IsNumeric()))
			throw new ArgumentException();

		if (type1 instanceof ValuedEnumType)
			type1 = ((ValuedEnumType)type1).getIntType();
		if (type2 instanceof ValuedEnumType)
			type2 = ((ValuedEnumType)type2).getIntType();

		AbstractRange result;
		AbstractRange rangeType1 = null;
		AbstractRange rangeType2 = null;
		AbstractOperations operations;
		final boolean resultIsFloat = type1.kind() == TypeKind.FloatType
				|| op == ExpressionKind.div
				|| (type2 != null && type2.kind() == TypeKind.FloatType);
		if (resultIsFloat)
		{
			rangeType1 = new DoubleRange(
				type1.kind() == TypeKind.IntType ? ((IntType)type1).rangeHigh() : ((FloatType)type1).high(),
				type1.kind() == TypeKind.IntType ? ((IntType)type1).rangeLow() : ((FloatType)type1).low());
			if (type2 != null)
			{
				rangeType2 = new DoubleRange(
					type2.kind() == TypeKind.IntType ? ((IntType)type2).rangeHigh() : ((FloatType)type2).high(),
					type2.kind() == TypeKind.IntType ? ((IntType)type2).rangeLow() : ((FloatType)type2).low());
			}
			operations = new SaturatedDoubleOperations();
		}
		else
		{
			rangeType1 = new IntegerRange(((IntType)type1).rangeHigh(), ((IntType)type1).rangeLow());
			rangeType2 = new IntegerRange(((IntType)type2).rangeHigh(), ((IntType)type2).rangeLow());
			operations = new SaturatedIntegerOperations();
		}

		switch (op)
		{
			case pow:
			case prod:
			case unminus:
			case unplus:
			case minus:
			case sum:
			case div:
				result = operations.GenericArithmeticCover(rangeType1, rangeType2, op);
				break;

			case idiv:
				if (resultIsFloat)
					throw new ArgumentException();
				assert(type2 != null);
				result = operations.GenericArithmeticCover(rangeType1, rangeType2, op);
				break;

			case mod:
				if (resultIsFloat)
					throw new ArgumentException();
				assert(type2 != null);
				// FIXME: see http://mathforum.org/library/drmath/view/52343.html; LLVM only supports rem!
				final boolean isSigned = ((IntegerRange)rangeType2).min < 0 || ((IntegerRange)rangeType1).min < 0;
				result = new IntegerRange(((IntegerRange)rangeType2).max - 1, isSigned ? -((IntegerRange)rangeType2).max - 1 : 0);
				break;

			default:
				throw new NotImplementedException();
		}

		if (resultIsFloat)
			return new FloatType(
					((DoubleRange)result).min,
					((DoubleRange)result).max,
					FloatType.defaultPrecision(),
					null);
		else
			// this is crude, but see whether it works..
			return new IntType(
					((IntegerRange)result).min,
					((IntegerRange)result).max,
					null);
	}


	@Override
	public String toString()
	{
		return toString(new OoaPrintVisitor(null));
	}

	public String toString(OoaPrintVisitor aFormatter) {
		this.Accept(aFormatter);
		return aFormatter.toString();
	}
}

