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

public final class RatioOperators implements Operators<Ratio> {
    public static RatioOperators Instance = new RatioOperators();

    private RatioOperators() {}
    
    public final Class<Ratio> forClass() {
        return Ratio.class;
    }

    public final int priority() {
        return 5;
    }

    @Override
    public Number absolute(Ratio value) {
        return isNegative(value) ? negate(value) : value ;
    }

    public final Number negate(Ratio value) {
        return new Ratio(value.numerator.negate(), value.denominator);
    }

    public final Number increment(Ratio value) {
        return Numbers.add(value, 1);
    }

    public final Number decrement(Ratio value) {
        return Numbers.add(value, -1);
    }

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

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

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

    public final boolean equalTo(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        return rx.numerator.equals(ry.numerator)
                && rx.denominator.equals(ry.denominator);
    }

    public final boolean lessThan(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        return Numbers.lessThan(rx.numerator.multiply(ry.denominator), ry.numerator.multiply(rx.denominator));
    }

    public final Number add(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        return divide(ry.numerator.multiply(rx.denominator)
                .add(rx.numerator.multiply(ry.denominator))
                , ry.denominator.multiply(rx.denominator));
    }

    public final Number multiply(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        return Numbers.divide(ry.numerator.multiply(rx.numerator)
                , ry.denominator.multiply(rx.denominator));
    }

    public final Number divide(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        return Numbers.divide(ry.denominator.multiply(rx.numerator)
                , ry.numerator.multiply(rx.denominator));
    }

    public final Number quotient(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        BigInteger q = rx.numerator.multiply(ry.denominator).divide(
                rx.denominator.multiply(ry.numerator));
        return Numbers.reduce(q);
    }

    public final Number remainder(Number x, Number y) {
        Ratio rx = toRatio(x);
        Ratio ry = toRatio(y);
        BigInteger q = rx.numerator.multiply(ry.denominator).divide(
                rx.denominator.multiply(ry.numerator));
        return Numbers.subtract(x, Numbers.multiply(q, y));
    }

    public static Ratio toRatio(Number value) {
        if (value instanceof Ratio)
            return (Ratio) value;
        else if (value instanceof BigDecimal) {
            BigDecimal bx = (BigDecimal) value;
            BigInteger bv = bx.unscaledValue();
            int scale = bx.scale();
            if (scale < 0)
                return new Ratio(bv.multiply(BigInteger.TEN.pow(-scale)), BigInteger.ONE);
            else
                return new Ratio(bv, BigInteger.TEN.pow(scale));
        }
        return new Ratio(BigIntegerOperators.bigInteger(value), BigInteger.ONE);
    }

}
