package oclastview.visitors.arithSimp;

import java.util.List;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.SendSignalAction;
import org.eclipse.ocl.expressions.ExpressionsFactory;
import org.eclipse.ocl.expressions.IntegerLiteralExp;
import org.eclipse.ocl.expressions.NumericLiteralExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.RealLiteralExp;
import org.eclipse.ocl.utilities.PredefinedType;
import org.eclipse.ocl.utilities.UtilitiesPackage;
import org.eclipse.ocl.utilities.Visitable;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.EnumerationLiteral;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.State;

public class OCLArithSimplifier<C, O, P, EL, PM, S, COA, SSA, CT> extends OCLCloner<C, O, P, EL, PM, S, COA, SSA, CT> {

  protected OCLArithSimplifier(Environment env) {
    super(env);
  }
  
  public static OCLArithSimplifier<EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> getEcoreVersion() {
    Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> auxEnv = EcoreEnvironmentFactory.INSTANCE
        .createEnvironment();
    OCLArithSimplifier<EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> res = new OCLArithSimplifier<EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint>(
        auxEnv);
    return res;
  }

  public static OCLArithSimplifier<Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, org.eclipse.uml2.uml.Constraint> getUML2Version() {
    org.eclipse.ocl.uml.OCL umlocl = org.eclipse.ocl.uml.OCL.newInstance();
    Environment<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, org.eclipse.uml2.uml.CallOperationAction, org.eclipse.uml2.uml.SendSignalAction, org.eclipse.uml2.uml.Constraint, Class, EObject> auxEnv = umlocl
        .getEnvironment();
    OCLArithSimplifier<Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, org.eclipse.uml2.uml.Constraint> res = new OCLArithSimplifier(
        auxEnv);
    return res;
  }


  @Override
  protected Visitable handleOperationCallExp(
      OperationCallExp<C, O> callExp, Visitable sourceResult,
      List<Visitable> argumentResults) {
    int opcode = callExp.getOperationCode();
    if (!isArithmeticOp(callExp)) {
      return super.handleOperationCallExp(callExp, sourceResult, argumentResults);
    }
    OCLExpression<C> newSource = (OCLExpression<C>sourceResult;
    OCLExpression<C> newArg = (OCLExpression<C>argumentResults.get(0);
    if (!(newSource instanceof NumericLiteralExp)
        || !(newArg instanceof NumericLiteralExp)) {
      return super.handleOperationCallExp(callExp, sourceResult, argumentResults);
    }
    /*
     * if one of source or arg is not IntegerLiteralExp then promotion to
     * Real takes place
     */
    if (newSource instanceof IntegerLiteralExp
        && newArg instanceof IntegerLiteralExp) {
      Integer intSource = ((IntegerLiteralExp<C>newSource).getIntegerSymbol();
      Integer intArg = ((IntegerLiteralExp<C>newArg).getIntegerSymbol();
      OCLExpression<C> res = simplifyInteger(opcode, intSource, intArg, ((IntegerLiteralExp<C>newSource).getType());
      return res;
    }
    /* promote to Double, compute, and return */
    Double dSource = null;
    Double dArg = null;
    C type = null
    if (newSource instanceof IntegerLiteralExp) {
      dSource = ((IntegerLiteralExp<C>newSource).getIntegerSymbol()
          .doubleValue();
    else {
      dSource = ((RealLiteralExp<C>newSource).getRealSymbol().doubleValue();
      type = ((RealLiteralExp<C>newSource).getType();
    }
    if (newArg instanceof IntegerLiteralExp) {
      dArg = ((IntegerLiteralExp<C>newArg).getIntegerSymbol().doubleValue();
    else {
      dArg = ((RealLiteralExp<C>newArg).getRealSymbol().doubleValue();
      type = ((RealLiteralExp<C>newArg).getType();
    }
    OCLExpression<C> res = simplifyDouble(opcode, dSource, dArg, type);
    return res;
  }
  
  /**
   
   * Returns either a RealLiteralExp or InvalidLiteralExp
   @param type 
   
   */
  private OCLExpression<C> simplifyDouble(int opcode, Double source, Double arg, C type) {

    RealLiteralExp<C> res = ExpressionsFactory.eINSTANCE
        .createRealLiteralExp();

    if ((opcode == PredefinedType.PLUS|| (opcode == PredefinedType.MINUS)
        || (opcode == PredefinedType.TIMES)) {
      switch (opcode) {
      case PredefinedType.PLUS:
        res.setRealSymbol(source + arg);
        break;
      case PredefinedType.MINUS:
        res.setRealSymbol(source - arg);
        break;
      case PredefinedType.TIMES:
        res.setRealSymbol(source * arg);
        break;
      }
      return res;
    }

    // denominator of 0 means undefined
    double num = source;
    double denom = arg;
    Double d = (denom == 0.0null new Double(num / denom);
    if (d == null) {
      return ExpressionsFactory.eINSTANCE.createInvalidLiteralExp();
    }

    res.setRealSymbol(d);
    res.setType(type);
    return res;
  }

  /**
   
   * Returns either an IntegerLiteralExp, RealLiteralExp, or InvalidLiteralExp
   @param type 
   
   */
  private OCLExpression<C> simplifyInteger(int opcode, int intSource, int intArg, C type) {

    if ((opcode == PredefinedType.PLUS|| (opcode == PredefinedType.MINUS)
        || (opcode == PredefinedType.TIMES)) {
      IntegerLiteralExp<C> res = ExpressionsFactory.eINSTANCE
          .createIntegerLiteralExp();
      switch (opcode) {
      case PredefinedType.PLUS:
        res.setIntegerSymbol(intSource + intArg);
        break;
      case PredefinedType.MINUS:
        res.setIntegerSymbol(intSource - intArg);
        break;
      case PredefinedType.TIMES:
        res.setIntegerSymbol(intSource * intArg);
        break;
      }
      res.setType(type);
      return res;
    }

    // denominator of 0 means undefined
    double num = intSource;
    double denom = intArg;
    Double d = (denom == 0.0null new Double(num / denom);
    if (d == null) {
      return ExpressionsFactory.eINSTANCE.createInvalidLiteralExp();
    }

    if (opcode == PredefinedType.DIVIDE) {
      RealLiteralExp<C> rle = ExpressionsFactory.eINSTANCE
          .createRealLiteralExp();
      rle.setRealSymbol(d);
      return rle;
    }
    if (opcode == PredefinedType.DIV) {
      IntegerLiteralExp<C> res = ExpressionsFactory.eINSTANCE
          .createIntegerLiteralExp();
      res.setIntegerSymbol(intSource / intArg);
      return res;
    }

    // we won't get to this point, just to make the compiler happy
    return null;
  }

  private boolean isArithmeticOp(OperationCallExp<C, O> oc) {
    int opcode = oc.getOperationCode();
    boolean res = (opcode == PredefinedType.PLUS)
        || (opcode == PredefinedType.MINUS)
        || (opcode == PredefinedType.TIMES)
        || (opcode == PredefinedType.DIVIDE)
        || (opcode == PredefinedType.DIV);
    return res;
  }


}