/*
 * 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.nio.file.*;
import java.security.*;
import java.security.cert.*;
import java.security.interfaces.ECKey;
import java.util.*;

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

/*
 * EDOC 2.0 sample application.
 * This example demonstrates how to create a signed EDOC 2.0 format document.
 *
 * Example creates EDOC 2.0 object, adds data file 'args[0]',
 * signs EDOC document if password is provided as a parameter 'args[2]'
 * and finally writes EDOC document to file 'args[1]'.
 */
public class EDoc2Sample 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.EDoc2Sample fileToAdd edocToWrite [password]");
	}

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

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

		//
		EDoc2 edoc = null;
		FileInputStream fileInputStream = null;
		FileOutputStream edocOutputStream = null;

		try {
			//create edoc
			System.out.println("Begin...");
			edoc = EDoc2Builder.newEDoc();
			System.out.println("1 - EDOC object created");

			//add file
			File file = new File(fileToAdd);
			fileInputStream = new FileInputStream(file);
			String mimeType = Files.probeContentType(file.toPath());
			if (mimeType == null) mimeType = "application/octet-stream";
			edoc.addDataObject(fileInputStream, file.getName(), mimeType);
			System.out.println("2 - File " + fileToAdd + " added");
			
			//check password
			if (password != null) {
				//prepare KeyAccessor for signature
				EDoc2Sample cardAccessorCallback = new EDoc2Sample();
				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
				Map<String, String> signatureProperties = new HashMap<>();
				boolean ecKey = (certificate.getPublicKey() instanceof ECKey);
				signatureProperties.put(EDoc2Signature.KEY_SIGNATURE_METHOD_URI,
					ecKey ? EDoc2Signature.SIGNATURE_METHOD_ECDSA_SHA256 : EDoc2Signature.SIGNATURE_METHOD_RSA_SHA256);
				EDoc2BasicSignature basicSignature = edoc.addSignature(signatureProperties);
				basicSignature.setSignatureProductionPlace(new SignatureProductionPlace("Riga"));
				basicSignature.setSignerClaimedRoles(Collections.singletonList("Manager"));
				basicSignature.setSigningCertificate(certificate);
				System.out.println("3.1 - Signature prepared");

				//get signable data
				byte[] signable = basicSignature.getSignableBytes();
				String signatureMethodAlgorithm = basicSignature.getSignatureMethodAlgorithm();

				/*
				 * NOTE: At this moment edoc optionally can be stored to file
				 * and opened later to support server side load balance scenario.
				 */

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

				//sign data, optionally use an external process
				Signature signature = Signature.getInstance(signatureMethodAlgorithm);
				signature.initSign(keyAccessor.getPrivateKey(signingAlias));
				signature.update(signable);
				byte[] signatureValue = signature.sign();

				//set signature value
				basicSignature.setSignatureValue(signatureValue);
				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) {
					EDoc2QualifiedSignature qualifiedSignature = basicSignature.qualifiedSignature();
					byte[] signatureValueDigest = qualifiedSignature.getSignatureValueDigest("SHA-256");
					TimeStamp timeStamp = TimeStampGenerator.requestTimeStamp(TimeStampGenerator.DIGEST_ALGORITHM_SHA256, signatureValueDigest, keyAccessor);
					qualifiedSignature.setSignatureTimestamps(Collections.singletonList(timeStamp.getEncoded()));
					boolean useTrustedList = true; //whether to use EU Trusted Status List data
					QualifiedSignatureHelper helper = new QualifiedSignatureHelper(certificate, timeStamp, useTrustedList);
					qualifiedSignature.setCertificateValues(helper.getCertificates());
					qualifiedSignature.setRevocationValues(helper.getOCSPResponses(), helper.getCRLResponses());
				}

				//complete signature
				basicSignature.complete();
				System.out.println("3.3 - Signature completed");

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

			//save edoc
			edocOutputStream = new FileOutputStream(edocToWrite);
			edoc.writeTo(edocOutputStream);
			System.out.println("4 - EDOC file written to " + edocToWrite);

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

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

			//close edocOutputStream
			if (edocOutputStream != null) edocOutputStream.close();

			//close edoc
			if (edoc != null) edoc.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;
	}

}
