/*
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package Purse;

import javacard.framework.APDU;
import javacard.framework.AID;
import javacard.framework.ISOException;
import javacard.framework.Util;
import javacard.framework.Shareable;
import javacard.framework.OwnerPIN;
import javacard.framework.ISO7816;
import javacard.framework.JCSystem;

/**
 *
 * @author  vo113324
 */
public class PurseApplet extends javacard.framework.Applet implements PurseShareable{
    //Variables
    private short balance;
    private byte[] number;
    private OwnerPIN pin;
    private static byte[] defaultPIN = { (byte)0x33, (byte)0x33, (byte)0x33, (byte)0x33 };
    private static byte[] defaultAccNo = {
                                         (byte)0x31, (byte)0x32,
                                         (byte)0x33, (byte)0x34,
                                         (byte)0x35
    };
    private byte[] UserName;
    private byte[] UserSurname;
    private byte[] UserAddress;

    //Constands
    final static byte MAX_TRIES = (byte)0x0A;
    final static byte MAX_LENGTH = (byte)0x04;
    final static short MAX_AMOUNT = (short)1000; // for whatever reason
    final static short MAX_TRANSACTION = (short)200;

    //CLA byte code
    final static byte PURSE_CLA =(byte)0x80;

    //INS byte code
    final static byte VALIDATE = (byte) 0x21;
    final static byte CREDIT = (byte) 0x22;
    final static byte DEBIT = (byte) 0x23;
    final static byte GET_BALANCE = (byte) 0x24;
    final static byte GET_ACCOUNT_NUMBER = (byte) 0x25;
    final static byte GET_USER_NAME = (byte) 0x26;
    final static byte GET_USER_ADDRESS = (byte) 0x27;
    final static byte GET_USER_SURNAME = (byte) 0x28;
    final static byte SET_ACCOUNT_NUMBER = (byte) 0x29;
    final static byte SET_USER_ADDRESS = (byte) 0x30;
    final static byte SET_USER_NAME = (byte) 0x31;
    final static byte SET_USER_SURNAME = (byte) 0x32;
    final static byte SET_USER_PIN = (byte) 0x33;

    final static short SW_UNDERFLOW = (short)0x9100;
    final static short SW_OVERFLOW  = (short)0x9101;
    final static short SW_BAD_ARGUMENT  = (short)0x9102;
    final static short SW_WRONG_LE = (short)0x9103;
    final static short SW_NOT_VERIFIED = (short)0x9104;
    final static short SW_VERIFICATION_FAILED = (short)0x9105;
    final static short SW_TRANSACTION_OVERFLOW = (short)0x9106;
    final static short SW_NO_DATA = (short)0x9107;
    final static short SW_APPLET_LOCKED = (short)0x9108;

    public PurseApplet() {
        super();                     // export it
        number = defaultAccNo;
        balance = (short)0;
        pin = new OwnerPIN(MAX_TRIES,MAX_LENGTH);
        pin.update(defaultPIN,(short)0,(byte)0x04);

        register();
    }


    public static void install(byte[] aid, short s, byte b) {
        new PurseApplet();
    }

    public boolean select() {
      if ( pin.getTriesRemaining() == 0 )
         return false;
      return true;
    }
    public void deselect() {
        if ( pin.getTriesRemaining() != 0 )
            pin.reset();
    }

    public void process(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();

        buffer[ISO7816.OFFSET_CLA] = (byte) (buffer[ISO7816.OFFSET_CLA] &
                                             (byte) 0xFC);

        if ((buffer[ISO7816.OFFSET_CLA] == 0) &&
            (buffer[ISO7816.OFFSET_INS] == (byte) (0xA4)))
            return;

        if (buffer[ISO7816.OFFSET_CLA] != PURSE_CLA)
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        switch (buffer[ISO7816.OFFSET_INS]) {
        case DEBIT:
            debit(apdu);
            break;
        case CREDIT:
            credit(apdu);
            break;
        case VALIDATE:
            validateUserPIN(apdu);
            break;
        case GET_ACCOUNT_NUMBER:
            getAccountNumber(apdu);
            break;
        case GET_BALANCE:
            getBalance(apdu);
            break;
        case GET_USER_ADDRESS:
            getUserAddress(apdu);
            break;
        case GET_USER_NAME:
            getUserName(apdu);
            break;
        case GET_USER_SURNAME:
            getUserSurname(apdu);
            break;
        case SET_ACCOUNT_NUMBER:
            setAccountNumber(apdu);
            break;
        case SET_USER_ADDRESS:
            setUserAddress(apdu);
            break;
        case SET_USER_NAME:
            setUserName(apdu);
            break;
        case SET_USER_SURNAME:
            setUserSurname(apdu);
            break;
        case SET_USER_PIN:
            setUserPIN(apdu);
            break;
        default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

    private void credit(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();

        byte[] amount = new byte[2];
        Util.arrayCopy(buffer, (short)ISO7816.OFFSET_CDATA, amount, (short)0, (short)2);
        short amountShort = (short)(amount[1] & 0x0F);
        amountShort += (short)(amount[1] & 0xF0);
        amountShort += (short)(amount[0]<<8);

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(amountShort<=0) ISOException.throwIt(SW_BAD_ARGUMENT);

        if(amountShort>MAX_TRANSACTION) ISOException.throwIt(SW_TRANSACTION_OVERFLOW);

        if((short)(balance+amountShort) > MAX_AMOUNT) ISOException.throwIt(SW_OVERFLOW);

        balance +=amountShort;
    }

    private void debit(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();

        byte[] amount = new byte[2];
        Util.arrayCopy(buffer, (short)ISO7816.OFFSET_CDATA, amount, (short)0, (short)2);
        short amountShort = (short)(amount[1] & 0x0F);
        amountShort += (short)(amount[1] & 0xF0);
        amountShort += (short)(amount[0]<<8);

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if (amountShort <= 0) ISOException.throwIt(SW_BAD_ARGUMENT);

        if(amountShort>MAX_TRANSACTION) ISOException.throwIt(SW_TRANSACTION_OVERFLOW);

        if ((short)(balance - amountShort) < 0) ISOException.throwIt(SW_UNDERFLOW);

        balance -= amountShort;
    }

    private void getBalance(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if ( le < (short)2 )ISOException.throwIt(SW_WRONG_LE);

        apdu.setOutgoingLength((byte)2);
        buffer[0] = (byte)(balance >> 8);
        buffer[1] = (byte)(balance & 0xFF);

        apdu.sendBytes((short)0, (short)2);
    }

    private void setAccountNumber(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short bytesRead = apdu.setIncomingAndReceive();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(bytesRead != 5) ISOException.throwIt(SW_BAD_ARGUMENT);

        Util.arrayCopy(buffer,ISO7816.OFFSET_CDATA, this.number, (short)0, (short)5);
    }

    private void getAccountNumber(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();
        apdu.setOutgoingLength((short)5);

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(le<5) ISOException.throwIt(SW_WRONG_LE);

        Util.arrayCopy(number, (short) 0, buffer, (short) 0, (short) 5);
        apdu.sendBytes((short) 0, (short) 5);
    }

    private void validateUserPIN(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        byte byteRead = (byte)(apdu.setIncomingAndReceive());

        //if(pin.getTriesRemaining() == 0) ISOException.throwIt(SW_APPLET_LOCKED);

        if ( pin.check(buffer, ISO7816.OFFSET_CDATA,byteRead) == false )
            ISOException.throwIt(SW_VERIFICATION_FAILED);
    }

    private void setUserName(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short byteRead = apdu.setIncomingAndReceive();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(byteRead > 15 || byteRead ==0) ISOException.throwIt(SW_BAD_ARGUMENT);

        UserName = new byte[(short)byteRead];
        Util.arrayCopy(buffer, (short)ISO7816.OFFSET_CDATA, UserName, (short)0, (short)byteRead);
    }

    private void getUserName(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(UserName == null) ISOException.throwIt(SW_NO_DATA);

        apdu.setOutgoingLength((short)UserName.length);
        Util.arrayCopy(UserName,(short)0,buffer,(short)0,(short)UserName.length);
        apdu.sendBytes((short)0,(short)UserName.length);
    }

    private void setUserSurname(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short byteRead = apdu.setIncomingAndReceive();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(byteRead > 20 || byteRead ==0) ISOException.throwIt(SW_BAD_ARGUMENT);

        UserSurname = new byte[(short)byteRead];
        Util.arrayCopy(buffer, (short)ISO7816.OFFSET_CDATA, UserSurname, (short)0, (short)byteRead);
    }

    private void getUserSurname(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(UserSurname == null) ISOException.throwIt(SW_NO_DATA);

        apdu.setOutgoingLength((short)UserSurname.length);
        Util.arrayCopy(UserSurname,(short)0,buffer,(short)0,(short)UserSurname.length);
        apdu.sendBytes((short)0,(short)UserSurname.length);
    }

    private void setUserAddress(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short byteRead = apdu.setIncomingAndReceive();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(byteRead > 70 || byteRead ==0) ISOException.throwIt(SW_BAD_ARGUMENT);

        UserAddress = new byte[(short)byteRead];
        Util.arrayCopy(buffer, (short)ISO7816.OFFSET_CDATA, UserAddress, (short)0, (short)byteRead);
    }

    private void getUserAddress(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();

        if(!pin.isValidated()) ISOException.throwIt(SW_NOT_VERIFIED);

        if(UserAddress == null) ISOException.throwIt(SW_NO_DATA);

        apdu.setOutgoingLength((short)UserAddress.length);
        Util.arrayCopy(UserAddress,(short)0,buffer,(short)0,(short)UserAddress.length);
        apdu.sendBytes((short)0,(short)UserAddress.length);
    }

    private void setUserPIN(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        byte byteRead = (byte)apdu.setIncomingAndReceive();
        byte lc = buffer[ISO7816.OFFSET_LC];
        byte p1 = buffer[ISO7816.OFFSET_P1];
        byte p2 = buffer[ISO7816.OFFSET_P2];

        if ( pin.check(buffer, ISO7816.OFFSET_CDATA, p1) == false )
            ISOException.throwIt(SW_VERIFICATION_FAILED);
        if((short)p2>MAX_LENGTH || (short)p2 ==0) ISOException.throwIt(SW_BAD_ARGUMENT);

        pin.update(buffer,(short)(ISO7816.OFFSET_CDATA+p1),p2);
    }

    public void foreignDebit(short amount){
        if (amount <= 0) ISOException.throwIt(SW_BAD_ARGUMENT);

        if(amount>MAX_TRANSACTION) ISOException.throwIt(SW_TRANSACTION_OVERFLOW);

        if ((short) (balance - amount) < 0) ISOException.throwIt(SW_UNDERFLOW);

        JCSystem.beginTransaction();
        balance -= amount;
        JCSystem.commitTransaction();
    }

    public Shareable getShareableInterfaceObject(AID clientAID, byte parameter){
        return this;
    }
}




