/*
This code is a a heavily modified version of Numbers from Rich Hickeys clojure core, see Numbers.java for copyright
 */

package com.googlecode.totallylazy.numbers;

import java.math.BigDecimal;
import java.math.BigInteger;

import static java.math.BigDecimal.ONE;

public final class BigDecimalOperators implements Operators<BigDecimal> {
    public static BigDecimalOperators Instance = new BigDecimalOperators();

    private BigDecimalOperators() {}

    public final Class<BigDecimal> forClass() {
        return BigDecimal.class;
    }

    public final int priority() {
        return 4;
    }

    @Override
    public Number absolute(BigDecimal value) {
        return value.abs();
    }

    public final Number negate(BigDecimal value) {
        return value.negate();
    }

    public final Number increment(BigDecimal value) {
        return value.add(ONE);
    }

    public final Number decrement(BigDecimal value) {
        return value.subtract(ONE);
    }

    public final boolean isZero(BigDecimal value) {
        return value.signum() == 0;
    }

    public final boolean isPositive(BigDecimal value) {
        return value.signum() > 0;
    }

    public final boolean isNegative(BigDecimal value) {
        return value.signum() < 0;
    }

    public final boolean equalTo(Number x, Number y) {
        return x.equals(decimal(y));
    }

    public final boolean lessThan(Number x, Number y) {
        return decimal(x).compareTo(decimal(y)) < 0;
    }

    public final Number add(Number x, Number y) {
        return decimal(x).add(decimal(y));
    }

    public final Number multiply(Number x, Number y) {
        return decimal(x).multiply(decimal(y));
    }

    public final Number divide(Number x, Number y) {
        return decimal(x).divide(decimal(y));
    }

    public final Number quotient(Number x, Number y) {
        return decimal(x).divideToIntegralValue(decimal(y));
    }

    public final Number remainder(Number x, Number y) {
        return decimal(x).remainder(decimal(y));
    }

    public static BigDecimal decimal(Number number) {
        if (number instanceof Ratio)
            return ((Ratio) number).decimalValue();
        if (number instanceof BigDecimal)
            return (BigDecimal) number;
        if (number instanceof BigInteger)
            return new BigDecimal((BigInteger) number);
        if (number instanceof Float)
            return BigDecimal.valueOf((Float) number);
        if (number instanceof Double)
            return BigDecimal.valueOf((Double) number);
        return BigDecimal.valueOf(number.longValue());
    }

    public static Number rationalize(BigDecimal number) {
        BigInteger unscaled = number.unscaledValue();
        int scale = number.scale();
        if (scale < 0) {
            return unscaled.multiply(BigInteger.TEN.pow(-scale));
        }
        return unscaled.divide(BigInteger.TEN.pow(scale));
    }
}