package org.bouncycastle.pqc.asn1;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.pqc.legacy.crypto.rainbow.util.RainbowUtil;

/**
 * This class implements an ASN.1 encoded Rainbow public key. The ASN.1 definition
 * of this structure is:
 * <pre>
 *       RainbowPublicKey ::= SEQUENCE {
 *         CHOICE
 *         {
 *         oid        OBJECT IDENTIFIER         -- OID identifying the algorithm
 *         version    INTEGER                    -- 0
 *         }
 *         docLength        Integer               -- length of the code
 *         coeffquadratic   SEQUENCE OF OCTET STRING -- quadratic (mixed) coefficients
 *         coeffsingular    SEQUENCE OF OCTET STRING -- singular coefficients
 *         coeffscalar    SEQUENCE OF OCTET STRING -- scalar coefficients
 *       }
 * </pre>
 */
public class RainbowPublicKey
    extends ASN1Object
{
    private ASN1Integer version;
    private ASN1ObjectIdentifier oid;
    private ASN1Integer docLength;
    private byte[][] coeffQuadratic;
    private byte[][] coeffSingular;
    private byte[] coeffScalar;

    private RainbowPublicKey(ASN1Sequence seq)
    {
        // <oidString>  or version
        if (seq.getObjectAt(0) instanceof ASN1Integer)
        {
            version = ASN1Integer.getInstance(seq.getObjectAt(0));
        }
        else
        {
            oid = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
        }

        docLength = ASN1Integer.getInstance(seq.getObjectAt(1));

        ASN1Sequence asnCoeffQuad = ASN1Sequence.getInstance(seq.getObjectAt(2));
        coeffQuadratic = new byte[asnCoeffQuad.size()][];
        for (int quadSize = 0; quadSize < asnCoeffQuad.size(); quadSize++)
        {
            coeffQuadratic[quadSize] = ASN1OctetString.getInstance(asnCoeffQuad.getObjectAt(quadSize)).getOctets();
        }

        ASN1Sequence asnCoeffSing = (ASN1Sequence)seq.getObjectAt(3);
        coeffSingular = new byte[asnCoeffSing.size()][];
        for (int singSize = 0; singSize < asnCoeffSing.size(); singSize++)
        {
            coeffSingular[singSize] = ASN1OctetString.getInstance(asnCoeffSing.getObjectAt(singSize)).getOctets();
        }

        ASN1Sequence asnCoeffScalar = (ASN1Sequence)seq.getObjectAt(4);
        coeffScalar = ASN1OctetString.getInstance(asnCoeffScalar.getObjectAt(0)).getOctets();
    }

    public RainbowPublicKey(int docLength, short[][] coeffQuadratic, short[][] coeffSingular, short[] coeffScalar)
    {
        this.version = new ASN1Integer(0);
        this.docLength = new ASN1Integer(docLength);
        this.coeffQuadratic = RainbowUtil.convertArray(coeffQuadratic);
        this.coeffSingular = RainbowUtil.convertArray(coeffSingular);
        this.coeffScalar = RainbowUtil.convertArray(coeffScalar);
    }

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

        return null;
    }

    public ASN1Integer getVersion()
    {
        return version;
    }

    /**
     * @return the docLength
     */
    public int getDocLength()
    {
        return this.docLength.intValueExact();
    }

    /**
     * @return the coeffquadratic
     */
    public short[][] getCoeffQuadratic()
    {
        return RainbowUtil.convertArray(coeffQuadratic);
    }

    /**
     * @return the coeffsingular
     */
    public short[][] getCoeffSingular()
    {
        return RainbowUtil.convertArray(coeffSingular);
    }

    /**
     * @return the coeffscalar
     */
    public short[] getCoeffScalar()
    {
        return RainbowUtil.convertArray(coeffScalar);
    }

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

        // encode <oidString>  or version
        if (version != null)
        {
            v.add(version);
        }
        else
        {
            v.add(oid);
        }

        // encode <docLength>
        v.add(docLength);

        // encode <coeffQuadratic>
        ASN1EncodableVector asnCoeffQuad = new ASN1EncodableVector();
        for (int i = 0; i < coeffQuadratic.length; i++)
        {
            asnCoeffQuad.add(new DEROctetString(coeffQuadratic[i]));
        }
        v.add(new DERSequence(asnCoeffQuad));

        // encode <coeffSingular>
        ASN1EncodableVector asnCoeffSing = new ASN1EncodableVector();
        for (int i = 0; i < coeffSingular.length; i++)
        {
            asnCoeffSing.add(new DEROctetString(coeffSingular[i]));
        }
        v.add(new DERSequence(asnCoeffSing));

        // encode <coeffScalar>
        ASN1EncodableVector asnCoeffScalar = new ASN1EncodableVector();
        asnCoeffScalar.add(new DEROctetString(coeffScalar));
        v.add(new DERSequence(asnCoeffScalar));


        return new DERSequence(v);
    }
}
