package net.java.otr4j.io;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.interfaces.DHPublicKey;

import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.io.messages.SignatureX;

public class OtrInputStream extends FilterInputStream implements
		SerializationConstants {

	public OtrInputStream(InputStream in) {
		super(in);
	}

	private int readNumber(int length) throws IOException {
		byte[] b = new byte[length];
		read(b);

		int value = 0;
		for (int i = 0; i < b.length; i++) {
			int shift = (b.length - 1 - i) * 8;
			value += (b[i] & 0x000000FF) << shift;
		}

		return value;
	}

	public int readByte() throws IOException {
		return readNumber(TYPE_LEN_BYTE);
	}

	public int readInt() throws IOException {
		return readNumber(TYPE_LEN_INT);
	}

	public int readShort() throws IOException {
		return readNumber(TYPE_LEN_SHORT);
	}

	public byte[] readCtr() throws IOException {
		byte[] b = new byte[TYPE_LEN_CTR];
		read(b);
		return b;
	}

	public byte[] readMac() throws IOException {
		byte[] b = new byte[TYPE_LEN_MAC];
		read(b);
		return b;
	}

	public BigInteger readBigInt() throws IOException {
		byte[] b = readData();
		return new BigInteger(1, b);
	}

	public byte[] readData() throws IOException {
		int dataLen = readNumber(DATA_LEN);
		byte[] b = new byte[dataLen];
		read(b);
		return b;
	}

	public PublicKey readPublicKey() throws IOException {
		int type = readShort();
		switch (type) {
		case 0:
			BigInteger p = readBigInt();
			BigInteger q = readBigInt();
			BigInteger g = readBigInt();
			BigInteger y = readBigInt();
			DSAPublicKeySpec keySpec = new DSAPublicKeySpec(y, p, q, g);
			KeyFactory keyFactory;
			try {
				keyFactory = KeyFactory.getInstance("DSA");
			} catch (NoSuchAlgorithmException e) {
				throw new IOException();
			}
			try {
				return keyFactory.generatePublic(keySpec);
			} catch (InvalidKeySpecException e) {
				throw new IOException();
			}
		default:
			throw new UnsupportedOperationException();
		}
	}

	public DHPublicKey readDHPublicKey() throws IOException {
		BigInteger gyMpi = readBigInt();
		try {
			return new OtrCryptoEngineImpl().getDHPublicKey(gyMpi);
		} catch (Exception ex) {
			throw new IOException();
		}
	}

	public byte[] readTlvData() throws IOException {
		int len = readNumber(TYPE_LEN_BYTE);

		byte[] b = new byte[len];
		in.read(b);
		return b;
	}

	public byte[] readSignature(PublicKey pubKey) throws IOException {
		if (!pubKey.getAlgorithm().equals("DSA"))
			throw new UnsupportedOperationException();

		DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey;
		DSAParams dsaParams = dsaPubKey.getParams();
		byte[] sig = new byte[dsaParams.getQ().bitLength() / 4];
		read(sig);
		return sig;
	}

	public SignatureX readMysteriousX() throws IOException {
		PublicKey pubKey = readPublicKey();
		int dhKeyID = readInt();
		byte[] sig = readSignature(pubKey);
		return new SignatureX(pubKey, dhKeyID, sig);
	}
}
