package org.bouncycastle.asn1.eac;

import java.math.BigInteger;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.util.Arrays;

/**
 * an Iso7816ECDSAPublicKeyStructure structure.
 *  
 *  Certificate Holder Authorization ::= SEQUENCE {
 *      ASN1TaggedObject primeModulusP;        // OPTIONAL
 *      ASN1TaggedObject firstCoefA;            // OPTIONAL
 *      ASN1TaggedObject secondCoefB;        // OPTIONAL
 *      ASN1TaggedObject basePointG;            // OPTIONAL
 *      ASN1TaggedObject orderOfBasePointR;    // OPTIONAL
 *      ASN1TaggedObject publicPointY;        //REQUIRED
 *      ASN1TaggedObject    cofactorF;            // OPTIONAL
 *  }
 * </pre>
 */
public class ECDSAPublicKey
    extends PublicKeyDataObject
{
    private ASN1ObjectIdentifier usage;
    private BigInteger primeModulusP;        // OPTIONAL
    private BigInteger firstCoefA;            // OPTIONAL
    private BigInteger secondCoefB;        // OPTIONAL
    private byte[]     basePointG;            // OPTIONAL
    private BigInteger orderOfBasePointR;    // OPTIONAL
    private byte[]     publicPointY;        //REQUIRED
    private BigInteger cofactorF;            // OPTIONAL
    private int options;
    private static final int P = 0x01;
    private static final int A = 0x02;
    private static final int B = 0x04;
    private static final int G = 0x08;
    private static final int R = 0x10;
    private static final int Y = 0x20;
    private static final int F = 0x40;

    ECDSAPublicKey(ASN1Sequence seq)
        throws IllegalArgumentException
    {
        Enumeration en = seq.getObjects();

        this.usage = ASN1ObjectIdentifier.getInstance(en.nextElement());

        options = 0;
        while (en.hasMoreElements())
        {
            Object obj = en.nextElement();

            if (obj instanceof ASN1TaggedObject)
            {
                ASN1TaggedObject to = (ASN1TaggedObject)obj;
                switch (to.getTagNo())
                {
                case 0x1:
                    setPrimeModulusP(UnsignedInteger.getInstance(to).getValue());
                    break;
                case 0x2:
                    setFirstCoefA(UnsignedInteger.getInstance(to).getValue());
                    break;
                case 0x3:
                    setSecondCoefB(UnsignedInteger.getInstance(to).getValue());
                    break;
                case 0x4:
                    setBasePointG(ASN1OctetString.getInstance(to, false));
                    break;
                case 0x5:
                    setOrderOfBasePointR(UnsignedInteger.getInstance(to).getValue());
                    break;
                case 0x6:
                    setPublicPointY(ASN1OctetString.getInstance(to, false));
                    break;
                case 0x7:
                    setCofactorF(UnsignedInteger.getInstance(to).getValue());
                    break;
                default:
                    options = 0;
                    throw new IllegalArgumentException("Unknown Object Identifier!");
                }
            }
            else
            {
                throw new IllegalArgumentException("Unknown Object Identifier!");
            }
        }
        if (options != 0x20 && options != 0x7F)
        {
            throw new IllegalArgumentException("All options must be either present or absent!");
        }
    }

    public ECDSAPublicKey(ASN1ObjectIdentifier usage, byte[] ppY)
        throws IllegalArgumentException
    {
        this.usage = usage;
        setPublicPointY(new DEROctetString(ppY));
    }

    public ECDSAPublicKey(ASN1ObjectIdentifier usage, BigInteger p, BigInteger a, BigInteger b, byte[] basePoint, BigInteger order, byte[] publicPoint, int cofactor)
    {
        this.usage = usage;
        setPrimeModulusP(p);
        setFirstCoefA(a);
        setSecondCoefB(b);
        setBasePointG(new DEROctetString(basePoint));
        setOrderOfBasePointR(order);
        setPublicPointY(new DEROctetString(publicPoint));
        setCofactorF(BigInteger.valueOf(cofactor));
    }

    public ASN1ObjectIdentifier getUsage()
    {
        return usage;
    }

    public byte[] getBasePointG()
    {
        if ((options & G) != 0)
        {
            return Arrays.clone(basePointG);
        }
        else
        {
            return null;
        }
    }

    private void setBasePointG(ASN1OctetString basePointG)
        throws IllegalArgumentException
    {
        if ((options & G) == 0)
        {
            options |= G;
            this.basePointG = basePointG.getOctets();
        }
        else
        {
            throw new IllegalArgumentException("Base Point G already set");
        }
    }

    public BigInteger getCofactorF()
    {
        if ((options & F) != 0)
        {
            return cofactorF;
        }
        else
        {
            return null;
        }
    }

    private void setCofactorF(BigInteger cofactorF)
        throws IllegalArgumentException
    {
        if ((options & F) == 0)
        {
            options |= F;
            this.cofactorF = cofactorF;
        }
        else
        {
            throw new IllegalArgumentException("Cofactor F already set");
        }
    }

    public BigInteger getFirstCoefA()
    {
        if ((options & A) != 0)
        {
            return firstCoefA;
        }
        else
        {
            return null;
        }
    }

    private void setFirstCoefA(BigInteger firstCoefA)
        throws IllegalArgumentException
    {
        if ((options & A) == 0)
        {
            options |= A;
            this.firstCoefA = firstCoefA;
        }
        else
        {
            throw new IllegalArgumentException("First Coef A already set");
        }
    }

    public BigInteger getOrderOfBasePointR()
    {
        if ((options & R) != 0)
        {
            return orderOfBasePointR;
        }
        else
        {
            return null;
        }
    }

    private void setOrderOfBasePointR(BigInteger orderOfBasePointR)
        throws IllegalArgumentException
    {
        if ((options & R) == 0)
        {
            options |= R;
            this.orderOfBasePointR = orderOfBasePointR;
        }
        else
        {
            throw new IllegalArgumentException("Order of base point R already set");
        }
    }

    public BigInteger getPrimeModulusP()
    {
        if ((options & P) != 0)
        {
            return primeModulusP;
        }
        else
        {
            return null;
        }
    }

    private void setPrimeModulusP(BigInteger primeModulusP)
    {
        if ((options & P) == 0)
        {
            options |= P;
            this.primeModulusP = primeModulusP;
        }
        else
        {
            throw new IllegalArgumentException("Prime Modulus P already set");
        }
    }

    public byte[] getPublicPointY()
    {
        if ((options & Y) != 0)
        {
            return Arrays.clone(publicPointY);
        }
        else
        {
            return null;
        }
    }

    private void setPublicPointY(ASN1OctetString publicPointY)
        throws IllegalArgumentException
    {
        if ((options & Y) == 0)
        {
            options |= Y;
            this.publicPointY = publicPointY.getOctets();
        }
        else
        {
            throw new IllegalArgumentException("Public Point Y already set");
        }
    }

    public BigInteger getSecondCoefB()
    {
        if ((options & B) != 0)
        {
            return secondCoefB;
        }
        else
        {
            return null;
        }
    }

    private void setSecondCoefB(BigInteger secondCoefB)
        throws IllegalArgumentException
    {
        if ((options & B) == 0)
        {
            options |= B;
            this.secondCoefB = secondCoefB;
        }
        else
        {
            throw new IllegalArgumentException("Second Coef B already set");
        }
    }

    public boolean hasParameters()
    {
        return primeModulusP != null;
    }

    public ASN1EncodableVector getASN1EncodableVector(ASN1ObjectIdentifier oid, boolean publicPointOnly)
    {
        ASN1EncodableVector v = new ASN1EncodableVector(8);
        v.add(oid);

        if (!publicPointOnly)
        {
            v.add(new UnsignedInteger(0x01, getPrimeModulusP()));
            v.add(new UnsignedInteger(0x02, getFirstCoefA()));
            v.add(new UnsignedInteger(0x03, getSecondCoefB()));
            v.add(new DERTaggedObject(false, 0x04, new DEROctetString(getBasePointG())));
            v.add(new UnsignedInteger(0x05, getOrderOfBasePointR()));
        }
        v.add(new DERTaggedObject(false, 0x06, new DEROctetString(getPublicPointY())));
        if (!publicPointOnly)
        {
            v.add(new UnsignedInteger(0x07, getCofactorF()));
        }

        return v;
    }

    public ASN1Primitive toASN1Primitive()
    {
        return new DERSequence(getASN1EncodableVector(usage, !hasParameters()));
    }
}
