/**
  *
  *                      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.math;

import org.momut.ooas.ast.expressions.ExpressionKind;
import org.momut.ooas.utils.exceptions.ArgumentException;
import org.momut.ooas.utils.exceptions.NotImplementedException;

public class Operations<T> extends AbstractOperations
{
	public static class BasicOps<T> {
		public T unMinus(T input) {throw new NotImplementedException();}

		public T minus(T inputA, T inputB) {throw new NotImplementedException();}
		public T plus(T inputA, T inputB) {throw new NotImplementedException();}
		public T div(T inputA, T inputB) {throw new NotImplementedException();}
		public T pow(T inputA, T inputB) {throw new NotImplementedException();}
		public T prod(T inputA, T inputB) {throw new NotImplementedException();}
		public T mod(T inputA, T inputB) {throw new NotImplementedException();}

		public boolean equal(T inputA, T intputB) {throw new NotImplementedException();}
		public boolean smaller(T inputA, T intputB) {throw new NotImplementedException();}
		public boolean greater(T inputA, T intputB){throw new NotImplementedException();}

		public T getDefaultValue() {throw new NotImplementedException();}
	}

	protected final BasicOps<T> m_basicOperations;


	@SuppressWarnings("unchecked")
	@Override
	public AbstractRange GenericArithmeticCover(AbstractRange type1, AbstractRange type2, ExpressionKind op)
	{
		final Range<T> a = (Range<T>) type1;
		final Range<T> b = (Range<T>) type2;

//		Range<T> a = type1 as Range<T>;
//		Range<T> b = type2 as Range<T>;

		if (a == null || b == null)
			throw new ArgumentException();

		return GenericArithmeticCover(a, b, op);
	}

	public Range<T> GenericArithmeticCover(Range<T> type1, Range<T> type2, ExpressionKind op)
	{
		final T defaultValue = m_basicOperations.getDefaultValue();
		final Range<T> result = type1.Create(defaultValue, defaultValue);

		switch (op)
		{
			case unminus:
				result.min = m_basicOperations.unMinus(type1.max);
				result.max = m_basicOperations.unMinus(type1.min);
				break;
			case unplus:
				break;

			case minus:
				assert(type2 != null);
				// we do some sort of resulttype = type1 - type2
				// hence, do the unminus stuff with type 2
				final T spare = type2.max;
				type2.max = m_basicOperations.unMinus(type2.min);
				type2.min = m_basicOperations.unMinus(spare);
				// now it's the same as with sum.. but c# does not let us fall through..
				result.min = m_basicOperations.plus(type1.min, type2.min);
				result.max = m_basicOperations.plus(type1.max, type2.max);
				break;
			case sum:
				assert(type2 != null);
				result.min = m_basicOperations.plus(type1.min, type2.min);
				result.max = m_basicOperations.plus(type1.max, type2.max);
				break;
			case idiv:
			case div:
				// get the closest values to 0 for the divisor.
				assert(type2 != null);
				final Range<T> closestToZero = type2.Create(defaultValue, defaultValue);
				if (m_basicOperations.greater(type2.max, defaultValue) && (m_basicOperations.greater(type2.min, defaultValue) || m_basicOperations.equal(type2.min, defaultValue))) {
					// divisor range [X...0+]
					closestToZero.max = m_basicOperations.equal(type2.min, defaultValue) ? type2.precision : type2.min; // type2.min or type2.precision if that was 0
					closestToZero.min = closestToZero.max;
				} else if (m_basicOperations.smaller(type2.max, defaultValue) && (m_basicOperations.smaller(type2.min, defaultValue) || m_basicOperations.equal(type2.min, defaultValue))) {
					// divisor range [0-...-X]
					closestToZero.max = m_basicOperations.equal(type2.min, defaultValue) ? m_basicOperations.unMinus(type2.precision) : type2.min; // type2.min or -type2.precision if that was 0
					closestToZero.min = closestToZero.max;
				} else {
					// divisor range [Y ... -X]
					closestToZero.max = type2.precision;
					closestToZero.min = m_basicOperations.unMinus(type2.precision);
				}

				result.max = m_basicOperations.div(type1.min, closestToZero.min);
				result.min = result.max;

				T tmp = m_basicOperations.div(type1.max, closestToZero.min);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;

				tmp = m_basicOperations.div(type1.min, closestToZero.max);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;

				tmp = m_basicOperations.div(type1.max, closestToZero.max);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;
				break;
			case mod:
				throw new ArgumentException(); // do this one level up
			case pow:
				// ok, this is bad. - just give up here
				result.max = result.typemax;
				result.min = result.typemin;
				break;
			case prod:
				// hmm, brute force.. is there some formula?
				result.max = m_basicOperations.prod(type1.min, type2.min);
				result.min = result.max;

				tmp = m_basicOperations.prod(type1.max, type2.min);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;

				tmp = m_basicOperations.prod(type1.min, type2.max);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;

				tmp = m_basicOperations.prod(type1.max, type2.max);
				result.max = m_basicOperations.smaller(result.max, tmp) ? tmp : result.max;
				result.min = m_basicOperations.greater(result.min, tmp) ? tmp : result.min;
				break;

			default:
				throw new NotImplementedException();
		}
		return result;
	}


	public Operations()
	{
		m_basicOperations = new BasicOps<T>();
	}

	public Operations(BasicOps<T> basicOperations)
	{
		m_basicOperations = basicOperations;
	}
}
