package org.bouncycastle.tls;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;

/*
 * RFC 3546 3.3
 */
public class CertificateURL
{
    protected short type;
    protected Vector urlAndHashList;

    /**
     * @param type
     *            see {@link CertChainType} for valid constants.
     * @param urlAndHashList
     *            a {@link Vector} of {@link URLAndHash}.
     */
    public CertificateURL(short type, Vector urlAndHashList)
    {
        if (!CertChainType.isValid(type))
        {
            throw new IllegalArgumentException("'type' is not a valid CertChainType value");
        }
        if (urlAndHashList == null || urlAndHashList.isEmpty())
        {
            throw new IllegalArgumentException("'urlAndHashList' must have length > 0");
        }
        if (type == CertChainType.pkipath && urlAndHashList.size() != 1)
        {
            throw new IllegalArgumentException("'urlAndHashList' must contain exactly one entry when type is " + CertChainType.getText(type));
        }

        this.type = type;
        this.urlAndHashList = urlAndHashList;
    }

    /**
     * @return {@link CertChainType}
     */
    public short getType()
    {
        return type;
    }

    /**
     * @return a {@link Vector} of {@link URLAndHash} 
     */
    public Vector getURLAndHashList()
    {
        return urlAndHashList;
    }

    /**
     * Encode this {@link CertificateURL} to an {@link OutputStream}.
     *
     * @param output the {@link OutputStream} to encode to.
     * @throws IOException
     */
    public void encode(OutputStream output)
        throws IOException
    {
        TlsUtils.writeUint8(this.type, output);

        ListBuffer16 buf = new ListBuffer16();
        for (int i = 0; i < this.urlAndHashList.size(); ++i)
        {
            URLAndHash urlAndHash = (URLAndHash)this.urlAndHashList.elementAt(i);
            urlAndHash.encode(buf);
        }
        buf.encodeTo(output);
    }

    /**
     * Parse a {@link CertificateURL} from an {@link InputStream}.
     * 
     * @param context
     *            the {@link TlsContext} of the current connection.
     * @param input
     *            the {@link InputStream} to parse from.
     * @return a {@link CertificateURL} object.
     * @throws IOException
     */
    public static CertificateURL parse(TlsContext context, InputStream input)
        throws IOException
    {
        short type = TlsUtils.readUint8(input);
        if (!CertChainType.isValid(type))
        {
            throw new TlsFatalAlert(AlertDescription.decode_error);
        }

        int totalLength = TlsUtils.readUint16(input);
        if (totalLength < 1)
        {
            throw new TlsFatalAlert(AlertDescription.decode_error);
        }

        byte[] urlAndHashListData = TlsUtils.readFully(totalLength, input);

        ByteArrayInputStream buf = new ByteArrayInputStream(urlAndHashListData);

        Vector url_and_hash_list = new Vector();
        while (buf.available() > 0)
        {
            URLAndHash url_and_hash = URLAndHash.parse(context, buf);
            url_and_hash_list.addElement(url_and_hash);
        }

        if (type == CertChainType.pkipath && url_and_hash_list.size() != 1)
        {
            throw new TlsFatalAlert(AlertDescription.decode_error);
        }

        return new CertificateURL(type, url_and_hash_list);
    }

    // TODO Could be more generally useful
    static class ListBuffer16 extends ByteArrayOutputStream
    {
        ListBuffer16() throws IOException
        {
            // Reserve space for length
            TlsUtils.writeUint16(0,  this);
        }

        void encodeTo(OutputStream output) throws IOException
        {
            // Patch actual length back in
            int length = count - 2;
            TlsUtils.checkUint16(length);
            TlsUtils.writeUint16(length, buf, 0);
            output.write(buf, 0, count);
            buf = null;
        }
    }
}
