package net.java.otr4j.io;

import net.java.otr4j.io.messages.*;

import org.bouncycastle.util.BigIntegers;

import java.io.*;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.interfaces.*;

import javax.crypto.interfaces.DHPublicKey;

public class OtrOutputStream extends FilterOutputStream implements SerializationConstants
{

	public OtrOutputStream(OutputStream out)
	{
		super(out);
	}

	private void writeNumber(int value, int length)
			throws IOException
	{
		byte[] b = new byte[length];
		for (int i = 0; i < length; i++) {
			int offset = (b.length - 1 - i) * 8;
			b[i] = (byte) ((value >>> offset) & 0xFF);
		}
		write(b);
	}

	public void writeBigInt(BigInteger bi)
			throws IOException
	{
		byte[] b = BigIntegers.asUnsignedByteArray(bi);
		writeData(b);
	}

	public void writeByte(int b)
			throws IOException
	{
		writeNumber(b, TYPE_LEN_BYTE);
	}

	public void writeData(byte[] b)
			throws IOException
	{
		int len = (b == null) ? 0 : b.length;
		writeNumber(len, DATA_LEN);
		if (len > 0)
			write(b);
	}

	public void writeInt(int i)
			throws IOException
	{
		writeNumber(i, TYPE_LEN_INT);

	}

	public void writeShort(int s)
			throws IOException
	{
		writeNumber(s, TYPE_LEN_SHORT);

	}

	public void writeMac(byte[] mac)
			throws IOException
	{
		if (mac == null || mac.length != TYPE_LEN_MAC)
			throw new IllegalArgumentException();

		write(mac);
	}

	public void writeCtr(byte[] ctr)
			throws IOException
	{
		if (ctr == null || ctr.length < 1)
			return;

		int i = 0;
		while (i < TYPE_LEN_CTR && i < ctr.length) {
			write(ctr[i]);
			i++;
		}
	}

	public void writeDHPublicKey(DHPublicKey dhPublicKey)
			throws IOException
	{
		byte[] b = BigIntegers.asUnsignedByteArray(dhPublicKey.getY());
		writeData(b);
	}

	public void writePublicKey(PublicKey pubKey)
			throws IOException
	{
		if (!(pubKey instanceof DSAPublicKey))
			throw new UnsupportedOperationException(
					"Key types other than DSA are not supported at the moment.");

		DSAPublicKey dsaKey = (DSAPublicKey) pubKey;
		writeShort(0);

		DSAParams dsaParams = dsaKey.getParams();
		writeBigInt(dsaParams.getP());
		writeBigInt(dsaParams.getQ());
		writeBigInt(dsaParams.getG());
		writeBigInt(dsaKey.getY());

	}

	public void writeTlvData(byte[] b)
			throws IOException
	{
		int len = (b == null) ? 0 : b.length;
		writeNumber(len, TLV_LEN);
		if (len > 0)
			write(b);
	}

	public void writeSignature(byte[] signature, PublicKey pubKey)
			throws IOException
	{
		if (!pubKey.getAlgorithm().equals("DSA"))
			throw new UnsupportedOperationException();
		out.write(signature);
	}

	public void writeMysteriousX(SignatureX x)
			throws IOException
	{
		writePublicKey(x.longTermPublicKey);
		writeInt(x.dhKeyID);
		writeSignature(x.signature, x.longTermPublicKey);
	}

	public void writeMysteriousX(SignatureM m)
			throws IOException
	{
		writeBigInt(m.localPubKey.getY());
		writeBigInt(m.remotePubKey.getY());
		writePublicKey(m.localLongTermPubKey);
		writeInt(m.keyPairID);
	}

	public void writeMysteriousT(MysteriousT t)
			throws IOException
	{
		writeShort(t.protocolVersion);
		writeByte(t.messageType);
		if (t.protocolVersion == 3) {
			writeInt(t.senderInstanceTag);
			writeInt(t.receiverInstanceTag);
		}
		writeByte(t.flags);

		writeInt(t.senderKeyID);
		writeInt(t.recipientKeyID);
		writeDHPublicKey(t.nextDH);
		writeCtr(t.ctr);
		writeData(t.encryptedMessage);
	}
}
