package org.bouncycastle.jce.provider;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchProviderException;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInputStream;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERSequence;

/**
 * CertPath implementation for X.509 certificates.
 * <br />
 * <b>TODO: add PKCS #7 encoding support</b>
 **/
public  class PKIXCertPath
    extends CertPath
{
    static final List certPathEncodings;

    static
    {
        List encodings = new ArrayList();
        encodings.add("PkiPath");
        certPathEncodings = Collections.unmodifiableList( encodings );
    }

    private List certificates;

    /**
     * Creates a CertPath of the specified type.
     * This constructor is protected because most users should use
     * a CertificateFactory to create CertPaths.
     * @param type the standard name of the type of Certificatesin this path
     **/
    PKIXCertPath( List certificates )
    {
        super("X.509");
        this.certificates = new ArrayList( certificates );
    }

    /**
     * Creates a CertPath of the specified type.
     * This constructor is protected because most users should use
     * a CertificateFactory to create CertPaths.
     *
     * <b>TODO: implement PKCS7 decoding</b>
     *
     * @param type the standard name of the type of Certificatesin this path
     **/
    PKIXCertPath( InputStream inStream, String encoding)
        throws CertificateException
    {
        super("X.509");
        try {
            if ( encoding.equals( "PkiPath" ) )
            {
                DERInputStream derInStream = new DERInputStream(inStream);
                DERObject derObject = derInStream.readObject();
                if ( derObject == null || ! ( derObject instanceof ASN1Sequence ) )
                {
                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath" );
                }
                Enumeration e = ((ASN1Sequence)derObject).getObjects();
                InputStream certInStream;
                ByteArrayOutputStream outStream;
                DEROutputStream derOutStream;
                certificates = new ArrayList();
                CertificateFactory certFactory= CertificateFactory.getInstance( "X.509", "BC" );
                while ( e.hasMoreElements() ) {
                    outStream = new ByteArrayOutputStream();
                    derOutStream = new DEROutputStream(outStream);
        
                    derOutStream.writeObject(e.nextElement());
                    derOutStream.close();
    
                    certInStream = new ByteArrayInputStream(outStream.toByteArray());
                    certificates.add(0,certFactory.generateCertificate(certInStream));
                }
            }
            else
            {
            throw new CertificateException( "unsupported encoding" );
            }
        } catch ( IOException ex ) {
            throw new CertificateException( "IOException throw while decoding CertPath:\n" + ex.toString() ); 
        } catch ( NoSuchProviderException ex ) {
            throw new CertificateException( "BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString() ); 
        }
    }
    
    /**
     * Returns an iteration of the encodings supported by this
     * certification path, with the default encoding
     * first. Attempts to modify the returned Iterator via its
     * remove method result in an UnsupportedOperationException.
     *
     * @return an Iterator over the names of the supported encodings (as Strings)
     **/
    public Iterator getEncodings()
    {
        return certPathEncodings.iterator();
    }

    /**
     * Returns the encoded form of this certification path, using
     * the default encoding.
     *
     * @return the encoded bytes
     * @exception CertificateEncodingException if an encoding error occurs
     **/
    public byte[] getEncoded()
        throws CertificateEncodingException
    {
        Iterator iter = getEncodings();
        if ( iter.hasNext() )
        {
            Object enc = iter.next();
            if ( enc instanceof String )
            {
            return getEncoded((String)enc);
            }
        }
        return null;
    }

    /**
     * Returns the encoded form of this certification path, using
     * the specified encoding.
     *
     * <b>TODO: implement PKCS7 decoding</b>
     *
     * @param encoding the name of the encoding to use
     * @return the encoded bytes
     * @exception CertificateEncodingException if an encoding error
     * occurs or the encoding requested is not supported
     *
     **/
    public byte[] getEncoded(String encoding)
        throws CertificateEncodingException
    {
        DERObject encoded = null;
        if ( encoding.equals("PkiPath") )
        {
            ASN1EncodableVector v = new ASN1EncodableVector();

            // TODO check ListIterator  implementation for JDK 1.1
            ListIterator iter = certificates.listIterator(certificates.size());
            while ( iter.hasPrevious() )
            {
                v.add(getEncodedX509Certificate((X509Certificate)iter.previous()));
            }

            encoded = new DERSequence(v);
        }
        else
            throw new CertificateEncodingException( "unsupported encoding" );
    
        if ( encoded == null )
            return null;
    
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            DEROutputStream derOutStream = new DEROutputStream(outStream);
    
        try {
            derOutStream.writeObject( encoded );
            derOutStream.close();
        } catch ( IOException ex ) {
            throw new CertificateEncodingException( "IOExeption thrown: " + ex.toString() );
        }
    
        return outStream.toByteArray();
    }

    /**
     * Returns the list of certificates in this certification
     * path. The List returned must be immutable and thread-safe. 
     *
     * <b>TODO: return immutable List</b>
     *
     * @return an immutable List of Certificates (may be empty, but not null)
     **/
    public List getCertificates()
    {
        return new ArrayList( certificates );
    }

    /**
     * Return a DERObject containing the encoded certificate.
     *
     * @param cert the X509Certificate object to be encoded
     *
     * @return the DERObject
     **/
    private DERObject getEncodedX509Certificate( X509Certificate cert )
        throws CertificateEncodingException
    {
        try
        {
            ByteArrayInputStream inStream = new ByteArrayInputStream( cert.getEncoded() );
            DERInputStream derInStream = new DERInputStream( inStream );
            return derInStream.readObject();
        }
        catch ( IOException ex )
        {
            throw new CertificateEncodingException( "IOException caught while encoding certificate\n" + ex.toString() );
        }
    }
}
