/**
 * 
 */
package anoncard;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.LinkedList;
import java.util.List;

import javacard.framework.ISO7816;

import org.junit.Before;
import org.junit.Test;

import mixconfig.tools.dataretention.smartcard.ApduConstants;
import mixconfig.tools.dataretention.smartcard.Helpers;

/**
 * @author christoph
 * 
 */
public class ANONCardAppletSubTest_Logging extends AbtractANONCardAppletSubTest {

	@Before
	public void setUp() throws Exception {
		super.setUp();
		LogEntry[] logEntries = new LogEntry[4];
		assertTrue("Stupid test data", logEntries.length <= ApduConstants.MAXIMAL_SIZE_OF_ANONCARDAPPLET_LOG);

		for (int i = 0; i < logEntries.length; i++) {
			byte[] messages = new byte[] { (byte) i, (byte) i };
			logEntries[i] = new LogEntry((byte) i, messages);
			getLog().addLogEntry(logEntries[i]);
		}
	}

	/**
	 * 
	 */
	@Test
	public void testProcessGetLogSize() {
		apduBuffer[ISO7816.OFFSET_INS] = ApduConstants.INSTRUCTION_GET_ANONCARDAPPLET_LOG_SIZE;
		assertProcessResponse(ANONShort.toBytes(getLog().size()));
		getLog().addLogEntry(new LogEntry((byte) 0, new byte[] {}));
		assertProcessResponse(ANONShort.toBytes(getLog().size()));
		getLog().addLogEntry(new LogEntry((byte) 0, new byte[] {}));
		assertProcessResponse(ANONShort.toBytes(getLog().size()));
	}

	/**
	 * 
	 */
	@Test
	public void testProcessGetLogEntry() {
		apduBuffer[ISO7816.OFFSET_INS] = ApduConstants.INSTRUCTION_GET_ANONCARDAPPLET_LOG_ENTRY;
		for (int i = 0; i < getLog().size(); i++) {
			apduBuffer[ISO7816.OFFSET_P1] = ANONShort.toBytes((short) i)[0];
			apduBuffer[ISO7816.OFFSET_P2] = ANONShort.toBytes((short) i)[1];
			LogEntry entry = getLog().getLogEntry((short) i);
			assertProcessResponse(ANONShort.toBytes((short) (entry.getData().length)));

			assertArrayEquals(entry.getData(), getDataSendChannel().getNext(Short.MAX_VALUE));
		}
		byte[] p1p2 = ANONShort.toBytes((short) (getLog().size() + 1));
		apduBuffer[ISO7816.OFFSET_P1] = p1p2[0];
		apduBuffer[ISO7816.OFFSET_P2] = p1p2[1];
		assertExceptionResponse(ApduConstants.EXCEPTION_ILLEGAL_ARGUMENT);
	}

	@Test
	public void testLogEntryAfterProcess() {
		byte[] adminPin = Helpers.concatenate(new byte[] { 0x01, 0x45 }, new byte[ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN]);
		byte[] operatorPin = Helpers.concatenate(new byte[] { 0x01, 0x45 }, new byte[ApduConstants.LENGTH_OF_OPERATOR_PIN]);
		List<Instruction> instructions = new LinkedList<Instruction>();
		applet.generateKeyPair();

		instructions.add(new Instruction(ApduConstants.INSTRUCTION_START_TRANSACTION, LogMessages.TRANSACTION_STARTED));
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_ABORT_TRANSACTION, LogMessages.TRANSACTION_ABORTED));
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_START_TRANSACTION, LogMessages.TRANSACTION_STARTED));
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_ADD_ADMINISTRATOR, adminPin, Helpers.concatenate(LogMessages.ADDED_ADMINISTRATOR, new byte[] { 0x45 })));
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_ADD_OPERATOR, operatorPin, Helpers.concatenate(LogMessages.ADDED_OPERATOR, new byte[] { 0x45 })));
		// TODO ? instructions.add(new
		// Instruction(ApduConstants.INSTRUCTION_CHANGE_ADMINISTRATOR_PIN,
		// Helpers.concatenate(adminPin, new
		// byte[ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN]));
		// TODO ? instructions.add(new
		// Instruction(ApduConstants.INSTRUCTION_CHANGE_OPERATOR_PIN,
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_REMOVE_ADMINISTRATOR, new byte[] { 0x45 }, Helpers.concatenate(LogMessages.REMOVED_ADMINISTRATOR,
				new byte[] { 0x45 })));

		instructions.add(new Instruction(ApduConstants.INSTRUCTION_REMOVE_OPERATOR, new byte[] { 0x45 }, Helpers.concatenate(LogMessages.REMOVED_OPERATOR, new byte[] { 0x45 })));
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_SET_NEEDED_NUMBER_OF_ADMINISTROTORS, (byte) 0x01, (byte) 0x00, Helpers.concatenate(
				LogMessages.SET_NEEDED_NUMBER_OF_ADMINISTRAOTRS, LogMessages.getReadableNumber((short) 0x01))));

		instructions.add(new Instruction(ApduConstants.INSTRUCTION_SET_NEEDED_NUMBER_OF_OPERATORS, (byte) 0x01, (byte) 0x00, Helpers.concatenate(
				LogMessages.SET_NEEDED_NUMBER_OF_OPERTORS, LogMessages.getReadableNumber((short) 0x01))));

		// TODO ? instructions.add(new
		// Instruction(ApduConstants.INSTRUCTION_SET_PERMISSION_CHANGE_SETTINGS,
		// TODO ! instructions.add(new
		// Instruction(ApduConstants.INSTRUCTION_GET_SYMMETRIC_KEY_FOR_LOGFILES,
		byte[] adminLogin = getMinimalNumberOfValidAdminLogins();
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_COMMIT_TRANSACTION, adminLogin, Helpers.concatenate(LogMessages.TRANSACTION_COMMITTED, applet.getNamesFromBytes(
				adminLogin, (short) 0, (short) adminLogin.length, ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN))));

		instructions.add(new Instruction(ApduConstants.INSTRUCTION_RESET_ANONCARDAPPLET_LOG, adminLogin, Helpers.concatenate(LogMessages.RESET_LOG, applet.getNamesFromBytes(
				adminLogin, (short) 0, (short) adminLogin.length, ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN))));

		// ########################## LAST TO TEST! ##########################
		instructions.add(new Instruction(ApduConstants.INSTRUCTION_RESET_ANONCARDAPPLET_CARD, adminLogin, Helpers.concatenate(LogMessages.RESET_CARD, applet.getNamesFromBytes(
				adminLogin, (short) 0, (short) adminLogin.length, ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN))));

		for (Instruction instruction : instructions) {
			apduBuffer[ISO7816.OFFSET_INS] = instruction.instruction;
			apduBuffer[ISO7816.OFFSET_P1] = instruction.p1;
			apduBuffer[ISO7816.OFFSET_P2] = instruction.p2;
			setAPDUData(instruction.send);
			assertProcessResponse(instruction.receive);
			LogEntry entry = getLog().getLogEntry((short) (getLog().size() - 1));
			assertEquals("Instruction not logged", instruction.instruction, entry.getInstruction());
			assertArrayEquals("Log Message of instruction 0x" + Helpers.byteToHex(instruction.instruction) + " is wrong.\nSend data:\t"
					+ Helpers.bytesToHexDigits(instruction.send) + "\nexpected Log:\t"
					+ Helpers.utf8BytesToString(Helpers.copyOfRange(instruction.getLogMessage(), 2, entry.getData().length)) + "\nactual Log\t\t"
					+ Helpers.utf8BytesToString(Helpers.copyOfRange(entry.getData(), 2, entry.getData().length)) + "\n", instruction.getLogMessage(), entry.getData());
			//System.out.println(Helpers.utf8BytesToString(Helpers.copyOfRange(entry.getData(), 2, entry.getData().length)));
		}
	}

	class Instruction {
		public byte instruction;
		public byte p1 = 0x00;
		public byte p2 = 0x00;
		public byte[] send = new byte[] {};
		public byte[] receive = new byte[] {};
		private byte[] logMessage = new byte[] {};
		public boolean succeeded = true;

		/**
		 * @param instruction
		 * @param send
		 * @param receive
		 */
		public Instruction(byte instruction, byte[] send, byte[] receive, byte[] logMessage) {
			this.instruction = instruction;
			this.send = send;
			this.receive = receive;
			this.logMessage = logMessage;
		}

		/**
		 * @param instruction
		 * @param send
		 */
		public Instruction(byte instruction, byte[] send, byte[] logMessage) {
			this.instruction = instruction;
			this.send = send;
			this.logMessage = logMessage;
		}

		/**
		 * @param instruction
		 */
		public Instruction(byte instruction, byte[] logMessage) {
			this.instruction = instruction;
			this.logMessage = logMessage;
		}

		/**
		 * @param instruction
		 */
		public Instruction(byte instruction, byte p1, byte p2, byte[] logMessage) {
			this.instruction = instruction;
			this.p1 = p1;
			this.p2 = p2;
			this.logMessage = logMessage;
		}

		public byte[] getLogMessage() {
			byte[] message = new byte[2 + logMessage.length];
			message[0] = instruction;
			message[1] = (succeeded ? ApduConstants.TRUE : ApduConstants.FALSE);
			for (int i = 0; i < logMessage.length; i++) {
				message[2 + i] = logMessage[i];
			}
			return message;
		}

	}
}
