/* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Random;

/**
 * The ASN.1 type <code>INTEGER</code>. This class extends BigInteger.
 */
public class INTEGER extends BigInteger implements ASN1Value {

    private static final long serialVersionUID = 1L;
    private byte[] encodedContents = null;

    private byte[] getEncodedContents() {
        if (encodedContents == null) {
            encodedContents = toByteArray();
        }
        return encodedContents;
    }

    private ASN1Header getHeader(Tag t) {
        return new ASN1Header(t, FORM, getContentLength());
    }

    public INTEGER(String s) throws NumberFormatException {
        super(s);
    }

    public INTEGER(String s, int r) throws NumberFormatException {
        super(s, r);
    }

    public INTEGER(byte[] bval) throws NumberFormatException {
        super(bval);
    }

    public INTEGER(int sign, byte[] mag) throws NumberFormatException {
        super(sign, mag);
    }

    public INTEGER(int numBits, Random rnd) throws NumberFormatException {
        super(numBits, rnd);
    }

    public INTEGER(int bitLength, int certainty, Random rnd) {
        super(bitLength, certainty, rnd);
    }

    public INTEGER(long val) {
        super(BigInteger.valueOf(val).toByteArray());
    }

    public INTEGER(BigInteger bi) {
        super(bi.toByteArray());
    }

    public static final Tag TAG = new Tag(Tag.Class.UNIVERSAL, 2);

    @Override
    public Tag getTag() {
        return TAG;
    }

    public static final Form FORM = Form.PRIMITIVE;

    @Override
    public void encode(OutputStream outStream) throws IOException {
        encode(getTag(), outStream);
    }

    @Override
    public void encode(Tag implicitTag, OutputStream outStream)
            throws IOException {
        // write header
        getHeader(implicitTag).encode(outStream);

        // write contents
        outStream.write(getEncodedContents());
    }

    public long getContentLength() {
        return getEncodedContents().length;
    }

    public byte[] encode() throws IOException {
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        encode(b);
        return b.toByteArray();
    }

    private static final INTEGER.Template templateInstance = new INTEGER.Template();

    public static ASN1Template getTemplate() {
        return templateInstance;
    }

    /**
     * Tests the DER encoding and decoding of the INTEGER class.
     * 
     * @param args Arguments.
     */
    public static void main(String args[]) {
        try {
            int[] Is = new int[11];
            int[][] Bs = new int[11][];
            int i = 0;

            Is[i] = 0;
            Bs[i++] = new int[] { 0x02, 0x01, 0x00 };

            Is[i] = 1;
            Bs[i++] = new int[] { 0x02, 0x01, 0x01 };

            Is[i] = -1;
            Bs[i++] = new int[] { 0x02, 0x01, 0xff };

            Is[i] = 127;
            Bs[i++] = new int[] { 0x02, 0x01, 0x7f };

            Is[i] = 128;
            Bs[i++] = new int[] { 0x02, 0x02, 0x00, 0x80 };

            Is[i] = 255;
            Bs[i++] = new int[] { 0x02, 0x02, 0x00, 0xff };

            Is[i] = 256;
            Bs[i++] = new int[] { 0x02, 0x02, 0x01, 0x00 };

            Is[i] = -128;
            Bs[i++] = new int[] { 0x02, 0x01, 0x80 };

            Is[i] = -129;
            Bs[i++] = new int[] { 0x02, 0x02, 0xff, 0x7f };

            Is[i] = 43568;
            Bs[i++] = new int[] { 0x02, 0x03, 0x00, 0xaa, 0x30 };

            Is[i] = -43568;
            Bs[i++] = new int[] { 0x02, 0x03, 0xff, 0x55, 0xd0 };

            for (i = 0; i < Is.length; i++) {
                INTEGER I = new INTEGER(Is[i]);
                byte[] compare = I.encode();
                if (!arraysEqual(compare, Bs[i])) {
                    System.err.println("Encoding FAILED: " + Is[i]);
                    System.exit(-1);
                }

                ByteArrayInputStream bis = new ByteArrayInputStream(compare);
                Template template = new Template();
                INTEGER create = (INTEGER) template.decode(bis);
                if (create.intValue() != Is[i]) {
                    System.err.println("Decoding FAILED: " + Is[i]);
                    System.exit(-1);
                }
            }
            System.out.println("PASS");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean arraysEqual(byte[] bytes, int[] ints) {
        if (bytes == null || ints == null) {
            return false;
        }

        if (bytes.length != ints.length) {
            return false;
        }

        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] != (byte) ints[i]) {
                return false;
            }
        }
        return true;
    }

    ///////////////////////////////////////////////////////////////////////
    // INTEGER.Template
    // This is a nested class.
    //
    public static class Template implements ASN1Template {

        Tag getTag() {
            return INTEGER.TAG;
        }

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

        @Override
        public ASN1Value decode(InputStream derStream)
                throws InvalidBERException, IOException {
            return decode(getTag(), derStream);
        }

        @Override
        public ASN1Value decode(Tag tag, InputStream derStream)
                throws InvalidBERException, IOException {
            try {
                ASN1Header wrapper = new ASN1Header(derStream);

                wrapper.validate(tag, FORM);

                // Is length < 1 ?
                if (wrapper.getContentLength() < 1) {
                    throw new InvalidBERException("Invalid 0 length for INTEGER");
                }

                byte[] valBytes = new byte[(int) wrapper.getContentLength()];
                ASN1Util.readFully(valBytes, derStream);
                return new INTEGER(valBytes);

            } catch (InvalidBERException e) {
                throw new InvalidBERException(e, "INTEGER");
            }
        }
    } // end of class Template

}
