/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * 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 version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.satsa.acl;

import com.sun.satsa.util.Utils;

import java.io.IOException;

/**
 * This class represents PKCS#15 PIN attributes.
 */
public class PINAttributes {

    /** PIN type constant. */
    private static final int TYPE_BCD        = 0;
    /** PIN type constant. */
    private static final int TYPE_ASCII      = 1;
    /** PIN type constant. */
    private static final int TYPE_UTF        = 2;
    /** PIN type constant. */
    private static final int TYPE_HN         = 3;
    /** PIN type constant. */
    private static final int TYPE_ISO        = 4;

    /** PIN flag constant. */
    public static final int FLAG_CASE_SENSITIVE    = 1;
    /** PIN flag constant. */
    public static final int FLAG_CHANGE_DISABLED   = 2;
    /** PIN flag constant. */
    public static final int FLAG_UNBLOCK_DISABLED  = 4;
    /** PIN flag constant. */
    public static final int FLAG_NEEDS_PADDING     = 8;
    /** PIN flag constant. */
    public static final int FLAG_DISABLE_ALLOWED   = 16;
    /** PIN flag constant. */
    public static final int FLAG_UNBLOCKING_PIN    = 32;

    /** PIN label. */
    public String label;
    /** PIN identifier. */
    public int id;
    /** pinType PIN attribute. */
    public int pinType;
    /** minLength PIN attribute. */
    public int minLength;
    /** storedLength PIN attribute. */
    public int storedLength;
    /** maxLength PIN attribute. */
    public int maxLength;
    /** pinReference PIN attribute. */
    public int pinReference;
    /** padChar PIN attribute. */
    public int padChar;
    /** pinFlags PIN attribute. */
    public int pinFlags;
    /** Path PIN attribute. */
    public short[] path;


    /**
     * Constructs PINAttributes object.
     */
    public PINAttributes() {}

    /**
     * Constructs PINAttributes object.
     * @param r reader for permissions file.
     * @throws java.io.IOException if I/O error occurs.
     */
    PINAttributes(ACLFileReader r) throws IOException {

        r.checkWord("{");

        while (true) {

            String s = r.readWord();

            if (s.equals("}")) {
                break;
            }


            if (s.equals("label")) {
                label = r.readLine();
                continue;
            }

            if (s.equals("id")) {
                id = r.readByte();
                continue;
            }

            String h = r.readWord();

            if (s.equals("type")) {
                if (h.equals("bcd")) {
                    pinType = TYPE_BCD;
                } else
                if (h.equals("ascii")) {
                    pinType = TYPE_ASCII;
                } else
                if (h.equals("utf")) {
                    pinType = TYPE_UTF;
                } else
                if (h.equals("half-nibble")) {
                    pinType = TYPE_HN;
                } else
                if (h.equals("iso")) {
                    pinType = TYPE_ISO;
                } else {
                    throw new IOException();
                }
                continue;
            }

            if (s.equals("min")) {
                minLength = Integer.parseInt(h, 16);
                continue;
            }

            if (s.equals("stored")) {
                storedLength = Integer.parseInt(h, 16);
                continue;
            }

            if (s.equals("max")) {
                maxLength = Integer.parseInt(h, 16);
                continue;
            }

            if (s.equals("reference")) {
                pinReference = Integer.parseInt(h, 16);
                continue;
            }

            if (s.equals("pad")) {
                padChar = Integer.parseInt(h, 16);
                continue;
            }

            if (s.equals("flag")) {
                if (h.equals("case-sensitive")) {
                    pinFlags |= FLAG_CASE_SENSITIVE;
                } else
                if (h.equals("change-disabled")) {
                    pinFlags |= FLAG_CHANGE_DISABLED;
                } else
                if (h.equals("unblock-disabled")) {
                    pinFlags |= FLAG_UNBLOCK_DISABLED;
                } else
                if (h.equals("needs-padding")) {
                    pinFlags |= FLAG_NEEDS_PADDING;
                } else
                if (h.equals("disable-allowed")) {
                    pinFlags |= FLAG_DISABLE_ALLOWED;
                } else
                if (h.equals("unblockingPIN")) {
                    pinFlags |= FLAG_UNBLOCKING_PIN;
                } else {
                    throw new IOException();
                }
                continue;
            }
            throw new IOException();
        }
    }

    /**
     * Returns true if this PIN is a number.
     * @return true if this PIN is a number.
     */
    public boolean isNumeric() {
        return (pinType != TYPE_UTF);
    }

    /**
     * Returns maximum PIN length in characters.
     * @return maximum PIN length in characters.
     */
    public int getMaxLength() {

        if ((pinFlags & FLAG_NEEDS_PADDING) == 0) {
            return maxLength;
        }

        if (pinType == TYPE_BCD) {
            return storedLength * 2;
        }

        // UTF symbol may occupy 1 or 2 bytes, additional check is necessary

        return storedLength;
    }

    /**
     * Verifies if the specified operation can be performed on this PIN.
     * @param action operation identifier.
     * @return true if the specified operation can be performed on this PIN.
     */
    public boolean check(int action) {

        if (action == ACLPermissions.CMD_CHANGE) {
            return (pinFlags & FLAG_CHANGE_DISABLED) == 0;
        }

        if (action == ACLPermissions.CMD_DISABLE) {
            return (pinFlags & FLAG_DISABLE_ALLOWED) != 0;
        }

        if (action == ACLPermissions.CMD_UNBLOCK) {
            return (pinFlags & FLAG_UNBLOCK_DISABLED) == 0;
        }

        return true;
    }

    /**
     * Verifies if this PIN can be used to unblock other PINs.
     * @return true if this PIN can be used to unblock other PINs.
     */
    public boolean isUnblockingPIN() {
        return (pinFlags & FLAG_UNBLOCKING_PIN) != 0;
    }

    /**
     * Transforms string entered by user according to PIN attributes.
     * @param s the value enterd by user.
     * @return converted and padded PIN value.
     */
    public byte[] transform(String s) {

        if (s.length() < minLength) {
            return null;
        }

        byte[] data = null;

        if (pinType == TYPE_UTF) {

            if ((pinFlags & FLAG_CASE_SENSITIVE) == 0) {
                s = s.toUpperCase();  // locale?
            }

            data = Utils.stringToBytes(s);

            if (data.length > getMaxLength()) {
                return null;
            }

        } else {

            byte[] tmp = new byte[s.length()];
            for (int i = 0; i < tmp.length; i++) {
                tmp[i] = (byte) (s.charAt(i));
            }

            if (pinType == TYPE_ASCII || pinType == TYPE_ISO) {
                data = tmp;
            } else {
                if (pinType == TYPE_HN) {
                    data = tmp;
                    for (int i = 0; i < data.length; i++) {
                        data[i] = (byte) (0xf0 | (data[i] - 0x30));
                    }
                } else {   // type == TYPE_BCD

                    data = new byte[(tmp.length + 1) / 2];

                    for (int i = 0; i < data.length; i++) {

                        int l = i * 2;
                        int b1 = tmp[l] - 0x30;
                        int b2;
                        if (l + 1 == tmp.length) {
                            b2 = padChar;
                        } else {
                            b2 = tmp[l + 1] - 0x30;
                        }
                        data[i] = (byte) ((b1 << 4) | (b2 & 0xf));
                    }

                }
            }
        }

        if (((pinFlags & FLAG_NEEDS_PADDING) == 0) ||
             (data.length == storedLength)) {
            return data;
        }

        byte[] r = new byte[storedLength];
        System.arraycopy(data, 0, r, 0, data.length);
        for (int i = data.length; i < storedLength; i++) {
            r[i] = (byte) padChar;
        }

        return r;
    }
}
