package anoncard;

import static org.junit.Assert.fail;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import javacard.framework.APDU;
import javacard.framework.ISO7816;
import mixconfig.tools.dataretention.smartcard.ApduConstants;

import org.junit.After;
import org.junit.Before;

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

public class AbtractANONCardAppletSubTest {

	protected ANONCardApplet applet;
	protected byte[] apduBuffer;
	protected APDU apdu;

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		applet = new ANONCardApplet(null, (byte) 0, (byte) 0);

		apduBuffer = new byte[java.lang.Short.MAX_VALUE];
		apduBuffer[ISO7816.OFFSET_CLA] = ApduConstants.CLA_ANON;

		apdu = instanciateApdu();
		apdu.setBuffer(apduBuffer); // this function exists in an aspect

		byte[] max = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("Max");
		byte[] chris = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("Chris");
		byte[] basti = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("Basti");
		byte[] max_adminpwd = new byte[ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN];
		byte[] chris_adminpwd = new byte[ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN];
		byte[] basti_adminpwd = new byte[ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN];
		byte[] max_operatorpwd = new byte[ApduConstants.LENGTH_OF_OPERATOR_PIN];
		byte[] chris_operatorpwd = new byte[ApduConstants.LENGTH_OF_OPERATOR_PIN];
		byte[] basti_operatorpwd = new byte[ApduConstants.LENGTH_OF_OPERATOR_PIN];

		for (int i = 0; i < ApduConstants.LENGTH_OF_ADMINISTRATOR_PIN; i++) {
			max_adminpwd[i] = (byte) i;
			chris_adminpwd[i] = (byte) (i + 1);
			basti_adminpwd[i] = (byte) (i + 2);
		}
		for (int i = 0; i < ApduConstants.LENGTH_OF_OPERATOR_PIN; i++) {
			max_operatorpwd[i] = (byte) (2 * i);
			chris_operatorpwd[i] = (byte) (2 * (i + 1));
			basti_operatorpwd[i] = (byte) (2 * (i + 2));
		}

		ANONCardAppletConfiguration configuration = getConfiguration();
		configuration.administrators = new User[] { new User(max, max_adminpwd), new User(chris, chris_adminpwd), new User(basti, basti_adminpwd) };
		configuration.operators = new User[] { new User(max, max_operatorpwd), new User(chris, chris_operatorpwd), new User(basti, basti_operatorpwd) };
		configuration.neededNumberOfAdministrators = 2;
		configuration.neededNumberOfOperators = 1;

		byte[] domain1 = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("http://www.timeserver.com/");
		byte[] public_key1 = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("123");
		byte[] domain2 = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("http://time.server.de/");
		byte[] public_key2 = mixconfig.tools.dataretention.smartcard.Helpers.stringToUtf8Bytes("456");
		configuration.timeservers = new Timeserver[] { new Timeserver(domain1, public_key1), new Timeserver(domain2, public_key2) };

	}

	private APDU instanciateApdu() {
		Class<APDU> clazz = APDU.class;
		Constructor<APDU> c;
		try {
			c = clazz.getDeclaredConstructor();
			c.setAccessible(true); // hack
			apdu = c.newInstance((Object[]) null);
			return apdu;
		} catch (SecurityException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		} catch (InstantiationException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			fail("Exception on initialization");
		}
		return null;
	}

	/**
	 * @throws java.lang.Exception
	 */
	@After
	public void tearDown() throws Exception {

	}

	/**
	 * @param data
	 */
	protected void setAPDUData(byte[] data) {
		for (int i = 0; i < data.length; i++) {
			apduBuffer[ISO7816.OFFSET_CDATA + i] = data[i];
		}
		apduBuffer[ISO7816.OFFSET_LC] = (byte) data.length;
	}

	protected void assertExceptionResponse(short expectedErrorCode) {
		try {
			applet.process(apdu);
			fail("Expect response in a ISOExceptionException");
		} catch (ISOExceptionException actualResponse) {
			if (!Helpers.checkErrorCodeInException(actualResponse, expectedErrorCode)) {
				fail(Helpers.alertResponseMismatch(actualResponse, expectedErrorCode));
			}
		} catch (ResponseException e) {
			fail("Got normal response: " + e.getResponse());
		}
	}

	protected void assertProcessResponse(byte[] expectedResponse) {
		try {
			applet.process(apdu);
			fail("Expect response in a ResponseException");
		} catch (ResponseException actualResponse) {
			if (!Helpers.checkResponseInException(actualResponse, expectedResponse)) {
				fail(Helpers.alertResponseMismatch(actualResponse, expectedResponse));
			}
		} catch (ISOExceptionException e) {
			fail("Got Exception: " + e.getErrorCode());
		}
	}

	protected boolean getIsTansactionOpen() {
		return applet.getIsTransactionOpen(); // function exists in aspect
	}

	protected ANONCardAppletConfiguration getConfiguration() {
		return applet.getConfiguration(); // function exists in aspect
	}

	protected DataReceiveChannel getDataReceiveChannel() {
		return applet.getDataReceiveChannel();
		// this method exists in an aspect
	}

	protected DataSendChannel getDataSendChannel() {
		return applet.getDataSendChannel();
		// this method exists in an aspect
	}

	protected Log getLog() {
		return applet.getLog();
		// this method exists in an aspect
	}

	protected Date getDate() {
		return applet.getDate();
		// this method exists in an aspect
	}

	/**
	 * @return
	 */
	protected byte[] getMinimalNumberOfValidAdminLogins() {
		User[] admins = new User[getConfiguration().neededNumberOfAdministrators];
		for (int i = 0; i < admins.length; i++) {
			admins[i] = getConfiguration().administrators[i].getCopy();
		}
		byte[] adminsStream = new byte[] {};
		adminsStream = usersToBytes(admins, adminsStream);
		return adminsStream;
	}

	/**
	 * @param users
	 * @param data
	 * @return data.append{LN N1 N2 .. P1 P2 ..}.append{Ln Na Nb .. Pa Pb ..}...
	 */
	protected byte[] usersToBytes(User[] users, byte[] data) {
		for (int i = 0; i < users.length; i++) {
			byte[] nextUser = mixconfig.tools.dataretention.smartcard.Helpers.concatenate(new byte[] { (byte) users[i].getUsername().length }, users[i].getUsername());
			nextUser = mixconfig.tools.dataretention.smartcard.Helpers.concatenate(nextUser, users[i].getPassword());
			data = mixconfig.tools.dataretention.smartcard.Helpers.concatenate(data, nextUser);
		}
		return data;
	}

	protected byte[] userToBytes(User user) {
		return usersToBytes(new User[] { user }, new byte[] {});
	}

	protected boolean exists(User needle, User[] haystack) {
		for (short i = 0; i < haystack.length; i++) {
			if (Arrays.arrayEqual(needle.getUsername(), haystack[i].getUsername()) && Arrays.arrayEqual(needle.getPassword(), haystack[i].getPassword())) {
				return true;
			}
		}
		return false;
	}

}