package aalg.algebra;

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

/**
 * Implementation of prime field. 
 * @author Ing. Petr Ležák
 */
public class PrimeField extends FieldImpl<PrimeFieldElement> {
		
	private final BigInteger prime;
	
	
	public PrimeField (final BigInteger prime) {
		if (!prime.isProbablePrime(20))
			throw new RuntimeException("Given number is not prime");
		
		this.prime = prime;
	}

	@Override
	public PrimeFieldElement add(final PrimeFieldElement a, final PrimeFieldElement b) {
		final BigInteger aNum = a.getNumber();
		final BigInteger bNum = b.getNumber();
		final BigInteger sum = aNum.add(bNum);
		
		return new PrimeFieldElement(sum.mod(prime));
	}


	@Override
	public PrimeFieldElement multiply(final PrimeFieldElement a, final PrimeFieldElement b) {
		final BigInteger aNum = a.getNumber();
		final BigInteger bNum = b.getNumber();
		final BigInteger product = aNum.multiply(bNum);
		
		return new PrimeFieldElement(product.mod(prime));
	}


	@Override
	public PrimeFieldElement additiveInverse(final PrimeFieldElement x) {
		final BigInteger xNum = x.getNumber();
		final BigInteger opposite = xNum.negate();
		
		return new PrimeFieldElement(opposite.mod(prime));
	}


	@Override
	public PrimeFieldElement multiplicativeInverse(final PrimeFieldElement x) {
		final BigInteger xNum = x.getNumber();
		final BigInteger inverse = xNum.modInverse(prime);
		
		return new PrimeFieldElement(inverse);
	}

	@Override
	public PrimeFieldElement getAdditiveIdentity() {
		return PrimeFieldElement.ZERO;
	}

	@Override
	public PrimeFieldElement getMultiplicativeIdentity() {
		return PrimeFieldElement.ONE;
	}

	@Override
	public PrimeFieldElement readElement(final InputStream stream) throws IOException {
		final BigInteger number = Utils.readNumber(stream);
		
		return new PrimeFieldElement(number);
	}

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

	public BigInteger getPrime() {
		return prime;
	}
	
	@Override
	public boolean containsElement (final PrimeFieldElement element) {
		final BigInteger number = element.getNumber();
		
		return number.compareTo(BigInteger.ZERO) >= 0 && number.compareTo(prime) < 0;
	}

	@Override
	public Iterable<PrimeFieldElement> getElements() {
		return new PrimeFieldIterable(this);
	}

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

	@Override
	public PrimeFieldElement elementFromString(final String string) {
		return new PrimeFieldElement(new BigInteger(string));
	}

	@Override
	public BigInteger getElementIndex(final PrimeFieldElement element) {
		return element.getNumber();
	}

}
