package aalg.algebra;

import java.math.BigInteger;
import java.security.InvalidParameterException;

/**
 * Rational field expressed as numerator / denominator.
 * @author Ing. Petr Ležák
 */
public class RationalFieldElement implements IElement {
	
	// Numerator and denominator of the rational number.
	// Invariants:
	//   denominator > 0
	//   gcd (numerator, denominator) = 1
	private final BigInteger numerator;
	private final BigInteger denominator;
	
	/**
	 * Creates rational field element.
	 * Values are NOT normalized so invariants must be fulfilled.
	 * @param numerator numerator of the number
	 * @param denominator denominator of the number
	 */
	public RationalFieldElement (final BigInteger numerator, final BigInteger denominator) {
		this.numerator = numerator;
		this.denominator = denominator;
	}

	/**
	 * Returns numerator.
	 * @return numerator
	 */
	public BigInteger getNumerator() {
		return numerator;
	}

	/**
	 * Returns denominator.
	 * @return denominator
	 */
	public BigInteger getDenominator() {
		return denominator;
	}
	
	/**
	 * Returns normalized element for any numerator and nonzero denominator.
	 * @param numerator numerator
	 * @param denominator nonzero denominator
	 * @return rational field element with value numerator / denominator that fulfills invariants 
	 */
	public static RationalFieldElement getNormalizedElement (final BigInteger numerator, final BigInteger denominator) {
		if (denominator.equals(BigInteger.ONE))
			return new RationalFieldElement(numerator, denominator);   // Common case - integer
		
		if (denominator.equals(BigInteger.ZERO))
			throw new InvalidParameterException("Zero denominator");
		
		final BigInteger gcd = numerator.gcd(denominator);
		final BigInteger signedGcd = (denominator.compareTo(BigInteger.ZERO) >= 0) ? gcd : gcd.negate();
		
		if (signedGcd.equals(BigInteger.ONE))
			return new RationalFieldElement(numerator, denominator);
		else
			return new RationalFieldElement(numerator.divide(signedGcd), denominator.divide(signedGcd));
	}
	
	@Override
	public boolean equals (final Object obj) {
		if (!(obj instanceof RationalFieldElement))
			return false;
		
		final RationalFieldElement cmp = (RationalFieldElement) obj;
		
		return numerator.equals (cmp.numerator) && denominator.equals (cmp.denominator);
	}
	
	@Override
	public int hashCode() {
		return numerator.hashCode() ^ denominator.hashCode();
	}
	
	@Override
	public String toString() {
		if (denominator.equals(BigInteger.ONE))
			return numerator.toString();
		else
			return numerator + "/" + denominator;
	}
	
	/**
	 * Verifies that number is integer.
	 * @return true if number is integer, false if not
	 */
	public boolean isInteger() {
		return denominator.equals(BigInteger.ONE);
	}
}
