/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.jss.asn1;

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

import org.mozilla.jss.util.Assert;

/**
 * An ASN.1 SEQUENCE.  This class is an ordered collection of ASN.1 values.
 * It has an interface similar to a Java <code>Vector</code>.
 * Null entries may be added; they will be skipped when encoded.
 */
public class SEQUENCE extends SET implements ASN1Value {

    public static final Tag TAG = new Tag(Tag.Class.UNIVERSAL, 16);
    public Tag getTag() {
        return TAG;
    }

    public static Template getTemplate() {
        return new Template();
    }

    /**
     * Writes the DER encoding to the given output stream,
     * using the given implicit tag.
     */
    public void encode(Tag implicitTag, OutputStream ostream)
        throws IOException
    {
        BERencode(implicitTag, ostream);
    }

    // SET.Element and SEQUENCE.Element are identical types.  We could
    // have just reused SET.Element, but that would have been a bit
    // confusing for users.
    private static class Element extends SET.Element {
        public Element( ASN1Value val ) {
            super(val);
        }
        public Element( Tag implicitTag, ASN1Value val) {
            super(implicitTag, val);
        }
    }

/**
 * A class for constructing a <code>SEQUENCE</code> from its BER encoding.
 * It is an ordered collection of sub-templates.  Each sub-template can be
 * marked optional, or a default value can be given.
 */
public static class Template implements ASN1Template {

    private Vector<Element> elements = new Vector<>();

    private void addElement(Element el) {
        elements.addElement( el );
    }

    private void insertElementAt(Element e, int index) {
        elements.insertElementAt(e, index);
    }

    /**
     * Adds a sub-template to the end of this SEQUENCE template. For example,
     *  if the ASN.1 included:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      item        SubType,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addElement( new SubType.Template() );
     * </pre>
     * @param t Sub-template.
     */
    public void addElement( ASN1Template t ) {
        addElement( new Element(null, t, false) );
    }

    /**
     * Inserts the template at the given index.
     * @param t Sub-template.
     * @param index Index.
     */
    public void insertElementAt( ASN1Template t, int index )
    {
        insertElementAt( new Element(null, t, false), index );
    }

    /**
     * Adds a sub-template to the end of this SEQUENCE template, with the
     *  given implicit tag. For example, if the ASN.1 were:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      item        [0] IMPLICIT  SubType,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addElement( new Tag(0), new SubType.Template());
     * </pre>
     * @param implicitTag Implicit tag.
     * @param t Sub-template.
     */
    public void addElement( Tag implicitTag, ASN1Template t ) {
        addElement( new Element(implicitTag, t, false) );
    }

    /**
     * Inserts the template with the given implicit tag at the given index.
     * @param implicit Implicit tag.
     * @param t Sub-Template.
     * @param index Index.
     */
    public void insertElementAt( Tag implicit, ASN1Template t,
        int index )
    {
        insertElementAt( new Element(implicit, t, false), index );
    }

    /**
     * Adds an optional sub-template. For example, if the ASN.1 were:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      item        SubType OPTIONAL,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addOptionalElement( new SubType.Template() );
     * </pre>
     * @param t Optional sub-template.
     */
    public void addOptionalElement( ASN1Template t ) {
        addElement( new Element(null, t, true) );
    }

    /**
     * Inserts the optional template at the given index.
     * @param t Optional sub-template.
     * @param index Index.
     */
    public void insertOptionalElementAt( ASN1Template t, int index )
    {
        insertElementAt( new Element(null, t, true), index );
    }

    /**
     * Adds an optional sub-template with an implicit tag. For example,
     *  if the ASN.1 were:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      item        [0] IMPLICIT SubType OPTIONAL,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addOptionalElement( new SubType.Template() );
     * </pre>
     * @param implicitTag Implicit tag.
     * @param t Optional sub-template.
     */
    public void addOptionalElement( Tag implicitTag, ASN1Template t ) {
        addElement( new Element(implicitTag, t, true) );
    }

    /**
     * Inserts the optional template with the given default
     * value at the given index.
     * @param implicit Implicit tag.
     * @param t Optional sub-template.
     * @param index Index.
     */
    public void insertOptionalElementAt( Tag implicit, ASN1Template t,
        int index )
    {
        insertElementAt( new Element(implicit, t, true), index );
    }

    /**
     * Adds a sub-template with a default value. For example,
     *  if the ASN.1 were:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      version     INTEGER DEFAULT 1,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addElement( new INTEGER.Template(), new INTEGER(1) );
     * </pre>
     * @param t Sub-template.
     * @param def The default value for this field, which will be used if
     *      no value is supplied by the encoded structure. It must be of
     *      the same type as what the template would produce.
     */
    public void addElement( ASN1Template t, ASN1Value def ) {
        addElement( new Element(null, t, def) );
    }

    /**
     * Inserts the template with the given default
     * value at the given index.
     * @param t Sub-template.
     * @param def Default value.
     * @param index Index.
     */
    public void insertElementAt( ASN1Template t, ASN1Value def, int index )
    {
        insertElementAt( new Element(null, t, def), index );
    }

    /**
     * Adds a sub-template with a default value and an implicit tag.
     *  For example, if the ASN.1 were:
     * <pre>
     * MySequence ::= SEQUENCE {
     *      version     [0] IMPLICIT INTEGER DEFAULT 1,
     *      ... }
     * </pre>
     * the "item" element would be added to the MySequence template with:
     * <pre>
     *  mySequence.addElement( new Tag(0), new INTEGER.Template(),
     *      new INTEGER(1) );
     * </pre>
     * @param implicitTag Implicit tag.
     * @param t Sub-template.
     * @param def The default value for this field, which will be used if
     *      no value is supplied by the encoded structure. It must be of
     *      the same type as what the template would produce.
     */
    public void addElement( Tag implicitTag, ASN1Template t, ASN1Value def) {
        addElement( new Element(implicitTag, t, def) );
    }

    /**
     * Inserts the template with the given implicit tag and given default
     * value at the given index.
     * @param implicit Implicit tag.
     * @param t Sub-template.
     * @param def Default value.
     * @param index Index.
     */
    public void insertElementAt( Tag implicit, ASN1Template t, ASN1Value def,
        int index )
    {
        insertElementAt( new Element(implicit, t, def), index );
    }

    /**
     * Returns the implicit tag of the item stored at the given index.
     * May be NULL if no implicit tag was specified.
     * @param index Index.
     * @return Tag.
     */
    public Tag implicitTagAt( int index ) {
        return elements.elementAt(index).getImplicitTag();
    }

    /**
     * Returns the sub-template stored at the given index.
     * @param index Index.
     * @return Sub-template.
     */
    public ASN1Template templateAt( int index ) {
        return elements.elementAt(index).getTemplate();
    }

    /**
     * Returns whether the sub-template at the given index is optional.
     * @param index Index.
     * @return True if the sub-template is optional.
     */
    public boolean isOptionalAt( int index ) {
        return elements.elementAt(index).isOptional();
    }

    /**
     * Returns the default value for the sub-template at the given index.
     * May return NULL if no default value was specified.
     * @param index Index.
     * @return Default value.
     */
    public ASN1Value defaultAt( int index ) {
        return elements.elementAt(index).getDefault();
    }

    /**
     * @return The number of elements in this SEQUENCE template.
     */
    public int size() {
        return elements.size();
    }

    /**
     * Removes all sub-templates from this SEQUENCE template.
     */
    public void removeAllElements() {
        elements.removeAllElements();
    }

    /**
     * Removes the sub-template at the given index.
     * @param index Index.
     */
    public void removeElementAt(int index) {
        elements.removeElementAt(index);
    }

    Tag getTag() {
        return SEQUENCE.TAG;
    }

    public boolean tagMatch(Tag tag) {
        return( tag.equals(SEQUENCE.TAG) );
    }

    /**
     * Decodes a SEQUENCE from its BER encoding.
     * @param istream Input stream.
     */
    public ASN1Value decode(InputStream istream)
        throws IOException, InvalidBERException
    {
        return decode(getTag(), istream);
    }

    /**
     * Decodes a SEQUENCE from its BER encoding, where the SEQUENCE itself has
     * an implicit tag.
     * @param tag Tag.
     * @param istream Input stream.
     */
    public ASN1Value decode(Tag tag, InputStream istream)
        throws IOException, InvalidBERException
    {
      int index = 0;

      try {
        ASN1Header header = new ASN1Header(istream);

        header.validate( tag, Form.CONSTRUCTED );

        // will be -1 for indefinite encoding
        long remainingContent = header.getContentLength();

        boolean repeatableElement=false;
        SEQUENCE seq = new SEQUENCE();
        ASN1Header lookAhead=null;

        // go through the whole template
        for( index = 0; index < size(); index++ ) {

            // find out about the next item
            if( remainingContent == 0 ) {
                lookAhead = null;
            } else {
                // remainingContent > 0 or remainingContent == -1, which means
                // indefinite encoding.
                lookAhead = ASN1Header.lookAhead(istream);
            }

            // skip over items that don't match.  Hopefully they are
            // optional or have a default.  Otherwise, it's an error.
            Element e = elements.elementAt(index);
            if( (lookAhead == null) || lookAhead.isEOC() ||
                    ! e.tagMatch( lookAhead.getTag() ) )
            {
                if( e.isRepeatable() ) {
                    repeatableElement = true;
                } else if( e.isOptional() ) {
                    // put an empty entry into the SEQUENCE
                    SEQUENCE.Element se = new SEQUENCE.Element(null, null );
                    seq.addElement( null );
                } else if( e.getDefault() != null ) {
                    // use the default
                    seq.addElement( e.getDefault() );
                } else {
                    String tagDesc;
                    if( lookAhead == null ) {
                        tagDesc = "(null)";
                    } else {
                        tagDesc = lookAhead.getTag().toString();
                    }
                    throw new InvalidBERException("Missing item #" + index +
                        ": found " + tagDesc );
                }
                continue;
            }

            // Decode this element
            ASN1Template t = e.getTemplate();
            ASN1Value val;

            try (CountingStream countstream = new CountingStream(istream)) {

                if (e.getImplicitTag() == null) {
                    val = t.decode(countstream);
                } else {
                    val = t.decode(e.getImplicitTag(), countstream);
                }

                // Decrement remaining count
                long len = countstream.getNumRead();
                if (remainingContent != -1) {
                    if (remainingContent < len) {
                        // this item went past the end of the SEQUENCE
                        throw new InvalidBERException("Item went "+
                            (len-remainingContent) + " bytes past the end of" +
                            " the SEQUENCE");
                    }
                    remainingContent -= len;
                }
            }

            // Store this element in the SEQUENCE
            if( e.producesOutput() ) {
                if(  e.getImplicitTag() == null ) {
                    // no implicit tag
                    seq.addElement( val );
                } else {
                    // there is an implicit tag
                    seq.addElement( e.getImplicitTag(), val );
                }
            }

            // If this element is repeatable, don't go on to the next element
            if( e.isRepeatable() ) {
                repeatableElement = true;
                index--;
            }
        }

        if( remainingContent > 0 ) {
            throw new InvalidBERException("SEQUENCE is " + remainingContent +
                " bytes longer than expected");
        }
        Assert._assert( remainingContent == 0 || remainingContent == -1 );

        // If this was indefinite-length encoding, consume the end-of-contents
        if( remainingContent == -1 ) {
            lookAhead = new ASN1Header(istream);
            if( ! lookAhead.isEOC() ) {
                throw new InvalidBERException("No end-of-contents marker");
            }
        }

        // Make sure we stayed in sync
        if( ! repeatableElement ) {
            Assert._assert(index == seq.size());
        }

        return seq;

      } catch(InvalidBERException e) {
        e.append("SEQUENCE(item #" +index + ")");
        throw e;
      }
    }

    /**
     * An element of a SEQUENCE template. For each sub-template, contains the
     * template, its optionality, its implicit tag, and its default value.
     */
    static class Element {

        /**
         * Creates a new element, which may or may not be optional.
         * @param implicitTag Implicit tag.
         * @param type Type.
         * @param optional Optional.
         */
        public Element(Tag implicitTag, ASN1Template type, boolean optional)
        {
            this(implicitTag, type, optional, true);
        }

        /**
         * Creates a new element, which may or may not be optional.
         * @param implicitTag Implicit tag.
         * @param type Type.
         * @param optional Optional.
         * @param doesProduceOutput True if produces output.
         */
        public Element(Tag implicitTag, ASN1Template type, boolean optional,
            boolean doesProduceOutput)
        {
            this.type = type;
            defaultVal = null;
            this.optional = optional;
            this.implicitTag = implicitTag;
            this.doesProduceOutput = doesProduceOutput;
        }

        /**
         * Creates a new element with a default value.
         * @param implicitTag Implicit tag.
         * @param type Type.
         * @param defaultVal Default value.
         */
        public Element(Tag implicitTag, ASN1Template type, ASN1Value defaultVal)
        {
            this.type = type;
            this.defaultVal = defaultVal;
            optional = false;
            this.implicitTag = implicitTag;
        }

        private boolean doesProduceOutput = true;
        boolean producesOutput() {
            return doesProduceOutput;
        }

        // repeatability is provided to allow for SEQUENCE OF SIZE
        // constructs.  It is package private.
        private boolean repeatable;
        void makeRepeatable() {
            repeatable = true;
        }
        boolean isRepeatable() {
            return repeatable;
        }

        private boolean optional;
        public boolean isOptional() {
            return optional;
        }

        private Tag implicitTag=null;
        public Tag getImplicitTag() {
            return implicitTag;
        }

        public boolean tagMatch(Tag tag) {
            if( implicitTag != null ) {
                return( implicitTag.equals(tag) );
            } else {
                return type.tagMatch(tag);
            }
        }

        private ASN1Template type;
        public ASN1Template getTemplate() {
            return type;
        }

        private ASN1Value defaultVal=null;
        public ASN1Value getDefault() {
            return defaultVal;
        }
    }
} // End of SEQUENCE.Template

/**
 * A Template for decoding SEQUENCE OF values. The main difference between
 *  a SEQUENCE.Template and a SEQUENCE.OF_Template is that a regular template
 *  specifies the exact ordering, number, and type of elements of the sequence,
 *  while
 *  an OF_Template has an indefinite number of elements, all the same type.
 * For example, given:
 * <pre>
 * MyType ::= SEQUENCE OF Extension
 * </pre>
 * a MyType could be decoded with:
 * <pre>
 *  SEQUENCE.OF_Template myTypeTemplate = new SEQUENCE.OF_Template( new
 *      Extension.Template) );
 *  SEQUENCE seq = (SEQUENCE) myTypeTemplate.decode(someInputStream);
 * </pre>
 * The number of <code>Extension</code>s actually decoded could be found
 * with <code>seq.size()</code>.
 */
public static class OF_Template implements ASN1Template {

    private OF_Template() { }

    Template template;  // a normal SEQUENCE template

    public OF_Template(ASN1Template type) {
        template = new Template();
        Template.Element el = new Template.Element(null, type, true); //optional
        el.makeRepeatable();
        template.addElement( el );
    }

    public static OF_Template makeOutputlessOFTemplate(ASN1Template type) {
        OF_Template t = new OF_Template();
        t.template = new Template();
        Template.Element el = new Template.Element(null, type, true, false);
        el.makeRepeatable();
        t.template.addElement(el);
        return t;
    }

    public boolean tagMatch(Tag tag) {
        return TAG.equals(tag);
    }

    /**
     * Decodes a SEQUENCE OF from an input stream.
     */
    public ASN1Value decode(InputStream istream)
        throws IOException, InvalidBERException
    {
        return template.decode(istream);
    }

    /**
     * Decodes a SEQUENCE OF with an implicit tag from an input stream.
     */
    public ASN1Value decode(Tag implicitTag, InputStream istream)
        throws IOException, InvalidBERException
    {
        return template.decode(implicitTag, istream);
    }
}

    public static void main(String args[]) {

      try {

        if(args.length > 0) {
            // input

            Template type = new Template();
            type.addOptionalElement( new Tag(15), new INTEGER.Template() );
            type.addElement( new Tag(16), new INTEGER.Template(),
                    new INTEGER(42) );
            type.addElement( new INTEGER.Template() );
            type.addElement( new BOOLEAN.Template() );
            type.addElement( new INTEGER.Template() );
            type.addOptionalElement( new Tag(12), new INTEGER.Template() );
            type.addElement( new BOOLEAN.Template() );
            type.addElement( new Tag(13), new INTEGER.Template(),
                new INTEGER(53) );
            type.addElement( new INTEGER.Template() );
            type.addElement( new INTEGER.Template() );
            type.addOptionalElement( new Tag(14), new INTEGER.Template() );
            type.addElement( new OBJECT_IDENTIFIER.Template() );
            type.addElement( new NULL.Template() );
            type.addElement( new EXPLICIT.Template(
                                    new Tag(27), new INTEGER.Template()));
            type.addElement( new ENUMERATED.Template() );
            type.addElement( new OCTET_STRING.Template() );
            type.addElement( new IA5String.Template() );

            CHOICE.Template choice = new CHOICE.Template();
            choice.addElement( new Tag(23), new INTEGER.Template() );
            choice.addElement( new BOOLEAN.Template() );
            type.addElement( choice );
            type.addElement( new BIT_STRING.Template() );
            type.addElement( new ANY.Template() );
            type.addElement( new PrintableString.Template() );
            type.addElement( new OF_Template( new INTEGER.Template() ) );
            type.addElement( new OF_Template( new INTEGER.Template() ) );

            byte[] stuff;

            try (FileInputStream fin = new FileInputStream(args[0])) {
                System.out.println("Available: " + fin.available());
                stuff = new byte[fin.available()];
                ASN1Util.readFully(stuff, fin);
            }

            SEQUENCE s=null;
            for( int i = 0; i < 1; i++) {
                s = (SEQUENCE) type.decode( new ByteArrayInputStream(stuff) );
            }

            for(int i=0; i < s.size(); i ++ ) {
                ASN1Value v = s.elementAt(i);
                if(v instanceof ENUMERATED) {
                    ENUMERATED en = (ENUMERATED) v;
                    System.out.println("ENUMERATED: "+en);
                } else if( v instanceof INTEGER ) {
                    INTEGER in = (INTEGER) v;
                    System.out.println("INTEGER: "+in);
                } else if(v instanceof BOOLEAN ) {
                    BOOLEAN bo = (BOOLEAN) v;
                    System.out.println("BOOLEAN: "+bo);
                } else if(v instanceof OBJECT_IDENTIFIER) {
                    OBJECT_IDENTIFIER oid = (OBJECT_IDENTIFIER) v;
                    System.out.println("OID: "+oid);
                } else if(v instanceof NULL) {
                    NULL n = (NULL) v;
                    System.out.println("NULL");
                } else if(v instanceof EXPLICIT) {
                    EXPLICIT ex = (EXPLICIT) v;
                    INTEGER in = (INTEGER) ex.getContent();
                    System.out.println("EXPLICIT ["+ex.getTag()+"]: "+
                        "INTEGER: "+in);
                } else if(v instanceof OCTET_STRING) {
                    OCTET_STRING os = (OCTET_STRING) v;
                    byte[] bytes = os.toByteArray();
                    System.out.print("OCTET_STRING: ");
                    for(int j = 0; j < bytes.length; j++) {
                        System.out.print(bytes[j]+" ");
                    }
                    System.out.println("");
                } else if( v instanceof CharacterString ) {
                    CharacterString cs = (CharacterString) v;
                    System.out.println("String: "+cs);
                } else if( v instanceof BIT_STRING ) {
                    BIT_STRING bs = (BIT_STRING) v;
                    System.out.print("BIT_STRING: padCount="+
                        bs.getPadCount()+" : ");
                    byte[] bits = bs.getBits();
                    for(int j = 0; j < bits.length; j++) {
                        System.out.print(bits[j]+" ");
                    }
                    System.out.println("");
                } else if( v instanceof ANY ) {
                    ANY any = (ANY) v;
                    Tag tag = any.getTag();
                    System.out.println("Got ANY, tag is "+tag);
                    ByteArrayInputStream bos =
                        new ByteArrayInputStream( any.getEncoded() );
                    INTEGER in = (INTEGER) new INTEGER.Template().decode(bos);
                    System.out.println("    INTEGER: "+in);
                } else if(v instanceof SEQUENCE ) {
                    SEQUENCE seq = (SEQUENCE)v;
                    System.out.println("SEQUENCE: ");
                    for(int j=0; j < seq.size(); j++ ) {
                        INTEGER in = (INTEGER) seq.elementAt(j);
                        System.out.println("    INTEGER: "+in);
                    }
                } else {
                    System.out.println("Unknown value");
                }
            }


        } else {
            // output

            SEQUENCE seq = new SEQUENCE();
            seq.addElement( new INTEGER(5) );
            seq.addElement( new BOOLEAN(true) );
            seq.addElement( new INTEGER(-322) );
            seq.addElement( new BOOLEAN(false) );
            seq.addElement( new INTEGER(0) );
            seq.addElement( new INTEGER("2934293834242") );
            seq.addElement( new OBJECT_IDENTIFIER(
                new long[] { 1, 2, 127, 563, 1231982 } ) );
            seq.addElement( new NULL() );
            seq.addElement( new EXPLICIT( new Tag(27), new INTEGER(39) ));
            seq.addElement( new ENUMERATED(983) );
            seq.addElement( new OCTET_STRING( new byte[] {
                    (byte)0x0, (byte)0xff, (byte)0xcc} ) );
            seq.addElement( new IA5String("foobar") );
            seq.addElement( new Tag(23), new INTEGER(234) );
            //seq.addElement( new BOOLEAN(false) );
            byte[] bits = new byte[]{ (byte)0x80, (byte)0xff, (byte)0x0f };
            seq.addElement( new BIT_STRING( bits, 3 ) );
            seq.addElement( new INTEGER(82734) );
            seq.addElement( new PrintableString("I'm printable??") );

            SEQUENCE nested = new SEQUENCE();
            nested.addElement( new INTEGER( 5 ) );
            nested.addElement( new INTEGER( 6 ) );
            seq.addElement( nested );

            nested = new SEQUENCE();
            seq.addElement( nested );


            seq.encode(System.out);
            System.out.flush();
        }
      } catch( Exception e) {
            e.printStackTrace();
      }
    }

}
