package org.bouncycastle.asn1.x509;

import java.util.Enumeration;
import java.util.NoSuchElementException;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.ASN1UTCTime;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.x500.X500Name;

/**
 * PKIX RFC-2459 - TBSCertList object.
 * <pre>
 * TBSCertList  ::=  SEQUENCE  {
 *      version                 Version OPTIONAL,
 *                                   -- if present, shall be v2
 *      signature               AlgorithmIdentifier,
 *      issuer                  Name,
 *      thisUpdate              Time,
 *      nextUpdate              Time OPTIONAL,
 *      revokedCertificates     SEQUENCE OF SEQUENCE  {
 *           userCertificate         CertificateSerialNumber,
 *           revocationDate          Time,
 *           crlEntryExtensions      Extensions OPTIONAL
 *                                         -- if present, shall be v2
 *                                }  OPTIONAL,
 *      crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
 *                                         -- if present, shall be v2
 *                                }
 * </pre>
 */
public class TBSCertList
    extends ASN1Object
{
    public static class CRLEntry
        extends ASN1Object
    {
        ASN1Sequence  seq;

        Extensions    crlEntryExtensions;

        private CRLEntry(
            ASN1Sequence  seq)
        {
            if (seq.size() < 2 || seq.size() > 3)
            {
                throw new IllegalArgumentException("Bad sequence size: " + seq.size());
            }
            
            this.seq = seq;
        }

        public static CRLEntry getInstance(Object o)
        {
            if (o instanceof CRLEntry)
            {
                return ((CRLEntry)o);
            }
            else if (o != null)
            {
                return new CRLEntry(ASN1Sequence.getInstance(o));
            }

            return null;
        }

        public ASN1Integer getUserCertificate()
        {
            return ASN1Integer.getInstance(seq.getObjectAt(0));
        }

        public Time getRevocationDate()
        {
            return Time.getInstance(seq.getObjectAt(1));
        }

        public Extensions getExtensions()
        {
            if (crlEntryExtensions == null && seq.size() == 3)
            {
                crlEntryExtensions = Extensions.getInstance(seq.getObjectAt(2));
            }
            
            return crlEntryExtensions;
        }

        public ASN1Primitive toASN1Primitive()
        {
            return seq;
        }

        public boolean hasExtensions()
        {
            return seq.size() == 3;
        }
    }

    private static class RevokedCertificatesEnumeration
        implements Enumeration
    {
        private final Enumeration en;

        RevokedCertificatesEnumeration(Enumeration en)
        {
            this.en = en;
        }

        public boolean hasMoreElements()
        {
            return en.hasMoreElements();
        }

        public Object nextElement()
        {
            return CRLEntry.getInstance(en.nextElement());
        }
    }

    private static class EmptyEnumeration
        implements Enumeration
    {
        public boolean hasMoreElements()
        {
            return false;
        }

        public Object nextElement()
        {
            throw new NoSuchElementException("Empty Enumeration");
        }
    }

    ASN1Integer             version;
    AlgorithmIdentifier     signature;
    X500Name                issuer;
    Time                    thisUpdate;
    Time                    nextUpdate;
    ASN1Sequence            revokedCertificates;
    Extensions              crlExtensions;

    public static TBSCertList getInstance(
        ASN1TaggedObject obj,
        boolean          explicit)
    {
        return getInstance(ASN1Sequence.getInstance(obj, explicit));
    }

    public static TBSCertList getInstance(
        Object  obj)
    {
        if (obj instanceof TBSCertList)
        {
            return (TBSCertList)obj;
        }
        else if (obj != null)
        {
            return new TBSCertList(ASN1Sequence.getInstance(obj));
        }

        return null;
    }

    public TBSCertList(
        ASN1Sequence  seq)
    {
        if (seq.size() < 3 || seq.size() > 7)
        {
            throw new IllegalArgumentException("Bad sequence size: " + seq.size());
        }

        int seqPos = 0;

        if (seq.getObjectAt(seqPos) instanceof ASN1Integer)
        {
            version = ASN1Integer.getInstance(seq.getObjectAt(seqPos++));
        }
        else
        {
            version = null;  // version is optional
        }

        signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqPos++));
        issuer = X500Name.getInstance(seq.getObjectAt(seqPos++));
        // RFC 5280 sec. 5.1.2.3: the CRL issuer field MUST contain a non-empty
        // distinguished name. (issue #2010)
        if (issuer.size() == 0)
        {
            throw new IllegalArgumentException("CRL issuer is an empty distinguished name");
        }
        thisUpdate = Time.getInstance(seq.getObjectAt(seqPos++));

        if (seqPos < seq.size()
            && (seq.getObjectAt(seqPos) instanceof ASN1UTCTime
               || seq.getObjectAt(seqPos) instanceof ASN1GeneralizedTime
               || seq.getObjectAt(seqPos) instanceof Time))
        {
            nextUpdate = Time.getInstance(seq.getObjectAt(seqPos++));
        }

        if (seqPos < seq.size()
            && !(seq.getObjectAt(seqPos) instanceof ASN1TaggedObject))
        {
            revokedCertificates = ASN1Sequence.getInstance(seq.getObjectAt(seqPos++));
        }

        if (seqPos < seq.size()
            && seq.getObjectAt(seqPos) instanceof ASN1TaggedObject)
        {
            crlExtensions = Extensions.getInstance(ASN1Sequence.getInstance((ASN1TaggedObject)seq.getObjectAt(seqPos), true));
        }
    }

    public int getVersionNumber()
    {
        if (version == null)
        {
            return 1;
        }
        return version.intValueExact() + 1;
    }

    public ASN1Integer getVersion()
    {
        return version;
    }

    public AlgorithmIdentifier getSignature()
    {
        return signature;
    }

    public X500Name getIssuer()
    {
        return issuer;
    }

    public Time getThisUpdate()
    {
        return thisUpdate;
    }

    public Time getNextUpdate()
    {
        return nextUpdate;
    }

    public CRLEntry[] getRevokedCertificates()
    {
        if (revokedCertificates == null)
        {
            return new CRLEntry[0];
        }

        CRLEntry[] entries = new CRLEntry[revokedCertificates.size()];

        for (int i = 0; i < entries.length; i++)
        {
            entries[i] = CRLEntry.getInstance(revokedCertificates.getObjectAt(i));
        }
        
        return entries;
    }

    public Enumeration getRevokedCertificateEnumeration()
    {
        if (revokedCertificates == null)
        {
            return new EmptyEnumeration();
        }

        return new RevokedCertificatesEnumeration(revokedCertificates.getObjects());
    }

    public Extensions getExtensions()
    {
        return crlExtensions;
    }

    public ASN1Primitive toASN1Primitive()
    {
        ASN1EncodableVector v = new ASN1EncodableVector(7);

        if (version != null)
        {
            v.add(version);
        }
        v.add(signature);
        v.add(issuer);

        v.add(thisUpdate);
        if (nextUpdate != null)
        {
            v.add(nextUpdate);
        }

        // Add CRLEntries if they exist
        if (revokedCertificates != null)
        {
            v.add(revokedCertificates);
        }

        if (crlExtensions != null)
        {
            v.add(new DERTaggedObject(0, crlExtensions));
        }

        return new DERSequence(v);
    }
}
