package Bonus;

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


/**
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2007</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */
public class BonusApplet extends javacard.framework.Applet{

    private short bonus;
    private OwnerPIN pin;
    private byte[] defaultPIN = { (byte)0x33, (byte)0x33, (byte)0x33, (byte)0x33 };

    //Constands
    final static byte MAX_TRIES = (byte)0x03;
    final static byte MAX_LENGTH = (byte)0x04;
    final static short MAX_BONUS = (short)1000;

    final static byte BONUS_CLA =(byte)0x80;

    final static byte VALIDATE = (byte) 0x21;
    final static byte MAKE_PURCHASE = (byte) 0x22;
    final static byte SUBSTRACT_BONUS = (byte) 0x23;
    final static byte ERASE_BONUS = (byte) 0x24;
    final static byte GET_BONUS = (byte) 0x25;
    final static byte CHANGE_USER_PIN = (byte) 0x26;

    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 BonusApplet() {
        super();
        bonus=(short)0;

        pin = new OwnerPIN(MAX_TRIES,MAX_LENGTH);
        JCSystem.beginTransaction();
        pin.update(defaultPIN,(short)0,(byte)0x04);
        JCSystem.abortTransaction();
        register();
    }

    /**
     * process
     *
     * @param apdu APDU
     * @throws ISOException
     */
    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] != BONUS_CLA)
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        switch (buffer[ISO7816.OFFSET_INS]) {
        case VALIDATE:
            validateUserPIN(apdu);
            return;
        case GET_BONUS:
            getBonus(apdu);
            return;
        case ERASE_BONUS:
            eraseBonus(apdu);
            return;
        case SUBSTRACT_BONUS:
            substractBonus(apdu);
            return;
        case MAKE_PURCHASE:
            makePurchase(apdu);
            return;
        case CHANGE_USER_PIN:
            changeUserPIN(apdu);
            return;
        default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

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

    public boolean select() {
      if ( pin.getTriesRemaining() == 0 )
         return false;
      return true;

    }

    public void deselect() {
      pin.reset();
    }

    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 changeUserPIN(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);

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

    public void makePurchase(APDU apdu){
        byte[] PURSE_AID = {
              (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00,
              (byte) 0x62, (byte) 0x03, (byte) 0x01, (byte) 0x0C,
              (byte) 0x08, (byte) 0x01
        };

        byte[] buffer = apdu.getBuffer();
        short bytesRead = 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);

        AID PurseAID = new AID(PURSE_AID,(short)0, (byte)0x0A);
        PurseShareable sio = (PurseShareable) JCSystem.getAppletShareableInterfaceObject(PurseAID, (byte)0x00);
        sio.foreignDebit((short)amountShort);

        short tempBonus = (short)(amountShort/10);
        if(tempBonus>=(short)(MAX_BONUS-bonus))
            bonus=MAX_BONUS;
        else
            bonus += tempBonus;
    }

    public void substractBonus(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short bytesRead = 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 (bonus < amountShort) ISOException.throwIt(SW_UNDERFLOW);

        bonus -= amountShort;
    }

    public void eraseBonus(APDU apdu){
        byte[] buffer = apdu.getBuffer();
        short le = apdu.setOutgoing();
        short credits = bonus;
        bonus = 0;

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

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

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

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

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

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

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

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

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

}
