package aalg.algebra;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;

/**
 * Field of rational numbers.
 * @author Ing. Petr Ležák
 */
public class RationalField extends FieldImpl<RationalFieldElement> {
	
	private static final RationalFieldElement ONE = new RationalFieldElement (BigInteger.ONE, BigInteger.ONE);
	private static final RationalFieldElement ZERO = new RationalFieldElement (BigInteger.ZERO, BigInteger.ONE);
	
	public static final RationalField INSTANCE = new RationalField();
	
	/**
	 * Private constructor.
	 */
	private RationalField() {
	}

	@Override
	public RationalFieldElement readElement(final InputStream stream) throws IOException {
		final BigInteger numerator = Utils.readNumber(stream);
		final BigInteger denominator = Utils.readNumber(stream);
		
		return new RationalFieldElement(numerator, denominator);
	}

	@Override
	public void writeElement(final RationalFieldElement element, final OutputStream stream) throws IOException {
		Utils.writeNumber(element.getNumerator(), stream);
		Utils.writeNumber(element.getDenominator(), stream);
	}

	@Override
	public Iterable<RationalFieldElement> getElements() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean containsElement(final RationalFieldElement element) {
		return true;
	}

	@Override
	public BigInteger getOrder() {
		return null;
	}

	@Override
	public RationalFieldElement add(final RationalFieldElement a, final RationalFieldElement b) {
		final BigInteger numerA = a.getNumerator();

		if (numerA.equals(BigInteger.ZERO))
			return b;   // Speedup: 0+b = b 
		
		final BigInteger numerB = b.getNumerator();

		if (numerB.equals(BigInteger.ZERO))
			return a;   // Speedup: a+0 = a
		
		final BigInteger denomA = a.getDenominator();
		final BigInteger denomB = b.getDenominator();
		
		final BigInteger part1 = numerA.multiply(denomB);
		final BigInteger part2 = numerB.multiply(denomA);
		final BigInteger numerator = part1.add (part2);
		final BigInteger denominator = denomA.multiply(denomB);
		
		return RationalFieldElement.getNormalizedElement(numerator, denominator);
	}

	@Override
	public RationalFieldElement multiply(final RationalFieldElement a, final RationalFieldElement b) {
		final BigInteger numerA = a.getNumerator();
		final BigInteger numerB = b.getNumerator();
		
		if (numerA.equals(BigInteger.ZERO) || numerB.equals(BigInteger.ZERO))
			return ZERO;   // Speedup: a*0=0*b=0 

		final BigInteger numerator = numerA.multiply(numerB);
		final BigInteger denominator = a.getDenominator().multiply(b.getDenominator());
		
		return RationalFieldElement.getNormalizedElement(numerator, denominator);
	}

	@Override
	public RationalFieldElement subtract(final RationalFieldElement a, final RationalFieldElement b) {
		final BigInteger numerA = a.getNumerator();
		final BigInteger numerB = b.getNumerator();
		
		if (numerB.equals(BigInteger.ZERO))
			return a;   // Speedup: a-0=a 

		final BigInteger denomA = a.getDenominator();
		final BigInteger denomB = b.getDenominator();
		final BigInteger part1 = numerA.multiply(denomB);
		final BigInteger part2 = numerB.multiply(denomA);
		final BigInteger numerator = part1.subtract (part2);
		final BigInteger denominator = denomA.multiply(denomB);
		
		return RationalFieldElement.getNormalizedElement(numerator, denominator);
	}
	
	@Override
	public RationalFieldElement divide (final RationalFieldElement a, final RationalFieldElement b) {
		final BigInteger numerA = a.getNumerator();
		final BigInteger numerB = b.getNumerator();
		
		if (numerB.equals(BigInteger.ZERO))
			throw new RuntimeException("Division by zero");
		
		if (numerA.equals(BigInteger.ZERO))
			return ZERO;   // Speedup: 0/b=0

		final BigInteger numerator = numerA.multiply(b.getDenominator());
		final BigInteger denominator = a.getDenominator().multiply(numerB);
		
		return RationalFieldElement.getNormalizedElement(numerator, denominator);
	}

	@Override
	public RationalFieldElement additiveInverse(final RationalFieldElement x) {
		return new RationalFieldElement(x.getNumerator().negate(), x.getDenominator());
	}

	@Override
	public RationalFieldElement multiplicativeInverse(final RationalFieldElement x) {
		return RationalFieldElement.getNormalizedElement(x.getDenominator(), x.getNumerator());
	}

	@Override
	public RationalFieldElement getAdditiveIdentity() {
		return ZERO;
	}

	@Override
	public RationalFieldElement getMultiplicativeIdentity() {
		return ONE;
	}

	/**
	 * Parses given string to element.
	 * @param string string representation
	 * @return element
	 */
	@Override
	public RationalFieldElement elementFromString(final String string) {
		final String[] tokens = string.split("/");
		
		if (tokens.length == 1)
			return new RationalFieldElement(new BigInteger(tokens[0]), BigInteger.ONE);

		if (tokens.length == 2)
			return new RationalFieldElement(new BigInteger(tokens[0]), new BigInteger(tokens[1]));
		
		throw new RuntimeException("Wrong format of rational field element");
	}

	@Override
	public BigInteger getElementIndex(final RationalFieldElement element) {
		return null;
	}

}
