package org.bouncycastle.pkcs;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.operator.ContentVerifier;
import org.bouncycastle.operator.ContentVerifierProvider;

/**
 * Holding class for a PKCS#10 certification request.
 */
public class PKCS10CertificationRequest
{
    private static Attribute[] EMPTY_ARRAY = new Attribute[0];

    private CertificationRequest certificationRequest;

    private static CertificationRequest parseBytes(byte[] encoding)
        throws IOException
    {
        try
        {
            return CertificationRequest.getInstance(ASN1Primitive.fromByteArray(encoding));
        }
        catch (ClassCastException e)
        {
            throw new PKCSIOException("malformed data: " + e.getMessage(), e);
        }
        catch (IllegalArgumentException e)
        {
            throw new PKCSIOException("malformed data: " + e.getMessage(), e);
        }
    }

    /**
     * Create a PKCS10CertificationRequestHolder from an underlying ASN.1 structure.
     *
     * @param certificationRequest the underlying ASN.1 structure representing a request.
     */
    public PKCS10CertificationRequest(CertificationRequest certificationRequest)
    {
         this.certificationRequest = certificationRequest;
    }

    /**
     * Create a PKCS10CertificationRequestHolder from the passed in bytes.
     *
     * @param encoded BER/DER encoding of the CertificationRequest structure.
     * @throws IOException in the event of corrupted data, or an incorrect structure.
     */
    public PKCS10CertificationRequest(byte[] encoded)
        throws IOException
    {
        this(parseBytes(encoded));
    }

    /**
     * Return the underlying ASN.1 structure for this request.
     *
     * @return a CertificateRequest object.
     */
    public CertificationRequest toASN1Structure()
    {
         return certificationRequest;
    }

    /**
     * Return the subject on this request.
     *
     * @return the X500Name representing the request's subject.
     */
    public X500Name getSubject()
    {
        return X500Name.getInstance(certificationRequest.getCertificationRequestInfo().getSubject());
    }

    /**
     * Return the details of the signature algorithm used to create this request.
     *
     * @return the AlgorithmIdentifier describing the signature algorithm used to create this request.
     */
    public AlgorithmIdentifier getSignatureAlgorithm()
    {
        return certificationRequest.getSignatureAlgorithm();
    }

    /**
     * Return the bytes making up the signature associated with this request.
     *
     * @return the request signature bytes.
     */
    public byte[] getSignature()
    {
        return certificationRequest.getSignature().getOctets();
    }

    /**
     * Return the SubjectPublicKeyInfo describing the public key this request is carrying.
     *
     * @return the public key ASN.1 structure contained in the request.
     */
    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
    {
        return certificationRequest.getCertificationRequestInfo().getSubjectPublicKeyInfo();
    }

    /**
     * Return the attributes, if any associated with this request.
     *
     * @return an array of Attribute, zero length if none present.
     */
    public Attribute[] getAttributes()
    {
        ASN1Set attrSet = certificationRequest.getCertificationRequestInfo().getAttributes();

        if (attrSet == null)
        {
            return EMPTY_ARRAY;
        }

        Attribute[] attrs = new Attribute[attrSet.size()];

        for (int i = 0; i != attrSet.size(); i++)
        {
            attrs[i] = Attribute.getInstance(attrSet.getObjectAt(i));
        }

        return attrs;
    }

    /**
     * Return an  array of attributes matching the passed in type OID.
     *
     * @param type the type of the attribute being looked for.
     * @return an array of Attribute of the requested type, zero length if none present.
     */
    public Attribute[] getAttributes(ASN1ObjectIdentifier type)
    {
        ASN1Set    attrSet = certificationRequest.getCertificationRequestInfo().getAttributes();

        if (attrSet == null)
        {
            return EMPTY_ARRAY;
        }
        
        List list = new ArrayList();

        for (int i = 0; i != attrSet.size(); i++)
        {
            Attribute attr = Attribute.getInstance(attrSet.getObjectAt(i));
            if (attr.getAttrType().equals(type))
            {
                list.add(attr);
            }
        }

        if (list.size() == 0)
        {
            return EMPTY_ARRAY;
        }

        return (Attribute[])list.toArray(new Attribute[list.size()]);
    }

    public byte[] getEncoded()
        throws IOException
    {
        return certificationRequest.getEncoded();
    }

    /**
     * Validate the signature on the PKCS10 certification request in this holder.
     *
     * @param verifierProvider a ContentVerifierProvider that can generate a verifier for the signature.
     * @return true if the signature is valid, false otherwise.
     * @throws PKCSException if the signature cannot be processed or is inappropriate.
     */
    public boolean isSignatureValid(ContentVerifierProvider verifierProvider)
        throws PKCSException
    {
        CertificationRequestInfo requestInfo = certificationRequest.getCertificationRequestInfo();

        ContentVerifier verifier;

        try
        {
            verifier = verifierProvider.get(certificationRequest.getSignatureAlgorithm());

            OutputStream sOut = verifier.getOutputStream();

            sOut.write(requestInfo.getEncoded(ASN1Encoding.DER));

            sOut.close();
        }
        catch (Exception e)
        {
            throw new PKCSException("unable to process signature: " + e.getMessage(), e);
        }

        return verifier.verify(this.getSignature());
    }

    public boolean equals(Object o)
    {
        if (o == this)
        {
            return true;
        }

        if (!(o instanceof PKCS10CertificationRequest))
        {
            return false;
        }

        PKCS10CertificationRequest other = (PKCS10CertificationRequest)o;

        return this.toASN1Structure().equals(other.toASN1Structure());
    }

    public int hashCode()
    {
        return this.toASN1Structure().hashCode();
    }
}
