package anoncard;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import javacard.framework.APDU;
import mixconfig.tools.dataretention.smartcard.ApduConstants;

import org.junit.Test;

import anoncard.util.Helpers;
import anoncard.util.ResponseException;

public class ANONCardAppletSubTest_StatelessMethods extends AbtractANONCardAppletSubTest {

	public ANONCardAppletSubTest_StatelessMethods() {
		super();
	}

	/**
	 * Test method for {@link anoncard.ANONCardApplet#getName(APDU)}.
	 */
	@Test
	public void testGetName() {
		byte[] name = new byte[] { 5, 7, 6, 9, 5 };
		setAPDUData(userToBytes(new User(name, new byte[] { 1, 2 })));
		assertArrayEquals(name, applet.getName(apdu));
	}

	/**
	 * Test method for {@link anoncard.ANONCardApplet#getPassword(APDU)}.
	 */
	@Test
	public void testGetPassword() {
		byte[] password = new byte[] { 5, 7, 6, 9, 5 };
		setAPDUData(userToBytes(new User(new byte[] { 1, 2 }, password)));
		assertArrayEquals(password, applet.getPassword(apdu));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#getVersion(javacard.framework.APDU)}.
	 */
	@Test
	public void testGetVersion() {
		byte[] expectedResponse = new byte[] { ANONCardApplet.APPLET_VERSION_MAJOR, ANONCardApplet.APPLET_VERSION_MINOR };
		try {
			// TODO: Refactor
			applet.getVersion(apdu);
			fail("Expect response in a ResponseException");
		} catch (ResponseException actualResponse) {
			if (!Helpers.checkResponseInException(actualResponse, expectedResponse)) {
				fail(Helpers.alertResponseMismatch(actualResponse, expectedResponse));
			}
		}
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#getVersion(javacard.framework.APDU)}.
	 */
	@Test
	public void testNOP() {
		byte[] expectedResponse = new byte[] {};
		try {
			applet.NOP(apdu);
			fail("Expect response in a ResponseException");
		} catch (ResponseException actualResponse) {
			if (!Helpers.checkResponseInException(actualResponse, expectedResponse)) {
				fail(Helpers.alertResponseMismatch(actualResponse, expectedResponse));
			}
		}
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_allAdminsRight() {
		User[] admins = Arrays.copyUserArray(getConfiguration().administrators);
		byte[] adminsStream = new byte[] {};
		adminsStream = usersToBytes(admins, adminsStream);

		assertTrue("Access must be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 5;
		adminsStream = addHeadData(adminsStream, offset);
		assertTrue("Access must be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * @param adminsStream
	 */
	private byte[] addHeadData(byte[] adminsStream, int length) {
		byte[] decoy = new byte[length];
		return mixconfig.tools.dataretention.smartcard.Helpers.concatenate(decoy, adminsStream);
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_minNumberOfAdminsRight() {
		byte[] adminsStream = getMinimalNumberOfValidAdminLogins();

		assertTrue("Access must be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 3;
		adminsStream = addHeadData(adminsStream, offset);
		assertTrue("Access must be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_notEnoughAdminsRight() {
		User[] admins = new User[getConfiguration().neededNumberOfAdministrators - 1];
		for (int i = 0; i < admins.length; i++) {
			admins[i] = getConfiguration().administrators[i].getCopy();
		}
		byte[] adminsStream = new byte[] {};
		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) 0));

		adminsStream = usersToBytes(admins, adminsStream);

		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 3;
		adminsStream = addHeadData(adminsStream, offset);
		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_oneWrongName() {
		User[] admins = Arrays.copyUserArray(getConfiguration().administrators);
		admins[admins.length - 1].getUsername()[0]++;
		byte[] adminsStream = new byte[] {};
		adminsStream = usersToBytes(admins, adminsStream);

		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 5;
		adminsStream = addHeadData(adminsStream, offset);
		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_oneWrongPwd() {
		User[] admins = Arrays.copyUserArray(getConfiguration().administrators);
		admins[admins.length - 1].getPassword()[0]++;
		byte[] adminsStream = new byte[] {};
		adminsStream = usersToBytes(admins, adminsStream);

		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 5;
		adminsStream = addHeadData(adminsStream, offset);
		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckAdminPermission_prohibitAdminLoginReuse() {
		User[] admins = Arrays.copyUserArray(getConfiguration().administrators);
		for (int i = 1; i < admins.length; i++) {
			admins[i] = admins[0];
		}
		byte[] adminsStream = new byte[] {};
		adminsStream = usersToBytes(admins, adminsStream);

		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) 0, (short) adminsStream.length));

		int offset = 5;
		adminsStream = addHeadData(adminsStream, offset);
		assertFalse("Access must not be granted", applet.checkAdminPermission(adminsStream, (short) offset, (short) (adminsStream.length - offset)));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkSingleAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckSingleUserPermission_rightAdmin() {
		short index = 0;
		byte[] admin = getValidAdminLogin(index);
		assertEquals("Access should be granted", index, applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN));

		short offset = 3;
		index = 1;
		admin = getValidAdminLogin(index);
		admin = addHeadData(admin, offset);
		assertEquals("Access should be granted", index, applet.checkSingleUserPermission(getConfiguration().administrators, admin, offset, (short) (admin.length
				- ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN - offset), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN));
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkSingleAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckSingleUserPermission_rightOperator() {
		short index = 0;
		byte[] operator = getValidOperatorLogin(index);
		assertEquals("Access should be granted", index, applet.checkSingleUserPermission(getConfiguration().operators, operator, (short) 0,
				(short) (operator.length - ApduConstants.LENGTH_OF_OPERATOR_PIN), ApduConstants.LENGTH_OF_OPERATOR_PIN));

		short offset = 3;
		index = 1;
		operator = getValidOperatorLogin(index);
		operator = addHeadData(operator, offset);
		assertEquals("Access should be granted", index, applet.checkSingleUserPermission(getConfiguration().operators, operator, offset, (short) (operator.length
				- ApduConstants.LENGTH_OF_OPERATOR_PIN - offset), ApduConstants.LENGTH_OF_OPERATOR_PIN));
	}

	/**
	 * 
	 */
	private byte[] getValidAdminLogin(int index) {
		return mixconfig.tools.dataretention.smartcard.Helpers.concatenate(getConfiguration().administrators[index].getUsername(), getConfiguration().administrators[index]
				.getPassword());
	}

	/**
	 * 
	 */
	private byte[] getValidOperatorLogin(int index) {
		return mixconfig.tools.dataretention.smartcard.Helpers.concatenate(getConfiguration().operators[index].getUsername(), getConfiguration().operators[index].getPassword());
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkSingleAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckSingleUserPermission_wrongName() {
		byte[] admin = getValidAdminLogin(0);
		admin[0]++;
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);

		int offset = 3;
		admin = getValidAdminLogin(1);
		admin[0]++;
		admin = addHeadData(admin, offset);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkSingleAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckSingleUserPermission_wrongPwd() {
		byte[] admin = getValidAdminLogin(0);
		admin[admin.length - 1]++;
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);

		int offset = 4;
		admin = getValidAdminLogin(1);
		admin[0]++;
		admin = addHeadData(admin, offset);
		admin = addHeadData(admin, offset);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#checkSingleAdminPermission(byte[], short, short)}
	 * .
	 */
	@Test
	public void testCheckSingleUserPermission_wrongLength() {
		byte[] admin = getValidAdminLogin(0);
		assertEquals("Access should not be granted", -1, applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0, (short) (admin.length
				- ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN + 1), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN));
		assertEquals("Access should not be granted", -1, applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 1,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN));
		assertEquals("Access should not be granted", -1, applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), (short) (ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN + 1)));

		// Set username to "MaxMax"
		byte[] username = getConfiguration().administrators[0].getUsername();
		admin = addHeadData(admin, username.length);
		for (int i = 0; i < username.length; i++) {
			admin[i] = username[i];
		}
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) 0,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);

		int offset = 3;
		admin = addHeadData(admin, offset);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) offset,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) (offset + 1),
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) offset, (short) (admin.length
				- ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN + 1), ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN) == -1);
		assertTrue("Access should not be granted", applet.checkSingleUserPermission(getConfiguration().administrators, admin, (short) offset,
				(short) (admin.length - ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN), (short) (ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN + 1)) == -1);
	}

	/**
	 * Test method for
	 * {@link anoncard.ANONCardApplet#getNamesFromBytes(byte[], short, short, short)}
	 * .
	 */
	@Test
	public void testGetNamesFromBytes() {
		// invalid input
		assertArrayEquals(null, applet.getNamesFromBytes(new byte[] {}, (short) 0, (short) 1, (short) 2));
		assertArrayEquals(null, applet.getNamesFromBytes(new byte[] { -1 }, (short) 0, (short) 1, (short) 2));
		assertArrayEquals(null, applet.getNamesFromBytes(new byte[] { -1, -1, -1, -1, -1 }, (short) 0, (short) 1, (short) 2));

		// valid input
		int usersLength = 2;
		short pinLength = 3;
		User[] users = new User[usersLength];
		byte[] pin = new byte[pinLength];
		for (int i = 0; i < pinLength; i++) {
			pin[i] = (byte) (2 * i + 1);
		}
		byte[] expected = new byte[(usersLength - 1) * (2 + 2) + 2];
		int j = 0;
		for (int i = 0; i < users.length; i++) {
			users[i] = new User(new byte[] { (byte) (i + 1), (byte) (i + 1) }, pin);
			expected[j++] = (byte) (i + 1);
			expected[j++] = (byte) (i + 1);
			if (j < expected.length) {
				expected[j++] = 44;
				expected[j++] = 32;
			}
		}
		byte[] usersBytes = usersToBytes(users, new byte[] {});
		// System.out.println(mixconfig.tools.dataretention.smartcard.Helpers.bytesToHex(usersBytes));
		// System.out.println(mixconfig.tools.dataretention.smartcard.Helpers.bytesToHex(expected));
		// System.out.println(mixconfig.tools.dataretention.smartcard.Helpers.bytesToHex(applet.getNamesFromBytes(usersBytes,
		// (short) 0, (short) usersBytes.length, pinLength)));
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 0, (short) usersBytes.length, pinLength));
		usersBytes = usersToBytes(users, new byte[] { -1, -1 });
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 2, (short) usersBytes.length, pinLength));

		usersLength = 8;
		pinLength = 7;
		users = new User[usersLength];
		pin = new byte[pinLength];
		for (int i = 0; i < pinLength; i++) {
			pin[i] = (byte) (2 * i + 1);
		}
		expected = new byte[(usersLength - 1) * (2 + 2) + 2];
		j = 0;
		for (int i = 0; i < users.length; i++) {
			users[i] = new User(new byte[] { (byte) (i + 1), (byte) (i + 1) }, pin);
			expected[j++] = (byte) (i + 1);
			expected[j++] = (byte) (i + 1);
			if (j < expected.length) {
				expected[j++] = 44;
				expected[j++] = 32;
			}
		}
		usersBytes = usersToBytes(users, new byte[] {});
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 0, (short) usersBytes.length, pinLength));
		usersBytes = usersToBytes(users, new byte[] { -1, -1 });
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 2, (short) usersBytes.length, pinLength));

		usersLength = 1;
		pinLength = 5;
		users = new User[usersLength];
		pin = new byte[pinLength];
		for (int i = 0; i < pinLength; i++) {
			pin[i] = (byte) (2 * i + 1);
		}
		expected = new byte[(usersLength - 1) * (2 + 2) + 2];
		j = 0;
		for (int i = 0; i < users.length; i++) {
			users[i] = new User(new byte[] { (byte) (i + 1), (byte) (i + 1) }, pin);
			expected[j++] = (byte) (i + 1);
			expected[j++] = (byte) (i + 1);
			if (j < expected.length) {
				expected[j++] = 44;
				expected[j++] = 32;
			}
		}
		usersBytes = usersToBytes(users, new byte[] {});
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 0, (short) usersBytes.length, pinLength));
		usersBytes = usersToBytes(users, new byte[] { -1, -1 });
		assertArrayEquals(expected, applet.getNamesFromBytes(usersBytes, (short) 2, (short) usersBytes.length, pinLength));

	}
}