package weiss.math;

import java.math.BigInteger;

public class BigRational
{
    public static final BigRational ZERO = new BigRational( );
    public static final BigRational ONE = new BigRational( "1" );

    public BigRational( String str )
    {
        if( str.length( ) == 0 )
            throw new IllegalArgumentException( "Zero-length string" );

        // Check for "/"
        int slashIndex = str.indexOf( '/' );
        if( slashIndex == -1 )
        {
            // no denominator... use 1
            den = BigInteger.ONE;
            num = new BigInteger( str.trim( ) );
        }
        else
        {
            den = new BigInteger( str.substring( slashIndex + 1 ).trim( ) );
            num = new BigInteger( str.substring( 0, slashIndex ).trim( ) );
            check00( ); fixSigns( ); reduce( );
        }
    }

    private void check00( )
    {
        if( num.equals( BigInteger.ZERO ) && den.equals( BigInteger.ZERO ) )
            throw new IllegalArgumentException( "0/0" );
    }

    // Ensure that the denominator is positive
    private void fixSigns( )
    {
        if( den.compareTo( BigInteger.ZERO ) < 0 )
        {
            num = num.negate( );
            den = den.negate( );
        }
    }

    // Divide num and den by their gcd
    private void reduce( )
    {
        BigInteger gcd = num.gcd( den );
        num = num.divide( gcd );
        den = den.divide( gcd );
    }

    public BigRational( BigInteger n, BigInteger d )
    {
        num = n;
        den = d;
        check00( ); fixSigns( ); reduce( );
    }

    public BigRational( BigInteger n )
    {
        this( n, BigInteger.ONE );
    }

    public BigRational( )
    {
        this( BigInteger.ZERO );
    }

    public BigRational abs( )
    {
        return new BigRational( num.abs( ), den );
    }

    public BigRational negate( )
    {
        return new BigRational( num.negate( ), den );
    }

    public BigRational add( BigRational other )
    {
        BigInteger newNumerator =
                          num.multiply( other.den ).add(
                          other.num.multiply( den ) );
        BigInteger newDenominator =
                            den.multiply( other.den );

        return new BigRational( newNumerator, newDenominator );
    }

    public BigRational subtract( BigRational other )
    {
        return add( other.negate( ) );
    }

    public BigRational multiply( BigRational other )
    {
        BigInteger newNumer = num.multiply( other.num );
        BigInteger newDenom = den.multiply( other.den );

        return new BigRational( newNumer, newDenom );
    }

    public BigRational divide( BigRational other )
    {
        if( other.num.equals( BigInteger.ZERO ) && num.equals( BigInteger.ZERO ) )
            throw new ArithmeticException( "ZERO DIVIDE BY ZERO" );

        BigInteger newNumer = num.multiply( other.den );
        BigInteger newDenom = den.multiply( other.num );

        return new BigRational( newNumer, newDenom );
    }

    public boolean equals( Object other )
    {
        if( ! ( other instanceof BigRational ) )
            return false;

        BigRational rhs = (BigRational) other;

        return num.equals( rhs.num ) && den.equals( rhs.den );
    }

    public String toString( )
    {
        if( den.equals( BigInteger.ZERO ) )
            if( num.compareTo( BigInteger.ZERO ) < 0 )
                return "-infinity";
            else
                return "infinity";

        if( den.equals( BigInteger.ONE ) )
            return num.toString( );
        else
            return num + "/" + den;
    }

    private BigInteger num;  // only this can be neg
    private BigInteger den;
}