/*
 *   
 *
 * 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 com.sun.midp.io.j2me.apdu.APDUManager;
import com.sun.midp.io.j2me.apdu.Handle;

import java.io.IOException;

/**
 * Represents APDU connection to the WIM application.
 */
public class Connection {

    /*
        The set of command APDUs used by WIM application is very small.
        Methods of this class that place data into APDU buffer don't
        check for buffer overflow.
    */

    /** The last response APDU status word. */
    public int lastSW;

    /** Temporary buffer for APDU command. */
    private byte[] command;
    /** Current offset in the command buffer. */
    private int offset;
    /** APDU connection handle. */
    private Handle h;
    /** Unit size for the read command */
    private int unitSize;
    /** CLAbyte for the send command */
    private byte CLAbyte;

    /**
     * Constructs new connection object.
     * @param h APDU connection handle.
     */
    public Connection(Handle h) {
        this.h = h;
        command = new byte[255];
    }

    /**
     * Safely closes the connection.
     */
    public void done() {
        try {
            if (h != null) {
                APDUManager.closeConnection(h);
            }
        } catch (IOException e) {} // ignore
        h = null;
    }

    /**
     * Resets command buffer.
     * @return this connection object
     */
    public Connection resetCommand() {
        offset = 5;
        return this;
    }

    /**
     * Places one byte into command buffer.
     * @param data byte to be placed
     * @return this connection object
     */
    public Connection putByte(int data) {
        command[offset++] = (byte) data;
        return this;
    }

    /**
     * Places short value into the buffer.
     * @param data value to be placed
     * @return this connection object
     */
    public Connection putShort(int data) {
        command[offset++] = (byte) (data >> 8);
        command[offset++] = (byte) data;
        return this;
    }

    /**
     * Places the byte sequence into the buffer.
     * @param data data to be placed
     * @param off data offset
     * @param length data length
     * @return this connection object
     */
    public Connection putBytes(byte[] data, int off, int length) {
        System.arraycopy(data, off, command, offset, length);
        offset += length;
        return this;
    }

    /**
     * Sends the command.
     * @param INS INS byte for this command.
     * @param P1P2 APDU parameters value
     * @return response APDU
     * @throws IOException if IO error occurs
     */
    public byte[] sendCommand(int INS, int P1P2) throws IOException {
        return sendCommand(INS, P1P2, 240, true);
    }

    /**
     * Sends the command.
     * @param INS INS byte for this command.
     * @param P1P2 P1 and P2 values for this command
     * @param LE response expected length
     * @param check if true, verify that SW is 0x9000
     * @return response APDU
     * @throws IOException if IO error occurs
     */
    public byte[] sendCommand(int INS, int P1P2, int LE, boolean check)
            throws IOException {

        command[0] = (byte) (CLAbyte | h.channel);
        command[1] = (byte) INS;
        command[2] = (byte) (P1P2 >> 8);
        command[3] = (byte) P1P2;
        command[4] = (byte) (offset - 5);
        putByte(LE);

        byte[] tmp = APDUManager.exchangeAPDU(h, command);

        lastSW = ((tmp[tmp.length - 2] & 0xff) << 8) |
                  (tmp[tmp.length - 1] & 0xff);

        if (check && lastSW != 0x9000) {
            throw new IOException("SW = " + Integer.toHexString(lastSW));
        }
        return tmp;
    }

    /**
     * This method returns the ATR received from the card that this
     * Handle object is used to communicate with.
     * @return ATR information received from the card at startup or
     * reset. In case of I/O troubles returns null.
     */
    public byte[] getATR() {
        return h.getATR();
    }

    /**
     * This method returns the FCI received from the card that this
     * Handle object is used to communicate with.
     * @return FCI information received from the card at startup or
     * reset. In case of I/O troubles returns null.
     */
    public byte[] getFCI() {
        return h.getFCI();
    }

    /**
     * This method sets value for the unitSize variable
     * @param unitSize int Required value.
     */
    public void setUnitSize(int unitSize) {
        this.unitSize = unitSize;
    }

    /**
     * This method sets value for the CLAbyte variable
     * @param cla byte Required value.
     */
    public void setCLAbyte(byte cla) {
        this.CLAbyte = cla;
    }

}
