package maths;

import java.io.Serializable;
import java.math.BigInteger;
import java.math.BigDecimal;


public class BigFraction implements Cloneable, Comparable, Serializable {
    protected final BigInteger numerator_;
    protected final BigInteger denominator_;

    final public static BigFraction ZERO = new BigFraction("0/1");
    final public static BigFraction ONE = new BigFraction("1/1");


    public long intNumerator() {
        return numerator_.intValue();
    }

    public long intDenominator() {
        return denominator_.intValue();
    }

    public BigInteger numerator() {
        return numerator_;
    }

    public BigInteger denominator() {
        return denominator_;
    }

    public BigFraction(BigInteger num, BigInteger den) {

        boolean numNonnegative = gteq(num, BigInteger.ZERO);
        boolean denNonnegative = gteq(den, BigInteger.ZERO);
        BigInteger a = numNonnegative ? num : num.negate();
        BigInteger b = denNonnegative ? den : den.negate();
        BigInteger g = a.gcd(b);
        if (numNonnegative == denNonnegative) {
            numerator_ = a.divide(g);
        } else {
            numerator_ = a.negate().divide(g);
        }
        denominator_ = b.divide(g);
    }


    public BigFraction(long n) {
        this(BigInteger.valueOf(n), BigInteger.valueOf(1));
    }

    public BigFraction(BigInteger b) {
        this(b, BigInteger.valueOf(1));
    }

    public BigFraction(BigFraction f) {
        numerator_ = f.numerator();
        denominator_ = f.denominator();
    }

    public BigFraction(String s) {
        this(new BigInteger(s.substring(0, s.indexOf('/'))),
                new BigInteger(s.substring(s.indexOf('/') + 1)));
    }

    public BigFraction(long num, long den) {
        this(new BigInteger(Long.toString(num)),
                new BigInteger(Long.toString(den)));
    }

    //------------------
    // Override toString
    //------------------

    public String toString() {
        BigInteger b2 = denominator();

        if (b2.compareTo(BigInteger.ONE) != 0)
            return numerator().toString() + "/" + denominator().toString();
        else
            return numerator().toString();
    }

    //--------------------------------
    // Required to implement Cloneable
    //--------------------------------

    public Object clone() {
        return new BigFraction(this);
    }

    //----------------------------
    // Utility comparison routines
    //----------------------------

    private boolean gt(BigInteger x, BigInteger y) {
        return x.compareTo(y) > 0;
    }

    private boolean gteq(BigInteger x, BigInteger y) {
        return x.compareTo(y) >= 0;
    }

    private boolean gteq(BigInteger y) {
        return numerator_.compareTo(y) >= 0;
    }

    private boolean lt(BigInteger x, BigInteger y) {
        return x.compareTo(y) < 0;
    }

    private boolean lteq(BigInteger x, BigInteger y) {
        return x.compareTo(y) <= 0;
    }

    //------------
    // Get minimum
    //------------

    public BigFraction min(BigFraction val) {
        if (compareTo(val) <= 0) {
            return this;
        } else {
            return val;
        }
    }

    //------------
    // Get maximum
    //------------

    public BigFraction max(BigFraction val) {
        if (compareTo(val) > 0) {
            return this;
        } else {
            return val;
        }
    }

    //-------------------------------------------------------
    // Convert to BigDecimal
    // Rounding mode is any of BigDecimal.ROUND_xxx constants
    //-------------------------------------------------------

    public BigDecimal asBigDecimal(int scale, int roundingMode) {
        BigDecimal num = new BigDecimal(numerator());
        BigDecimal den = new BigDecimal(denominator());
        return num.divide(den, scale, roundingMode);
    }

    //------------------
    // Get negated value
    //------------------

    public BigFraction negate() {
        return new BigFraction(numerator().negate(), denominator());
    }

    //---------------------------
    // Get multiplicative inverse
    //---------------------------

    public BigFraction inverse() {
        return new BigFraction(denominator(), numerator());
    }

    //----
    // Add
    //----

    public BigFraction pow(int d) {
        BigInteger an = numerator();
        BigInteger ad = denominator();
        return new BigFraction(an.pow(d), (ad.pow(d)));
    }

    public BigFraction add(BigFraction b) {
        BigInteger an = numerator();
        BigInteger ad = denominator();
        BigInteger bn = b.numerator();
        BigInteger bd = b.denominator();
        return new BigFraction(an.multiply(bd).add(bn.multiply(ad)), ad.multiply(bd));
    }

    public BigFraction add(BigInteger n) {
        return add(new BigFraction(n, BigInteger.ONE));
    }

    public BigFraction add(long n) {
        return add(new BigInteger(Long.toString(n)));
    }

    //---------
    // Subtract
    //---------

    public BigFraction subtract(BigFraction b) {
        BigInteger an = numerator();
        BigInteger ad = denominator();
        BigInteger bn = b.numerator();
        BigInteger bd = b.denominator();
        return new BigFraction(an.multiply(bd).subtract(bn.multiply(ad)), ad.multiply(bd));
    }

    public BigFraction subtract(BigInteger n) {
        return subtract(new BigFraction(n, BigInteger.ONE));
    }

    public BigFraction subtract(long n) {
        return subtract(new BigInteger(Long.toString(n)));
    }

    //---------
    // Multiply
    //---------

    public BigFraction multiply(BigFraction b) {
        BigInteger an = numerator();
        BigInteger ad = denominator();
        BigInteger bn = b.numerator();
        BigInteger bd = b.denominator();
        return new BigFraction(an.multiply(bn), ad.multiply(bd));
    }

    public BigFraction multiply(BigInteger n) {
        return multiply(new BigFraction(n, BigInteger.ONE));
    }

    public BigFraction multiply(long n) {
        return multiply(new BigInteger(Long.toString(n)));
    }

    //-------
    // Divide
    //-------

    public BigFraction divide(BigFraction b) {
        BigInteger an = numerator();
        BigInteger ad = denominator();
        BigInteger bn = b.numerator();
        BigInteger bd = b.denominator();
        return new BigFraction(an.multiply(bd), ad.multiply(bn));
    }

    public BigFraction divide(BigInteger n) {
        return divide(new BigFraction(n, BigInteger.ONE));
    }

    public BigFraction divide(long n) {
        return divide(new BigInteger(Long.toString(n)));
    }

    public BigFraction sqrt() {
        if (!gteq(BigInteger.ZERO))
            return null;

        BigInteger an = numerator();
        BigInteger ad = denominator();
        an = BigSquareRoot.sqrtI(an);
        ad = BigSquareRoot.sqrtI(ad);
        if (an != null && ad != null)
            return new BigFraction(an, ad);

        return null;
    }
    //---------------------------------
    // Required to implement Comparable
    //---------------------------------

    public boolean isZero() {
        return numerator().compareTo(BigInteger.ZERO) == 0;
    }

    public int compareTo(Object other) {
        BigFraction b = (BigFraction) (other);
        BigInteger an = numerator();
        BigInteger ad = denominator();
        BigInteger bn = b.numerator();
        BigInteger bd = b.denominator();
        BigInteger left = an.multiply(bd);
        BigInteger right = bn.multiply(ad);
        if (lt(left, right)) {
            return -1;
        }
        if (left.equals(right)) {
            return 0;
        } else {
            return 1;
        }
    }

    public int compareTo(BigInteger n) {
        Object obj = new BigFraction(n, BigInteger.ONE);
        return compareTo(obj);
    }

    //----------------
    // Override equals
    //----------------

    public boolean equals(Object other) {
        return compareTo((BigFraction) other) == 0;
    }

    public boolean equals(BigInteger n) {
        return compareTo(n) == 0;
    }

    public boolean equals(long n) {
        return equals(new BigInteger(Long.toString(n)));
    }

    //------------------
    // Override hashCode
    //------------------

    public int hashCode() {
        int num = numerator().intValue();
        int den = denominator().intValue();
        return num ^ den;
    }
}
