/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.mcmaster.cas.matlab2smt;

import java.math.BigDecimal;
import java.math.BigInteger;
import org.apache.commons.lang3.StringUtils;

/**
 *
 * @author matthew
 */
final public class CVC3Generator implements CheckerGenerator{

    // For operations that don't have specific signed/unsigned representations, use this function.
    private static String GetIndifferentFixedPointFunctionFor(MatlabBinaryOperation op) {
        switch (op) {
            case Addition:
                return "BVPLUS(";
            case Minus:
                return "BVSUB(";
            case Multiplication:
                return "BVMULT(";
        }
        throw new IllegalArgumentException("Operation " + op + " doesn't have a fixed point function!");
    }

    private static String GetSignedFixedPointFunctionFor(MatlabBinaryOperation op) {
        switch (op) {
            case Division:
                return "BVSDIV(";
            case GreaterThen:
                return "BVSGT(";
            case GreaterThenEqual:
                return "BVSGE(";
            case LessThen:
                return "BVSLT(";
            case LessThenEqual:
                return "BVSLE(";
        }
        return GetIndifferentFixedPointFunctionFor(op);
    }

    private static String GetUnsignedFixedPointFunctionFor(MatlabBinaryOperation op) {
        switch (op) {
            case Division:
                return "BVUDIV(";
            case GreaterThen:
                return "BVGT(";
            case GreaterThenEqual:
                return "BVGE(";
            case LessThen:
                return "BVLT(";
            case LessThenEqual:
                return "BVLE(";
        }
        return GetIndifferentFixedPointFunctionFor(op);
    }

    private static String GetInfixSymbolFor(MatlabBinaryOperation op) {
        switch (op) {
            case Addition:
                return "+";
            case Minus:
                return "-";
            case Multiplication:
                return "*";
            case Division:
                return "/";
            case GreaterThen:
                return ">";
            case GreaterThenEqual:
                return ">=";
            case LessThen:
                return "<";
            case LessThenEqual:
                return "<=";
            case Equals:
                return "=";
            case BooleanAnd:
                return "AND";
            case BooleanOr:
                return "OR";
        }
        throw new RuntimeException("Should never happen!");
    }

    protected static String ConvertToOutputOp(MatlabBinaryOperation op, VariableType type) {
        switch (OpTypeForVariableType(op, type)) {
            case Function:
                String func = GetSignedFixedPointFunctionFor(op);
                if (!((FixedPointVariableType) type).isSigned()) {
                    func = GetUnsignedFixedPointFunctionFor(op);
                }
                if (op == MatlabBinaryOperation.Addition || op == MatlabBinaryOperation.Multiplication) {
                    FixedPointVariableType typeF = (FixedPointVariableType) type;
                    return func + typeF.digits() + ",";
                } else {
                    return func;
                }
            case CenterOp:
                return GetInfixSymbolFor(op);
            default:
                throw new IllegalStateException("CVC3 Operation type asked for that doesn't exist!");
        }
    }

    private static OperationType OpTypeForVariableType(MatlabBinaryOperation op, VariableType type) {
        if (type instanceof FixedPointVariableType && op != MatlabBinaryOperation.Equals) {
            return OperationType.Function;
        } else {
            return OperationType.CenterOp;
        }
    }

    @Override
    public String GenerateBinaryOperation(MatlabBinaryOperation op, String lhsExp, String rhsExp, VariableType usedType) {
        switch (CVC3Generator.OpTypeForVariableType(op, usedType)) {
            case CenterOp:
                return "(" + lhsExp + " " + CVC3Generator.ConvertToOutputOp(op, usedType) + " " + rhsExp + ")";
            case Function:
                return CVC3Generator.ConvertToOutputOp(op, usedType) + lhsExp + "," + rhsExp + ")";
            default:
                throw new IllegalStateException("Cannot output expression operation for requested operation type.");
        }
    }

    private static String GetUnaryOperationSymbolFor(MatlabUnaryOperation op) {
        switch (op) {
            case Negation:
                return "NOT";
            case Minus:
                return "-";
        }
        throw new RuntimeException("Should never happen!  Asked for unhandled op: " + op + "!");
    }

    @Override
    public String GenerateUnaryOperation(MatlabUnaryOperation op, String expression, VariableType usedType) {
        switch (CVC3Generator.OpTypeForVariableType(op, usedType)) {
            case PrefixOp:
                return "(" + CVC3Generator.ConvertToOutputOp(op, usedType) + " " + expression + ")";
            case Function:
                return CVC3Generator.ConvertToOutputOp(op, usedType) + "(" + expression + ")";
            default:
                throw new IllegalStateException("Cannot output expression operation for requested operation type.");
        }
    }

    private static OperationType OpTypeForVariableType(MatlabUnaryOperation op, VariableType usedType) {
        if (usedType instanceof FixedPointVariableType) {
            return OperationType.Function;
        }
        return OperationType.PrefixOp;
    }

    private static String ConvertToOutputOp(MatlabUnaryOperation op, VariableType usedType) {
        switch (op) {
            case Negation:
                return "NOT";
            case Minus:
                if (usedType instanceof FixedPointVariableType) {
                    return "BVUMINUS";
                } else {
                    return "-";
                }
        }
        throw new IllegalArgumentException("Unhandled operation: " + op);
    }

    public String GenerateVariableType(VariableType type) {
        if (type instanceof RealVariableType) {
            return "REAL";
        } else if (type instanceof FixedPointVariableType) {
            return "BITVECTOR(" + ((FixedPointVariableType) type).digits() + ")";
        } else if (type instanceof BooleanVariableType) {
            return "BOOLEAN";
        } else {
            throw new IllegalArgumentException("CVC3 Doesn't handle this variable type (Class: " + type.getClass().getName() + ")");
        }
    }

    @Override
    public String GenerateVariableDecleration(Variable var) {
        return var.name() + ":" + GenerateVariableType(var.type()) + ";";
    }

    @Override
    public String GenerateLiterlaValue(String value, VariableType requestedType) {
        if (requestedType instanceof RealVariableType) {
            if (value.contains(".")) {
                String[] fraction = value.split("\\.");

                String res = "(";
                res += StringUtils.join(fraction, "");
                res += "/1" + StringUtils.repeat('0', fraction[1].length()) + ")";

                return res;
            } else {
                return value;
            }
        } else if (requestedType instanceof FixedPointVariableType) {
            FixedPointVariableType type = (FixedPointVariableType) requestedType;
            BigDecimal decimal = new BigDecimal(value);
            decimal = decimal.multiply(BigDecimal.valueOf(1 << type.fractionPart()));
            BigInteger intOut = decimal.toBigIntegerExact();

            //This gets the two's compliment representation.  Ugly for loop, but best I could find.
            String bits = "";
            for (int i = intOut.bitLength() - 1; i >= 0; --i) {
                bits += (intOut.testBit(i)) ? '1' : '0';
            }

            if (intOut.signum() < 0) {
                return "0bin" + StringUtils.repeat('1', type.digits() - bits.length()) + bits;
            } else {
                return "0bin" + StringUtils.repeat('0', type.digits() - bits.length()) + bits;
            }
        } else {
            throw new IllegalArgumentException("Can't cast literal to that type!");
        }
    }
}
