/*
 * Copyright (c) VAS "Latvijas Valsts radio un televīzijas centrs" (http://www.eparaksts.lv)
 * Developed by SIA "EUSO" (http://www.euso.lv)
 */
package lv.eparaksts.sample;

import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;

import lv.eparaksts.edoc.*;
import lv.eparaksts.helpers.*;
import lv.pasts.eme.security.*;
import lv.pasts.eme.smartcard.*;

/*
 * EDOC 2.0 sample application.
 * This example demonstrates how to create multiple signed EDOC 2.0 format documents in a batch job.
 *
 * Example creates EDOC 2.0 objects, adds 3 data files from directory 'args[0]',
 * signs EDOC documents if password is provided as a parameter 'args[2]'
 * and finally writes EDOC documents to directory 'args[1]'.
 */
public class EDoc2BatchSample implements CardAccessorCallback {

	private char[] password;

	static void printUsage(String[] args) {
		System.out.println("");
		for (int i = 0; i < args.length; i++) {
			System.out.println(args[i]);
		}

		System.out.println("");
		System.out.println("usage: java lv.eparaksts.sample.EDoc2BatchSample sourceDir targetDir [password]");
	}

	public static void main(String[] args) throws Exception {
		if (args.length != 3) {
			printUsage(args);
			//return;
		}

		//collect parameters
		String sourceDir = args[0];
		String targetDir = args[1];
		char[] password = (args.length > 2) ? args[2].toCharArray() : null;

		//
		EDoc2Batch batch = null;

		try {
			//collect source files
			System.out.println("Begin...");
			List<File> sourceFiles = new ArrayList<>();
			File sourceDirectory = new File(sourceDir);
			for (File sourceFile : sourceDirectory.listFiles()) {
				if (sourceFile.isFile()) {
					sourceFiles.add(sourceFile);
				}

				//ensure max 3 files
				if (sourceFiles.size() >= 3) {
					break;
				}
			}

			//create batch
			System.out.println("1 - EDOC batch source prepared: " + sourceFiles.size());
			batch = EDoc2Batch.fromFiles(sourceFiles.toArray(new File[0]));
			System.out.println("2 - EDOC batch job created");

			//check password
			if (password != null) {
				//prepare KeyAccessor for signature
				EDoc2BatchSample cardAccessorCallback = new EDoc2BatchSample();
				cardAccessorCallback.password = password;
				KeyAccessor keyAccessor = CardAccessor.getInstance(cardAccessorCallback);

				//prepare certificate
				String signingAlias = keyAccessor.selectDocumentSigningCertificate();
				X509Certificate certificate = keyAccessor.getCertificate(signingAlias);

				/*
				 * Signature PHASE 1 - prepare signature [PrivateKey IS NOT required]
				 */

				//prepare signature
				batch.setSignatureProductionPlace(new SignatureProductionPlace("Riga"));
				batch.setSignerClaimedRoles(Collections.singletonList("Manager"));
				batch.setSigningCertificate(certificate);
				System.out.println("3.1 - Signature prepared");

				//get signable data
				Map<String, String> signatureProperties = new HashMap<>();
				signatureProperties.put(EDoc2Signature.KEY_SIGNATURE_METHOD_URI, EDoc2Signature.SIGNATURE_METHOD_RSA_SHA256);
				Map<String, byte[]> signableBytesMap = batch.getSignableBytes(signatureProperties);
				Map<String, String> signatureMethodAlgorithmMap = batch.getSignatureMethodAlgorithm();

				/*
				 * Signature PHASE 2 - calculate signature [PrivateKey IS required]
				 */

				//sign data
				Map<String, byte[]> signatureValueMap = new HashMap<>();
				for (String key : signableBytesMap.keySet()) {
					byte[] signableBytes = signableBytesMap.get(key);
					String signatureMethodAlgorithm = signatureMethodAlgorithmMap.get(key);
					Signature signature = Signature.getInstance(signatureMethodAlgorithm);
					signature.initSign(keyAccessor.getPrivateKey(signingAlias));
					signature.update(signableBytes);
					byte[] signatureValue = signature.sign();
					signatureValueMap.put(key, signatureValue);
				}

				//setSignatureValue
				batch.setSignatureValue(signatureValueMap);
				System.out.println("3.2 - Signature added");

				/*
				 * NOTE: At this moment signature has to be extended with
				 * timestamp and revocation data to conform QUALIFIED profile.
				 */
				boolean requestTimeStamp = false; //false to avoid billing
				if (requestTimeStamp) {
					batch.setSignatureTimestamps("SHA-256", keyAccessor);
				}

			} else {
				System.out.println("3 - Signature not added, smartcard access password not provided");
			}

			//complete
			boolean useTrustedList = true; //whether to use EU Trusted Status List data
			List<EDoc2BatchResult> resultList = batch.complete(useTrustedList);
			System.out.println("4 - EDOC batch job completed");

			//prepare targetDir
			File targetDirectory = new File(targetDir);
			if (!targetDirectory.exists()) targetDirectory.mkdirs();

			//process result
			for (EDoc2BatchResult result : resultList) {
				if (result.hasException()) {
					System.out.println("hasException:");
					result.getException().printStackTrace();
				}

				//store edoc
				EDoc2 edoc = result.getEDoc();
				FileOutputStream fileOutputStream = null;
				try {
					File tempFile = new File(targetDirectory, result.getFileName() + ".edoc");
					fileOutputStream = new FileOutputStream(tempFile);
					edoc.writeTo(fileOutputStream);

				} finally {
					if (fileOutputStream != null) fileOutputStream.close();
				}
			}

			//done
			System.out.println("5 - EDOC files written to " + targetDir);

		} catch (Exception exc) {
			exc.printStackTrace();

		} finally {
			//close batch
			if (batch != null) {
				batch.close();
			}
		}

		//done
		System.out.println("Finished");
		System.exit(0);
	}

	public char[] getCardPIN(String tokenSerial, String tokenLabel, long sessionId, X509Certificate cert, boolean isRetry) {
		System.out.println("getCardPIN: " + tokenLabel + ", cert: " + cert.getSubjectX500Principal().toString());
		if (isRetry) {
			//PIN was incorrect, cancel
			System.err.println("getCardPIN: PIN incorrect, exit!");
			return null;
		}

		System.out.println("using PIN: " + new String(password));
		return password;
	}

	public CertificateEntry selectDocumentSigningCertificate(CertificateEntry[] certList) {
		if (certList.length > 0) {
			return certList[0];
		}

		return null;
	}

	public CertificateEntry selectClientAuthenticationCertificate(CertificateEntry[] certList) {
		if (certList.length > 0) {
			return certList[0];
		}

		return null;
	}

}
