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

import java.io.IOException;

/**
 * This class provides interface to card file system.
 */
abstract public class FileSystemAbstract {

    /** Expected maximum path depth. */
    static final int MAX_PATH_DEPTH  = 10;
    /** APDU INS byte. */
    protected static final byte INS_SELECT    = (byte) 0xa4;
    /** APDU INS byte. */
    protected static final byte INS_READ      = (byte) 0xb0;
    /** APDU INS byte. */
    protected static final byte INS_UPDATE    = (byte) 0xd6;
    /** Root path for this application. */
    short[] root;
    /** The length of path to current DF. */
    int pathLength;
    /**
     * Path to the current DF. Element at index pathLength contains EF
     * identifier if EF is selected.
     */
    short[] currentPath;
    /** Connection used by this file system. */
    protected Connection apdu;
    /** Size of currently selected EF or -1 if DF is selected. */
    protected int currentFileSize;
    /** True if currently selected file is EF. */
    protected boolean isEFSelected;

    /**
     * Constructs new FileSystem object.
     * @param apdu connection to be used by this object.
     */
    public FileSystemAbstract(Connection apdu) {
        this.apdu = apdu;
    }

    /**
     * Sets the root directory for this file system.
     * @param root root directory path
     */
    public void setRoot(short[] root) {

        this.root = root;
        currentPath = new short[MAX_PATH_DEPTH];

        for (int j = 0; j < root.length; j++) {
            currentPath[j] = root[j];
        }
        pathLength = root.length;
    }

    /**
     * Sends the select DF command to the card.
     * @param root short[] directory path
     * @throws IOException if IO error occurs
     */
    public void selectRoot(short[] root) throws IOException {
        setRoot(root);
        for (int i = 0; i < root.length; i++) {
            byte[] data = apdu.resetCommand().
                          putShort(root[i]).
                          sendCommand(INS_SELECT, 0x0100);
        }
    }

    /**
     * If necessary converts path into completely specified path.
     * @param t TLV object that contains octet string representing path
     * @return completely specified path
     * @throws IOException if path is incorrect
     */
    public short[] makePath(TLV t) throws IOException {

        short[] path;
        short s = Utils.getShort(t.data, t.valueOffset);
        if (root == null) {     // only file IDs are used
            if (t.length == 2) {
                path = new short[1];
                path[0] = s;
                return path;
            }
            throw new IOException("Invalid path - ID expected");
        }

        // absolute path
        if (s == 0x3f00) {
            path = new short[t.length / 2];
            for (int i = 0; i < path.length; i++) {
                path[i] = Utils.getShort(t.data, t.valueOffset + i * 2);
            }
            return path;
        }

        // it must be relative path
        int len = root.length + t.length / 2;
        int offset = t.valueOffset;

        if (s == 0x3fff) {
            len--;
            offset += 2;
        } else {
            if (t.length != 2) {
                throw new IOException(
                        "Invalid path - file ID must be used");
            }
        }

        path = new short[len];
        System.arraycopy(root, 0, path, 0, root.length);
        for (int i = root.length; i < path.length; i++) {
            path[i] = Utils.getShort(t.data, offset);
            offset += 2;
        }
        return path;
    }

    /**
     * Selects file using only SELECT APDU command with file identifier.
     * According to WIM specification support of this selection method
     * is mandatory.
     * @param path file path
     * @throws IOException if IO error occurs
     */
    public void select(short[] path) throws IOException {

        if (root == null) {
            if (path.length == 1) {
                select(path[0]);
                return;
            }
            throw new IOException(
                    "Invalid path - file ID must be used");
        }

        int c = 0;      // index of first path element that differs
        while (c < pathLength && c < path.length) {
            if (currentPath[c] != path[c]) {
                break;
            }
            c++;
        }

        if (c == 0) {
            throw new IOException("Invalid path - wrong root directory");
        }

        if (c == pathLength &&
            pathLength == (path.length - 1) &&
            isEFSelected &&
            currentPath[pathLength] == path[pathLength]) {
            return;
        }

        while (c < pathLength) {
            pathLength--;
            select(currentPath[pathLength]);
        }

        while (pathLength < path.length) {
            select(path[pathLength]);
            currentPath[pathLength] = path[pathLength];
            if (isEFSelected) {
                if (pathLength == path.length - 1) {
                    break;
                }
                throw new IOException("Invalid path - EF in path");
            }
            pathLength++;
        }
    }

    /**
     * Selects file by ID.
     * @param id file ID
     * @throws IOException if IOError occurs
     */
    abstract public void select(short id) throws IOException;

    /**
     * Reads the current EF.
     * @return array that contains EF body.
     * @throws IOException if IO error occurs
     */
    public byte[] readFile() throws IOException {
        byte[] data = readData(0, currentFileSize, 0);
        return data;
    }


    /**
     * Reads part of selected file.
     * @param offset the offset into data buffer where data should be
     * placed
     * @param length data length
     * @param fileOffset file data offset
     * @throws IOException if IO error occurs
     * @return data byte array of the data
     */
    abstract public byte[] readData(int offset, int length, int fileOffset)
            throws IOException;


    /**
     * Loads and parses one DER encoded object.
     * @param l location of the object.
     * @return the object.
     * @throws IOException if I/O error occurs
     * @throws TLVException if parsing error occurs
     */
    public TLV loadObject(Location l) throws IOException, TLVException {
        select(l.path);
        byte[] tmp = readData(0, l.length, l.offset);
        return new TLV(tmp, 0);
    }

    /**
     * Converts PKCS#15 path into location.
     * @param path TLV structure that represents path
     * @return Location object
     */
    public Location pathToLocation(TLV path) throws IOException {

        try {
            if (path.type == TLV.SEQUENCE_TYPE) {
                path = path.child;
            }

            short[] p = makePath(path);
            int offset = 0;
            int length = -1;

            if (path.next != null) {
                offset = path.next.getInteger();
                length = path.next.next.getInteger();
            }
            return new Location(p, offset, length);
        } catch (IOException e) {
            throw new IOException("Invalid path structure");
        } catch (NullPointerException npe) {
            throw new IOException("Invalid path structure");
        } catch (IndexOutOfBoundsException iobe) {
            throw new IOException("Invalid path structure");
        }
    }

    /**
     * Returns the size of currently selected EF or -1 if DF is
     * selected.
     * @return file size
     */
    public int getCurrrentFileSize() {
        return currentFileSize;
    }
}
