/*
 * GidsApplet: A Java Card implementation of the GIDS (Generic Identity
 * Device Specification) specification
 * https://msdn.microsoft.com/en-us/library/windows/hardware/dn642100%28v=vs.85%29.aspx
 * Copyright (C) 2016  Vincent Le Toux([email protected])
 *
 * It has been based on the IsoApplet
 * Copyright (C) 2014  Philip Wendland ([email protected])
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysmartlogon.gidsApplet;

import javacard.framework.ISO7816;
import javacard.framework.ISOException;

/**
 * \brief The File class acting as superclass for any file.
 */
public abstract class File {
    private final short fileID;
    private DedicatedFile parentDF;

    final byte[] fcp;
    private final short aclPos;
    private byte state;
    /* Access Control Operations */
    public static final byte ACL_OP_01 = (byte) 0x01;
    public static final byte ACL_OP_02 = (byte) 0x02;
    public static final byte ACL_OP_04 = (byte) 0x04;
    public static final byte ACL_OP_08 = (byte) 0x08;
    public static final byte ACL_OP_10 = (byte) 0x10;
    public static final byte ACL_OP_20 = (byte) 0x20;
    public static final byte ACL_OP_40 = (byte) 0x40;

    public static final byte ACL_OP_DF_DELETE_CHILD = (byte) 0x01;
    public static final byte ACL_OP_DF_CREATE_EF = (byte) 0x02;
    public static final byte ACL_OP_DF_CREATE_DF = (byte) 0x04;
    public static final byte ACL_OP_DF_DEACTIVATE = (byte) 0x08;
    public static final byte ACL_OP_DF_ACTIVATE = (byte) 0x10;
    public static final byte ACL_OP_DF_TERMINATE = (byte) 0x20;
    public static final byte ACL_OP_DF_DELETE_SELF = (byte) 0x40;

    public static final byte ACL_OP_EF_READ = (byte) 0x01;
    public static final byte ACL_OP_EF_UPDATE = (byte) 0x02;
    public static final byte ACL_OP_EF_WRITE = (byte) 0x04;
    public static final byte ACL_OP_EF_DEACTIVATE = (byte) 0x08;
    public static final byte ACL_OP_EF_ACTIVATE = (byte) 0x10;
    public static final byte ACL_OP_EF_TERMINATE = (byte) 0x20;
    public static final byte ACL_OP_EF_DELETE = (byte) 0x40;

    public static final byte ACL_OP_DO_GET_DATA = (byte) 0x01;
    public static final byte ACL_OP_DO_PUT_DATA = (byte) 0x02;

    public static final byte ACL_OP_KEY_GETPUBLICKEY = (byte) 0x01;
    public static final byte ACL_OP_KEY_PUTKEY = (byte) 0x02;
    public static final byte ACL_OP_KEY_MANAGE_SEC_ENV = (byte) 0x04;
    public static final byte ACL_OP_KEY_GENERATE_ASYMETRIC = (byte) 0x08;


    /* Card/Applet lifecycle states */
    // see 7.4.10 Life cycle status table 14
    public static final byte STATE_CREATION = (byte) 0x01; // No restrictions, PUK not set yet.
    public static final byte STATE_INITIALISATION = (byte) 0x03; // PUK set, PIN not set yet. PUK may not be changed.
    public static final byte STATE_OPERATIONAL_ACTIVATED = (byte) 0x07; // PIN is set, data is secured.
    public static final byte STATE_OPERATIONAL_DEACTIVATED = (byte) 0x06; // Applet usage is deactivated. (Unused at the moment.)
    public static final byte STATE_TERMINATED = (byte) 0x0F; // Applet usage is terminated. (Unused at the moment.)


    /**
     * \brief Abstract constructor to be called by subclasses.
     *
     * \param fileID The ID of the file.
     *
     * \param fileControlInformation The FCI according to ISO 7816-4 table 12. Necessary tags: 82, 83. No copy is made.
     */
    public File(short fileID, byte[] fileControlParameter) {
        this.fileID = fileID;
        this.parentDF = null;
        this.fcp = fileControlParameter;
        // Save the position of the ACL (Value field) in the FCI for performance reasons.
        // If the position is -1, then every action may be performed.

        // try the following tag by order
        // tag 0x86 = security attribute in proprietary format
        // tag 0x8C = compact format

        short pos;
        try {
            pos = UtilTLV.findTag(fcp, (short) 2, fcp[(short)1], (byte) 0x8C);

        } catch (NotFoundException e) {
            pos = -1;
        } catch (InvalidArgumentsException e) {
            pos = -1;
        }
        this.aclPos = pos;

        state = STATE_CREATION;
    }

    public void CheckPermission(GidsPINManager pinManager, byte flag_operation) {
        if (state == STATE_CREATION) {
            if (this instanceof ApplicationFile) {
                // every operation is allowed on the application on the creation state
                return;
            }
            if (this instanceof ElementaryFile) {
                // only a transition to operational state is allowed
                if (flag_operation != ACL_OP_EF_ACTIVATE) {
                    ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
                }
            }
            if (this instanceof DedicatedFile) {
                // only a transition to operational state is allowed
                if (flag_operation != ACL_OP_DF_ACTIVATE) {
                    ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
                }
            }
        } else if (state == STATE_TERMINATED) {
            if (this instanceof ApplicationFile) {
                // every operation is denied on the application on the termination state
                ISOException.throwIt(ErrorCode.SW_TERMINATION_STATE);
            }
            if (this instanceof ElementaryFile) {
                // only a transition to operational state is allowed
                if (flag_operation != ACL_OP_EF_DELETE) {
                    ISOException.throwIt(ErrorCode.SW_TERMINATION_STATE);
                }
            }
            if (this instanceof DedicatedFile) {
                // only a transition to operational state is allowed
                if (flag_operation != ACL_OP_DF_DELETE_SELF) {
                    ISOException.throwIt(ErrorCode.SW_TERMINATION_STATE);
                }
            }
        }
        CheckACLRequirements(pinManager, flag_operation);
    }


    /**
     * \brief Get the relevant ACL byte for the operation.
     *
     * \param flag_operation The operation. One of ACL_OP_*.
     *
     * \return The ACL byte.
     */
    private void CheckACLRequirements(GidsPINManager pinManager, byte flag_operation) {
        if(aclPos == -1) {
            return; // Any operation is allowed if there is no ACL.
        }
        byte accessmod = fcp[(short)(aclPos+2)];
        short index = (short)(aclPos+2);
        if ((accessmod & ACL_OP_40) != 0) {
            index++;
            if (flag_operation == ACL_OP_40) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_20) != 0) {
            index++;
            if (flag_operation == ACL_OP_20) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_10) != 0) {
            index++;
            if (flag_operation == ACL_OP_10) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_08) != 0) {
            index++;
            if (flag_operation == ACL_OP_08) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_04) != 0) {
            index++;
            if (flag_operation == ACL_OP_04) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_02) != 0) {
            index++;
            if (flag_operation == ACL_OP_02) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        if ((accessmod & ACL_OP_01) != 0) {
            index++;
            if (flag_operation == ACL_OP_01) {
                pinManager.CheckACL(fcp[index]);
            }
        }
        // TODO: check if a second ACL is following
        // typically ACL for contact & contactless operations
        return; // Any operation is allowed if there is no ACL.
    }



    /**
     * \brief Get the file identifier.
     *
     * \return The file ID.
     */
    public short getFileID() {
        return this.fileID;
    }

    /**
     * \brief Get the parent Dedicated File (DF).
     *
     * \return The parent DF or null if the file had not been added yet.
     */
    public DedicatedFile getParentDF() {
        return this.parentDF;
    }

    /**
     * \brief Set the parent Dedicated File (DF).
     *
     * \param parent the parent DF.
     */
    public void setParentDF(DedicatedFile parent) {
        this.parentDF = parent;
    }

    /**
     * \brief Get the File Control Information (FCI).
     *
     * \return The FCI array.
     */
    public final byte[] getFileControlParameter() {
        return this.fcp;
    }

    public final byte getState() {
        return state;
    }

    public final void setState(byte state) {
        this.state = state;
    }

    /**
     * \brief Clear the contents of the file.
     *
     * Used when deleting files and JCSystem.requestObjectDeletion() is not
     * implemented.
     */
    abstract void clearContents();
}