/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2022 CardContact Software & System Consulting
 * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
 *  ---------
 *
 *  This file is part of OpenSCDP.
 *
 *  OpenSCDP is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  OpenSCDP is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenSCDP; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @fileoverview Service Request model to import and trust certificates from
 * a PKCS12 keystore
 */



var ServiceRequestModel		= require('pki-as-a-service/service/ServiceRequestModel').ServiceRequestModel;
var PKIXCommon			= require('scsh/x509/PKIXCommon').PKIXCommon;
var X509CertificateStore	= require('scsh/x509/X509CertificateStore').X509CertificateStore;



/**
 * Data model for importing trusted certificates
 *
 *
 * @constructor
 */
function ImportTrustedCertificatesRequestModel(service, bo) {
	ServiceRequestModel.call(this, service, bo);
	this.validationResult = [];
}

ImportTrustedCertificatesRequestModel.prototype = Object.create(ServiceRequestModel.prototype);
ImportTrustedCertificatesRequestModel.constructor = ImportTrustedCertificatesRequestModel;

exports.ImportTrustedCertificatesRequestModel = ImportTrustedCertificatesRequestModel;



ImportTrustedCertificatesRequestModel.prototype.getForm = function(user) {
	if (this.form == undefined) {

		this.form = [];

		if (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_EDIT) {
			this.form.push(
				{
					id: "applicant",
					legend: "msg.itc.container",
					fields: [
						{ id: "pkcs12", label: "msg.itc.pkcs12", type: "file", required: this.model.pkcs12 ? false : true, editable: true, value: this.model.pkcs12 ? "uploaded-pkcs12" : "nothing" },
						{ id: "password", label: "msg.itc.pw", type: "text", required: false, editable: true, value: "" }
					]
				}
			);
		} else if (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_APPROVE) {

			var certlist = this.getImportList();

			this.form.push(
				{
					id: "applicant",
					legend: "msg.itc.importList",
					fields: [
						{ id: "selectedCertificates", label: "msg.itc.certificates", type: "select", multiselect: true, required: true, editable: true, value: certlist }
					]
				}
			);
		}  else { // List trusted certifciates
			this.form.push(
				{
					id: "applicant",
					legend: "msg.itc.trustedList",
					fields: []
				}
			);

			if (this.model.trustedCertificates != null) {
				this.form[0].fields.push(
					{ id: "selectedCertificates", label: "msg.itc.trustedCertificates", type: "select", multiselect: true, required: true, editable: true, value: this.model.trustedCertificates }
				);
			} else { // Terminated
				this.form[0].fields.push(
					{ id: "selectedCertificates", label: "msg.itc.trustedCertificates", type: "select", multiselect: true, required: false, editable: false, value: []},
					{ id: "status", type: "link", value: "msg.itc.status.terminated" }
				);
			}
		}
	}

	return this.form;
}



ImportTrustedCertificatesRequestModel.prototype.updateModel = function(fld, val) {
	GPSystem.log(GPSystem.DEBUG, module.id, "updateModel(" + fld.id + "," + val + ")");

	switch(fld.id) {
		case "password":
			GPSystem.log(GPSystem.DEBUG, module.id, "Received PW " + val);
			// Don't save password in model
			// It can be read via rest
			this.password = val;
			return true;
		case "selectedCertificates":
			break;
		case "pkcs12":
			break;
		default:
			return false;
	}

	this.model[fld.id] = val;
	return true;
}




ImportTrustedCertificatesRequestModel.prototype.getActionList = function(user) {
	if (this.actionList != undefined) {
		return this.actionList;
	}

	this.actionList = [];


	var trustCenter = this.getRecipient();
	var raOfficer = trustCenter.isRegistrationOfficer(user);

	if ((user.id == this.getOriginatorId()) || raOfficer) {
		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_NEW ||
			this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_EDIT) {
			this.actionList.push("action.itc.upload");
		}

		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_APPROVE) {
			this.actionList.push("action.itc.import");
			this.actionList.push("action.itc.discard");
		}

		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_INUSE) {
			this.actionList.push("action.itc.untrust");
		}
	}

	return this.actionList;
}



ImportTrustedCertificatesRequestModel.prototype.perform = function(user, action) {
	GPSystem.log(GPSystem.DEBUG, module.id, "perform(" + user.id + "," + action + ")");

	var actionList = this.getActionList(user);

	if (actionList.indexOf(action) == -1) {
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Action " + action + " unknown");
	}

	switch(action) {
		case "action.itc.upload":
			this.handleUpload();
			break;
		case "action.itc.discard":
			this.model.certificates = [];
			this.model.selectedCertificates = [];
			this.setStatusInfo("PKCS12 container discarded");
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_EDIT);
			break;
		case "action.itc.import":
			this.importCertificates();
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_INUSE);
			this.setStatusInfo("Certificate imported");
			break;
		case "action.itc.untrust":
			this.untrustCertificates();
			this.setStatusInfo("Certificate untrusted");
	}

	this.commit(user.id);
	return true;
}



ImportTrustedCertificatesRequestModel.prototype.handleUpload = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleUpload()");

	if (this.model.pkcs12 != null) {
		var p12 = new KeyStore("BC", "PKCS12", this.model.pkcs12, this.password);

		this.certlist = [];

		var aliases = p12.getAliases();
		this.model.certificates = [];
		GPSystem.log(GPSystem.DEBUG, module.id, "aliases " + aliases);
		for (var i = 0; i < aliases.length; i++) {
			var alias = aliases[i];
			var cert = p12.getCertificate(alias);
			GPSystem.log(GPSystem.DEBUG, module.id, alias + " cert: " + cert);
			var dn = cert.getSubjectDNString();

			this.model.certificates.push({
				id: i,
				alias: alias,
				dn: dn,
				cert: cert.getBytes()
			});
		}

		delete this.model.pkcs12;
	} else {
		GPSystem.log(GPSystem.DEBUG, module.id, "Missing pkcs12 file");
	}

	this.setStatusInfo("PKCS12 container uploaded");
	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_APPROVE);
}



ImportTrustedCertificatesRequestModel.prototype.getImportList = function() {
	var certlist = [];
	for (var i = 0; i < this.model.certificates.length; i++) {
		var cert = this.model.certificates[i];
		var e = { id: cert.id, value: cert.dn };
		if (this.model.selectedCertificates != null && this.model.selectedCertificates.indexOf(cert.id) >= 0) {
			e.selected = true;
		}
		certlist.push(e);
	}
	return certlist;
}



ImportTrustedCertificatesRequestModel.prototype.getCertificate = function(id) {
	for (var i = 0; i < this.model.certificates.length; i++) {
		var cert = this.model.certificates[i];
		if (cert.id == id) {
			return cert;
		}
	}
}



/*
 * - Only import if there is a full cert chain.
 * - Make current only if there is no other certificate
 * - Start with root cert and build up holder path
 * - Show certificates by subject dn
 */
ImportTrustedCertificatesRequestModel.prototype.importCertificates = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "importCertificates()");
	var holderDAO = this.service.daof.getHolderDAO();
	var certDAO = this.service.daof.getCertificateDAO();

	var store = new X509CertificateStore(this.service.daof);

	var toBeProcessed = [];

	var trustCenter = this.getRecipient();
	var content = trustCenter.bo.getContent();
	if (!content.trustedCA) {
		content.trustedCA = [];
	}
	var trustedCA = content.trustedCA;

	this.model.trustedCertificates = [];

	// First process all root certificates

	for (var i = 0; i < this.model.selectedCertificates.length; i++) {
		var selectedId = this.model.selectedCertificates[i];
		var o = this.getCertificate(selectedId);
		var x509 = new X509(o.cert);

		if (x509.getIssuerDNString().equals(x509.getSubjectDNString())) {
			// Process root certifcate
			var subject = x509.getSubjectDNString();
			// holder name and parent must be unique
			// therefore use the subject dn as holder name
			// for root certificates
			var path = "/" + subject;
			var certDTO = store.storeCertificate(path, x509, false, this.getId());

			this.model.trustedCertificates.push({ id: certDTO.id, value: subject });
			if (trustedCA.indexOf(certDTO.id) == -1) {
				GPSystem.log(GPSystem.DEBUG, module.id, "Add cert.id " + certDTO.id + " to TrustCenter.trustedCA");
				trustedCA.push(certDTO.id);
			}
		} else {
			toBeProcessed.push(x509);
		}
	}

	// Import certificates that can be validated by one of certificates in the database

	GPSystem.log(GPSystem.DEBUG, module.id, "Import subordinate certificates");
	do {
		var count = toBeProcessed.length;
		GPSystem.log(GPSystem.DEBUG, module.id, "#toBeProcessed " + count);
		for (var i = 0; i < toBeProcessed.length; i++) {
			var cert = toBeProcessed[i];
			var certDTO = store.importCertificate(cert, false, this.getId());
			if (certDTO) {
				this.model.trustedCertificates.push(
					{
						id: certDTO.id,
						value: cert.getSubjectDNString()
					}
				);

				if (trustedCA.indexOf(certDTO.id) == -1) {
					GPSystem.log(GPSystem.DEBUG, module.id, "Add cert.id " + certDTO.id + " to TrustCenter.trustedCA");
					trustedCA.push(certDTO.id);
				}
				toBeProcessed.splice(i, 1);
				break;
			}
		}
	} while (toBeProcessed.length > 0 &&
		toBeProcessed.length != count);

	// These certificates could not be validated
	for (var i = 0; i < toBeProcessed.length; i++) {
		var cert = toBeProcessed[i];
		GPSystem.log(GPSystem.DEBUG, module.id, "Could not build certificate chain for certificate " + cert);
	}

	// The import list won't be used any more
	delete this.model.certificates;

	// Persist trustedCA
	var subjectDAO = this.service.daof.getSubjectDAO();
	subjectDAO.updateContent(trustCenter.bo);
}



ImportTrustedCertificatesRequestModel.prototype.untrustCertificates = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "untrustCertificates()");

	var trustCenter = this.getRecipient();
	var content = trustCenter.bo.getContent();
	if (!content.trustedCA) {
		content.trustedCA = [];
	}
	var trustedCA = content.trustedCA;

	var updateNeeded = false;
	for (var i = 0; i < this.model.selectedCertificates.length; i++) {
		var certId = this.model.selectedCertificates[i];

		for (var j = 0; j < this.model.trustedCertificates.length; j++) {
			if (this.model.trustedCertificates[j].id == certId) {
				this.model.trustedCertificates.splice(j, 1);
			}
		}

		var j = trustedCA.indexOf(certId);
		if (j >= 0) {
			GPSystem.log(GPSystem.DEBUG, module.id, "Remove cert.id " + certId + " from TrustCenter.trustedCA");
			trustedCA.splice(j, 1);
			updateNeeded = true;
		}
	}

	if (updateNeeded) {
		// Persist trustedCA
		var subjectDAO = this.service.daof.getSubjectDAO();
		subjectDAO.updateContent(trustCenter.bo);
	}

	if (this.model.trustedCertificates.length == 0) {
		delete this.model.trustedCertificates;
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_TERMINATED);
	}
}
