/* 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.pkix.cmmf;

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

import org.mozilla.jss.asn1.ANY;
import org.mozilla.jss.asn1.ASN1Template;
import org.mozilla.jss.asn1.ASN1Value;
import org.mozilla.jss.asn1.ENUMERATED;
import org.mozilla.jss.asn1.GeneralizedTime;
import org.mozilla.jss.asn1.INTEGER;
import org.mozilla.jss.asn1.InvalidBERException;
import org.mozilla.jss.asn1.OCTET_STRING;
import org.mozilla.jss.asn1.SEQUENCE;
import org.mozilla.jss.asn1.Tag;
import org.mozilla.jss.asn1.UTF8String;

/**
 * CMMF <i>RevRequest</i>.
 * <pre>
 * RevRequest ::= SEQUENCE {
 *      issuerName      Name,
 *      serialNumber    INTEGER,
 *      reason          CRLReason,
 *      invalidityDate  GeneralizedTime OPTIONAL,
 *      sharedSecret    OCTET STRING OPTIONAL,
 *      comment         UTF8String OPTIONAL }
 * </pre>
 * For maintenance and conformance reasons, this code has been brought
 * over and renamed to cmc/RevokeRequest during the CMC update to rfc 5272.
 * All new code should use cmc/RevokeRequest instead
 */
public class RevRequest implements ASN1Value {

    ///////////////////////////////////////////////////////////////////////
    // Constants
    ///////////////////////////////////////////////////////////////////////

    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED unspecified = new ENUMERATED(0);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED keyCompromise = new ENUMERATED(1);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED cACompromise = new ENUMERATED(2);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED affiliationChanged = new ENUMERATED(3);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED superseded = new ENUMERATED(4);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED cessationOfOperation = new ENUMERATED(5);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED certificateHold = new ENUMERATED(6);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED removeFromCRL = new ENUMERATED(8);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED privilegeWithdrawn = new ENUMERATED(9);
    /**
     * A <code>CRLReason</code>, which can be used in the <code>reason</code>
     *  field.
     */
    public static final ENUMERATED aACompromise = new ENUMERATED(10);


    ///////////////////////////////////////////////////////////////////////
    // Members and member access
    ///////////////////////////////////////////////////////////////////////
    private ANY issuerName;
    private INTEGER serialNumber;
    private ENUMERATED reason;
    private GeneralizedTime invalidityDate; // may be null
    private OCTET_STRING sharedSecret; // may be null
    private UTF8String comment; // may be null
    private SEQUENCE sequence;

    /**
     * Returns the <code>issuerName</code> field as an ANY.
     */
    public ANY getIssuerName() {
        return issuerName;
    }

    /**
     * Returns the <code>serialNumber</code> field.
     */
    public INTEGER getSerialNumber() {
        return serialNumber;
    }

    /**
     * Returns the <code>reason</code> field, which should indicate the
     *  reason for the revocation.  The currently supported reasons are:
     * <pre>
     * CRLReason ::= ENUMERATED {
     *      unspecified             (0),
     *      keyCompromise           (1),
     *      cACompromise            (2),
     *      affiliationChanged      (3),
     *      superseded              (4),
     *      cessationOfOperation    (5),
     *      certificateHold         (6),
     *      removeFromCRL           (8),
     *      privilegeWithdrawn      (9),
     *      aACompromise            (10) }
     * </pre>
     * These are all defined as constants in this class.
     */
    public ENUMERATED getReason() {
        return reason;
    }

    /**
     * Returns the <code>invalidityDate</code> field. Returns null
     * if the field is not present.
     */
    public GeneralizedTime getInvalidityDate() {
        return invalidityDate;
    }

    /**
     * Returns the <code>passphrase</code> field.  Returns
     *  <code>null</code> if the field is not present.
     * @deprecated The <code>passphrase</code> field has been renamed
     *  <code>sharedSecret</code>. Call <code>getSharedSecret</code> instead.
     */
    @Deprecated
    public OCTET_STRING getPassphrase() {
        return sharedSecret;
    }

    /**
     * Returns the <code>sharedSecret</code> field.  Returns
     *  <code>null</code> if the field is not present.
     */
    public OCTET_STRING getSharedSecret() {
        return sharedSecret;
    }

    /**
     * Returns the <code>comment</code> field.  Returns <code>null</code>
     * if the field is not present.
     */
    public UTF8String getComment() {
        return comment;
    }

    ///////////////////////////////////////////////////////////////////////
    // Constructors
    ///////////////////////////////////////////////////////////////////////

    /**
     * Constructs a new <code>RevRequest</code> from its components,
     *  omitting the <code>invalidityDate</code> field.
     *
     * @deprecated This constructor is obsolete now that
     *      <code>invalidityDate</code> has been added to the class.
     *
     * @param issuerName The <code>issuerName</code> field.
     * @param serialNumber The <code>serialNumber</code> field.
     * @param reason The <code>reason</code> field.  The constants defined
     *      in this class may be used.
     * @param sharedSecret The <code>sharedSecret</code> field.  This field is
     *      optional, so <code>null</code> may be used.
     * @param comment The <code>comment</code> field.  This field is optional,
     *      so <code>null</code> may be used.
     */
    @Deprecated
    public RevRequest(ANY issuerName, INTEGER serialNumber,
                    ENUMERATED reason, OCTET_STRING sharedSecret,
                    UTF8String comment)
    {
        this(issuerName, serialNumber, reason, null, sharedSecret, comment);
    }

    /**
     * Constructs a new <code>RevRequest</code> from its components.
     *
     * @param issuerName The <code>issuerName</code> field.
     * @param serialNumber The <code>serialNumber</code> field.
     * @param reason The <code>reason</code> field.  The constants defined
     *      in this class may be used.
     * @param invalidityDate The suggested value for the Invalidity Date
     *      CRL extension. This field is optional, so <code>null</code> may be
     *      used.
     * @param sharedSecret The <code>sharedSecret</code> field.  This field is
     *      optional, so <code>null</code> may be used.
     * @param comment The <code>comment</code> field.  This field is optional,
     *      so <code>null</code> may be used.
     */
    public RevRequest(ANY issuerName, INTEGER serialNumber,
                    ENUMERATED reason, GeneralizedTime invalidityDate,
                    OCTET_STRING sharedSecret, UTF8String comment)
    {
        if( issuerName==null || serialNumber==null || reason==null ) {
            throw new IllegalArgumentException(
                "parameter to RevRequest constructor is null");
        }
        sequence = new SEQUENCE();

        this.issuerName = issuerName;
        sequence.addElement(issuerName);

        this.serialNumber = serialNumber;
        sequence.addElement(serialNumber);

        this.reason = reason;
        sequence.addElement(reason);

        this.invalidityDate = invalidityDate;
        sequence.addElement(invalidityDate);

        this.sharedSecret = sharedSecret;
        sequence.addElement(sharedSecret);

        this.comment = comment;
        sequence.addElement(comment);
    }


    ///////////////////////////////////////////////////////////////////////
    // encoding/decoding
    ///////////////////////////////////////////////////////////////////////

    private static final Tag TAG = SEQUENCE.TAG;
    @Override
    public Tag getTag() {
        return TAG;
    }

    @Override
    public void encode(OutputStream ostream) throws IOException {
        sequence.encode(ostream);
    }

    @Override
    public void encode(Tag implicitTag, OutputStream ostream)
            throws IOException {
        sequence.encode(implicitTag, ostream);
    }



    /**
     * A Template class for decoding a <code>RevRequest</code>.
     */
    public static class Template implements ASN1Template {

        private SEQUENCE.Template seqt;

        public Template() {
            seqt = new SEQUENCE.Template();
            seqt.addElement(ANY.getTemplate());
            seqt.addElement(INTEGER.getTemplate());
            seqt.addElement(ENUMERATED.getTemplate());
            seqt.addOptionalElement(GeneralizedTime.getTemplate());
            seqt.addOptionalElement(OCTET_STRING.getTemplate());
            seqt.addOptionalElement(UTF8String.getTemplate());
        }

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

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

        @Override
        public ASN1Value decode(Tag implicitTag, InputStream istream)
                throws InvalidBERException, IOException {

            SEQUENCE seq = (SEQUENCE) seqt.decode(implicitTag, istream);

            return new RevRequest(  (ANY) seq.elementAt(0),
                                    (INTEGER) seq.elementAt(1),
                                    (ENUMERATED) seq.elementAt(2),
                                    (GeneralizedTime) seq.elementAt(3),
                                    (OCTET_STRING) seq.elementAt(4),
                                    (UTF8String) seq.elementAt(5) );

        }
    }
}
