package org.bouncycastle.asn1;

import java.io.IOException;
import java.io.InputStream;

public class BERTaggedObjectParser
    implements ASN1TaggedObjectParser
{
    private int _baseTag;
    private int _tagNumber;
    private InputStream _contentStream;

    private boolean _indefiniteLength;

    protected BERTaggedObjectParser(
        int         baseTag,
        int         tagNumber,
        InputStream contentStream)
    {
        _baseTag = baseTag;
        _tagNumber = tagNumber;
        _contentStream = contentStream;
        _indefiniteLength = contentStream instanceof IndefiniteLengthInputStream;
    }

    public boolean isConstructed()
    {
        return (_baseTag & DERTags.CONSTRUCTED) != 0;
    }

    public int getTagNo()
    {
        return _tagNumber;
    }
    
    public DEREncodable getObjectParser(
        int     tag,
        boolean isExplicit)
        throws IOException
    {
        if (isExplicit)
        {
            return new ASN1StreamParser(_contentStream).readObject();
        }
        else
        {
            switch (tag)
            {
            case DERTags.SET:
                if (_indefiniteLength)
                {
                    return new BERSetParser(new ASN1ObjectParser(_baseTag, _tagNumber, _contentStream));
                }
                else
                {
                    return new DERSet(loadVector(_contentStream)).parser();
                }
            case DERTags.SEQUENCE:
                if (_indefiniteLength)
                {
                    return new BERSequenceParser(new ASN1ObjectParser(_baseTag, _tagNumber, _contentStream));
                }
                else
                {
                    return new DERSequence(loadVector(_contentStream)).parser();
                }
            case DERTags.OCTET_STRING:
                if (_indefiniteLength || this.isConstructed())
                {
                    return new BEROctetStringParser(new ASN1ObjectParser(_baseTag, _tagNumber, _contentStream));
                }
                else
                {
                    return new DEROctetString(((DefiniteLengthInputStream)_contentStream).toByteArray()).parser();
                }
            }
        }

        throw new RuntimeException("implicit tagging not implemented");
    }

    private ASN1EncodableVector loadVector(InputStream in)
        throws IOException
    {
        ASN1StreamParser        aIn = new ASN1StreamParser(in);
        ASN1EncodableVector     v = new ASN1EncodableVector();
        DEREncodable            obj = aIn.readObject();

        while (obj != null)
        {
            v.add(obj.getDERObject());
            obj = aIn.readObject();
        }

        return v;
    }

    private ASN1EncodableVector rLoadVector(InputStream in)
    {
        try
        {
            return loadVector(in);
        }
        catch (IOException e)
        {
            throw new IllegalStateException(e.getMessage());
        }
    }

    public DERObject getDERObject()
    {
        if (_indefiniteLength)
        {
            ASN1EncodableVector v = rLoadVector(_contentStream);

            if (v.size() > 1)
            {
                return new BERTaggedObject(false, _tagNumber, new BERSequence(v));
            }
            else if (v.size() == 1)
            {
                return new BERTaggedObject(true, _tagNumber, v.get(0));
            }
            else
            {
                return new BERTaggedObject(false, _tagNumber, new BERSequence());
            }
        }
        else
        {
            if (this.isConstructed())
            {
                ASN1EncodableVector v = rLoadVector(_contentStream);

                if (v.size() == 1)
                {
                    return new DERTaggedObject(true, _tagNumber, v.get(0));
                }

                return new DERTaggedObject(false, _tagNumber, new DERSequence(v));                
            }

            try
            {
                return new DERTaggedObject(false, _tagNumber, new DEROctetString(((DefiniteLengthInputStream)_contentStream).toByteArray()));
            }
            catch (IOException e)
            {
                throw new IllegalStateException(e.getMessage());
            }
        }

    }
}
