package com.gemalto.examples;

/*
 * Imported packages
 */
import com.gemalto.examples.utility.*;
import sim.toolkit.*;
import sim.access.*;
import javacard.framework.*;

public class ZyrconCinemas extends javacard.framework.Applet implements
ToolkitInterface, ToolkitConstants {
	// Mandatory variables
	private SIMView gsmFile;

	private ToolkitRegistry reg;

	// Main Menu
	private byte idMenu1;

	private byte[] Menu1;

	private byte idMenu2;

	private byte[] Menu2;

	// Sub Menu
	private byte[] addCinemas = {
	    (byte) 'A', (byte) 'd', (byte) 'd', (byte) ' ', (byte) 'a', (byte) ' ', 
	    (byte) 'C', (byte) 'i', (byte) 'n', (byte) 'e', (byte) 'm', (byte) 'a', 
	    (byte) 's' };

	private byte[] promptInputPlace = { 
	    (byte) 'P', (byte) 'l', (byte) 'e', (byte) 'a', (byte) 's', (byte) 'e',
	    (byte) ' ', (byte) 'i', (byte) 'n', (byte) 'p', (byte) 'u', (byte) 't',
	    (byte) ' ', (byte) 'a', (byte) ' ', (byte) 'c', (byte) 'i', (byte) 'n',
	    (byte) 'e', (byte) 'm', (byte) 'a', (byte) 's' };
	
	// Server address
	private byte[] serverAddress = { 
	    (byte) 0x08, (byte) '2', (byte) '2', (byte) '1', (byte) '1', (byte) '8',
	    (byte) '7',	(byte) '9',	(byte) '9'};
	
	// Commands
	private byte[] cmdGetCinema = { 
	    (byte) 'g', (byte) 'e', (byte) 't', (byte) 'C', (byte) 'i', (byte) 'n',
	    (byte) 'e',	(byte) 'm',	(byte) 'a',	(byte) '*'};
	
	private byte[] cmdGetShowTime = { 
	    (byte) 'g', (byte) 'e', (byte) 't', (byte) 'S', (byte) 'h', (byte) 'o',
	    (byte) 'w',	(byte) 't',	(byte) 'i',	(byte) 'm', (byte) 'e', (byte) '*' };
	
	// Custom Commands
	final byte CLA_MOVIE_UPDATE =           (byte) 0x28;
	
	final byte INS_UPDATE_MOVIE =           0x01;
	
	final byte INS_CLEAR_MOVIE =            0x02;
	
	final short SW_MOVIE_FILE_NOT_FOUND =   0x6FF0;
	
	final short SW_INDEX_OUT_OF_RANGE =     0x6FF1;
	
	final short SW_DATA_TOO_LONG =          0x6FF2;

	
	// Temporary buffer for storing intermediate data and results
	private byte[] tempBuffer;
	
	// Buffer for the FCI (File Control Information)
	private byte[] EFFciBuffer;

	/**
	 * Constructor of the applet
	 */
	public ZyrconCinemas() {
		// Get the GSM application reference
		gsmFile = SIMSystem.getTheSIMView();
		// Get the reference of the applet ToolkitRegistry object
		reg = ToolkitRegistry.getEntry();

		tempBuffer = JCSystem.makeTransientByteArray((short) 50,
				JCSystem.CLEAR_ON_RESET);
		/**@todo: Customize your menu titles here*/
		Menu1 = new byte[] { (byte) 'C', (byte) 'i', (byte) 'n', (byte) 'e',
				(byte) 'm', (byte) 'a', (byte) 's' };
		Menu2 = new byte[] { (byte) 'M', (byte) 'o', (byte) 'v', (byte) 'i',
				(byte) 'e', (byte) 's' };
		// Define the applet Menu Entry
		idMenu1 = reg.initMenuEntry(Menu1, (short) 0, (short) Menu1.length,
				PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0);
		idMenu2 = reg.initMenuEntry(Menu2, (short) 0, (short) Menu2.length,
				(byte) 0, false, (byte) 0, (short) 0);
		// Define the Formatted SMS PP event that trigger the applet
		reg.setEvent(EVENT_FORMATTED_SMS_PP_ENV);
		
		// Initialize the FCI Buffer
		EFFciBuffer = new byte[22];

	}

	/**
	 * Method called by the JCRE at the installation of the applet
	 * @param bArray the byte array containing the AID bytes
	 * @param bOffset the start of AID bytes in bArray
	 * @param bLength the length of the AID bytes in bArray
	 */
	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// Create the Java SIM toolkit applet
		ZyrconCinemas StkCommandsExampleApplet = new ZyrconCinemas();
		// Register this applet
		StkCommandsExampleApplet.register(bArray, (short) (bOffset + 1),
				(byte) bArray[bOffset]);
	}

	/**
	 * Method called by the SIM Toolkit Framework
	 * @param event the byte representation of the event triggered
	 */
	public void processToolkit(byte event) {
		EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler();

		// Manage the request following the MENU SELECTION event type
		if (event == EVENT_MENU_SELECTION) {
			// Get the selected item
			byte selectedItemId = envHdlr.getItemIdentifier();

			// Perform the required service following the Menu1 selected item
			if (selectedItemId == idMenu1) {
				menu1Action();
			}

			// Perform the required service following the Menu2 selected item
			if (selectedItemId == idMenu2) {
				menu2Action();
			}
		}
		// Manage the request following the FORMATTED SMS PP event type
		if (event == EVENT_FORMATTED_SMS_PP_ENV) {
			formattedSmsDownloadService();
		}
	}

	/**
	 * Method called by the JCRE, once selected
	 * @param apdu the incoming APDU object
	 */
	public void process(APDU apdu) {
		// ignore the applet select command dispached to the process
		if (selectingApplet()) {
			return;
		}
	}

	/**
	 * Manage the Menu1 selection
	 */
	private void menu1Action() {
		// Get the received envelope
		ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();

		// Prepare the "Cinemas" STK Menu
		proHdlr.init(PRO_CMD_SELECT_ITEM, (byte) 0x00, (byte) ToolkitConstants.DEV_ID_ME);
		proHdlr.appendTLV((byte) (TAG_ALPHA_IDENTIFIER), Menu1, (short) 0x0000, (short) Menu1.length);

		// Reset the FCI Buffer
		resetBuffer(EFFciBuffer, (byte) 0x00);

		// Select ADN file and store ADN file properties in the FCI Buffer
		FileAccessLibrary.selectDF_telecom(gsmFile);
		FileAccessLibrary.selectEF(gsmFile, (short) 0x6F3A, EFFciBuffer);

		// Get the number of records and the length of each record
		short recCount = FileAccessLibrary.getLinearFileRecordNumber(EFFciBuffer);
		short recLength = FileAccessLibrary.getLinearFileRecordLength(EFFciBuffer);

		// Reset buffer
		resetBuffer(tempBuffer, (byte) 0xFF);

		// Append sub menu item for each record that not empty
		short lastRecordIndex = 0;
		for (short i = 1; i <= recCount; i ++) {
			FileAccessLibrary.readRecord(gsmFile, i, tempBuffer, (short) recLength);
			if (!FileAccessLibrary.isEmptyRecord(tempBuffer)) {
				byte[] name = FileAccessLibrary.getNameFromAdnRecord(tempBuffer, recLength);
				proHdlr.appendTLV((byte) (TAG_ITEM), (byte) i, name, (short) 0x0000, (short) name.length);
				lastRecordIndex = i;
			}
		}

		// select the MF to avoid locking the EF ADN
		FileAccessLibrary.selectMF(gsmFile);

		// append the "add a cinemas" item
		proHdlr.appendTLV((byte) (TAG_ITEM), (byte) (lastRecordIndex + 1), addCinemas,
				(short) 0x0000, (short) addCinemas.length);

		// send the proactive command
		proHdlr.send();

		// get the user selection in sub-menu
		ProactiveResponseHandler rspHdlr = ProactiveResponseHandler
				.getTheHandler();
		byte selItemId = rspHdlr.getItemIdentifier();

		// if want to add a cinemas, send a message
		if (selItemId == (byte) (lastRecordIndex + 1)) {
			// Prompt user to input a place
			proHdlr.initGetInput((byte) 0x01, DCS_8_BIT_DATA, promptInputPlace,
					(short) 0, (short) promptInputPlace.length, (short) 3,
					(short) 15);

			// Send the Get Input proactive command to the mobile equipment
			byte result = proHdlr.send();

			// check result
			if (result == (byte) 0) {
				// Prepare the user data for sms (L-V structure)
				// reset buffer
				resetBuffer(tempBuffer, (byte) 0x00);
				// the length
				tempBuffer[0] = (byte) (cmdGetCinema.length + rspHdlr.getTextStringLength());
				// then the command prefix
				Util.arrayCopy(cmdGetCinema, (short) 0, tempBuffer, (short) 1, (short)cmdGetCinema.length);
				// then the user input content
				rspHdlr.copyTextString(tempBuffer, (short) (1 + cmdGetCinema.length));
				
				// Send the message
				SMSLibrary.sendShortMessage(proHdlr, tempBuffer, serverAddress);
			}
		} else {
			// If select a cinemas in the menu, setup the call
			// Reset buffer
			resetBuffer(EFFciBuffer, (byte) 0x00);
			
			// Select ADN
			FileAccessLibrary.selectDF_telecom(gsmFile);
			FileAccessLibrary.selectEF(gsmFile, (short) 0x4F3A, EFFciBuffer);

			// Reset buffer
			resetBuffer(tempBuffer, (byte) 0x00);
			
			// Read ADN record
			FileAccessLibrary.readRecord(gsmFile, selItemId, tempBuffer, (short) recLength);
			
			// set up call
			setupCallByAdnRecord(proHdlr, tempBuffer, recLength);
			
			// Select MF
			FileAccessLibrary.selectMF(gsmFile);
		}
		return;
	}

	/**
	 * Manage the Menu2 selection
	 */
	private void menu2Action() {
		/**@todo: Replace following sample code with your implementation*/
		// Get the received envelope
		ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();

		// Prepare for "Current Movie" submenu
		proHdlr.init(PRO_CMD_SELECT_ITEM, (byte) 0x00, (byte) ToolkitConstants.DEV_ID_ME);
		proHdlr.appendTLV((byte) (TAG_ALPHA_IDENTIFIER), Menu2, (short) 0x0000,
				(short) Menu2.length);
		
		// Reset buffer
		resetBuffer(EFFciBuffer, (byte) 0x00);
		
		// Select 4F99 file and store the file properties in EFFciBuffer
		FileAccessLibrary.selectDF_telecom(gsmFile);
		FileAccessLibrary.selectEF(gsmFile, (short) 0x4F99, EFFciBuffer);
		
	    // Get record number and record length
	    short recCount = FileAccessLibrary.getLinearFileRecordNumber(EFFciBuffer);
	    short recLength = FileAccessLibrary.getLinearFileRecordLength(EFFciBuffer);

	    // reset buffer
	    resetBuffer(tempBuffer, (byte) 0xFF);

	    // Append sub menu item for each record that not empty
	    for (short i = 1; i <= recCount; i ++) {
			FileAccessLibrary.readRecord(gsmFile, i, tempBuffer, (short) recLength);
		    if (!FileAccessLibrary.isEmptyRecord(tempBuffer)) {
		    	byte[] movieName = FileAccessLibrary.trimPaddingBytes(tempBuffer);
		    	proHdlr.appendTLV((byte) (TAG_ITEM), (byte) i, movieName, (short) 0x0000, (short) movieName.length);
		    }
	    }
	    
	    // select the MF to avoid locking the EF_4F99
	    FileAccessLibrary.selectMF(gsmFile);
	    
		// send the proactive command and check the result
		proHdlr.send();
		
		// get the user selection in sub-menu
		ProactiveResponseHandler rspHdlr = ProactiveResponseHandler
				.getTheHandler();
		byte selItemId = rspHdlr.getItemIdentifier();
		
		// send a message to get the description and show time
		sendSmsByMovieId(proHdlr, selItemId);
		
		return;
	}

	/**
	 * Method illustrating the use of the FORMATTED SMS PP ENV event.
	 */
	private void formattedSmsDownloadService() {
		/**@todo: Replace following sample code with your implementation*/
		EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler();

		// Copy the user data into tempBuffer[], command by command
		short dataLength = envHdlr.getSecuredDataLength();
		short dataOffset = 0;
		while (dataOffset != dataLength) {
			envHdlr.copyValue((short) (envHdlr.getSecuredDataOffset() + dataOffset), 
					tempBuffer, (short) 0, (short) 5);
			dataOffset += 5;
			// If it is our custom command
			if (tempBuffer[ISO7816.OFFSET_CLA] == CLA_MOVIE_UPDATE) {
				envHdlr.copyValue((short) (envHdlr.getSecuredDataOffset() + dataOffset), 
						tempBuffer, (short) 5, (short) tempBuffer[ISO7816.OFFSET_LC]);
				dataOffset += tempBuffer[ISO7816.OFFSET_LC];
				
				// process the custom command
				processCustomCommand(tempBuffer);
			} else {
				// there is some incorrect data
				break;
			}
		}
		
		return;
	}

	/**
	 * Process the custom command, to update or clear the movie name
	 * 
	 * @param cmdBuffer	the custom Command to process
	 * @throws ISOException throw this exception when any error occur
	 */
	private void processCustomCommand(byte[] cmdBuffer) throws ISOException {
		// get elements from command
		byte ins = cmdBuffer[ISO7816.OFFSET_INS];
		byte p1 = cmdBuffer[ISO7816.OFFSET_P1];
		byte lc = cmdBuffer[ISO7816.OFFSET_LC];
		
		switch (ins) {
    	case INS_UPDATE_MOVIE:	// update movie
    		updateMovieName(p1, cmdBuffer, ISO7816.OFFSET_CDATA, lc);
    		break;
    		
    	case INS_CLEAR_MOVIE:	// clear movie
    		updateMovieName(p1, null, (short) 0, (short) 0);
    		break;
    	}
	}
	

	/**
	 * ========================== utility methods ==================================
	 */

	/**
	 * Call this method to reset the buffer, fill with 0x00
	 * 
	 * @param buffer the buffer to be reset
	 */
	private static void resetBuffer(byte[] buffer, byte fillingByte) {
		Util.arrayFillNonAtomic(buffer, (short) 0, (short) buffer.length, (byte) fillingByte);
	}

	/**
	 * Set up a call according to the ADN record
	 * 
	 * @param proHdlr	the proactive command handler
	 * @param recData	the ADN record data
	 * @param recLength	the length of ADN record
	 */
	private void setupCallByAdnRecord(ProactiveHandler proHdlr, byte[] recData, short recLength) {
		// prepare to set up call
		proHdlr.init(PRO_CMD_SET_UP_CALL, (byte) 0x00, DEV_ID_NETWORK);
		
		// append the alpha identifier
		byte[] alphaBuf = FileAccessLibrary.getNameFromAdnRecord(recData, recLength);
		proHdlr.appendTLV(TAG_ALPHA_IDENTIFIER, alphaBuf, (short) 0, (short) alphaBuf.length);
		
		// append the address
		byte[] addressBuf = FileAccessLibrary.trimPaddingBytes(FileAccessLibrary.getNumberFromAdnRecord(recData, recLength));
		proHdlr.appendTLV(TAG_ADDRESS, addressBuf, (short) 0, (short) addressBuf.length);
		
		// send it
		proHdlr.send();
	}

	/**
	 * Send a message to get description and show time according to the movie id
	 * 
	 * @param proHdlr	the proactive command handler
	 * @param movieId	the id of the movie
	 */
	private void sendSmsByMovieId(ProactiveHandler proHdlr, byte movieId) {
		// Prepare the user data for sms (L-V structure)
		// get text for movie id
		byte[] movieIdText = SMSLibrary.convertValueToText(movieId);
		
		// reset buffer
		resetBuffer(tempBuffer, (byte) 0x00);
		
		// the length
		tempBuffer[0] = (byte) (cmdGetShowTime.length + movieIdText.length);
		
		// then the command prefix
		Util.arrayCopy(cmdGetShowTime, (short) 0, tempBuffer, (short) 1, (short)cmdGetShowTime.length);
		
		// then the movie id
		Util.arrayCopy(movieIdText, (short) 0, tempBuffer, (short) (1 + cmdGetShowTime.length), (short)movieIdText.length);
		
		// Send the message
		SMSLibrary.sendShortMessage(proHdlr, tempBuffer, serverAddress);
	}
	
	/**
	 * Update the movie name stored in EF_4F99
	 * 
	 * @param recIndex	index of the record to be updated
	 * @param buffer	the buffer that containing the movie name
	 * @param offset	the offset of the movie name in the buffer
	 * @param length	the length of the movie name, in bytes
	 * @throws ISOException	throw this exception when there is any error
	 */
	private void updateMovieName(short recIndex, byte[] buffer, short offset, short length) throws ISOException {
 		// reset buffer
		resetBuffer(EFFciBuffer, (byte) 0x00);
		
		// select the EF_4F99
		FileAccessLibrary.selectDF_telecom(gsmFile);
        FileAccessLibrary.selectEF(gsmFile, (short) 0x4F99, EFFciBuffer);
		
		// get record length and record number
		short recLength = FileAccessLibrary.getLinearFileRecordLength(EFFciBuffer);
		short recNumber = FileAccessLibrary.getLinearFileRecordNumber(EFFciBuffer);
		
		// check the index
		if (recIndex > recNumber) {
			throw new ISOException(SW_INDEX_OUT_OF_RANGE);
		}
		
		// check if the data is too long
		if (length > recLength) {
			throw new ISOException(SW_DATA_TOO_LONG);
		}
		
		// All is ok, we can clear the record now
		
		// prepare the new record first
		byte[] newRecord = new byte[recLength];
		resetBuffer(newRecord, (byte) 0xFF);
		if (length != 0) {
			Util.arrayCopy(buffer, offset, newRecord, (short) 0, length);
		}
		
		// then update it to the EF
		gsmFile.updateRecord(recIndex, (byte) 0x04, (short) 0, newRecord, (short) 0, recLength);
	}

}
