/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2018 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 for PKCS#10 certificate requests
 */



var ServiceRequestModel		= require('pki-as-a-service/service/ServiceRequestModel').ServiceRequestModel;
var CertificateRequestModel	= require('x509ca/processes/CertificateRequestModel').CertificateRequestModel;
var PKCS10			= require('scsh/pkcs/PKCS10').PKCS10;
var X509Name			= require('scsh/x509/X509Name').X509Name;
var PKIXCommon			= require('scsh/x509/PKIXCommon').PKIXCommon;



/**
 * Data model for requesting a certificate
 *
 *
 * @constructor
 */
function ESTRequestModel(service, bo) {
	CertificateRequestModel.call(this, service, bo);
	this.validationResult = [];
}

ESTRequestModel.prototype = Object.create(CertificateRequestModel.prototype);
ESTRequestModel.constructor = ESTRequestModel;

exports.ESTRequestModel = ESTRequestModel;



ESTRequestModel.prototype.getSubjectList = function(cn) {
	var trustCenter = this.getRecipient();
	var role = trustCenter.getRARoleId();
	var subjectDAO = this.service.daof.getSubjectDAO();
	var sublist = subjectDAO.getManagedSubjects(role);

	var sellist = [];

	for (var i = 0; i < sublist.length; i++) {
		var subject = sublist[i];
		var e = { id: subject.id, value: subject.name + " (" + subject.type + ")" };
		if (this.model.subjectId) {
			e.selected = this.model.subjectId == subject.id;
		} else {
			e.selected = cn == subject.name;
		}
		sellist.push(e);
	}

	return sellist;
}



ESTRequestModel.prototype.getForm = function(user) {
	if (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_APPROVE) {
		if (this.form == undefined) {
			var editable = (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_APPROVE) && this.isRegistrationOfficer(user);

			var req = this.getRequest();

			this.form = [{
				id: "applicant",
				legend: "msg.cr.applicant",
				fields: [
					{ id: "csr", label: "msg.cr.csr", type: "text", size: 100, required: false, editable: false, value: req.toString() }
				]
				}
			];

			var subject = req.getSubject();
			var cn = "";
			if (subject.elements > 0) {
				var xName = new X509Name(subject)
				var reqDN = xName.toString();

				try {
					cn = xName.getRDNAsString(X509Name.commonName);
				} catch(e) {
					GPSystem.log(GPSystem.DEBUG, module.id, e);
				}
			}

			var subjectList = this.getSubjectList(cn);
			this.form[0].fields.push({ id: "subjectId", label: "msg.subject", type: "select", required: true, editable: editable, value: subjectList });

			// Issuer must be set before transforming the subject dn
			if (this.model.issuer) {
				if (this.model.subjectDN) {
					var dnval = PKIXCommon.dnToString(this.model.subjectDN);
				} else { // issuer required
					var dnval = PKIXCommon.dnToString(this.transformSubjectDN());
				}

				this.form[0].fields.push(
					{ id: "dn", label: "msg.cr.dn", type: "text", size: 100, required: false, editable: editable, value: dnval }
				);

			}

			var caList = this.getRecipient().getCAList();
			var issuerList = [];
			for (var i = 0; i < caList.length; i++) {
				var issuer = caList[i];
				var e = { id: issuer.id, value: issuer.name };
				if (issuer.id == this.model.issuer) {
					e.selected = true;
				}
				issuerList.push(e);
			}

			this.form.push(
			{
				id: "approval",
				legend: "msg.cr.approval",
				fields: [
					{ id: "issuer", label: "msg.cr.issuer", type: "select", editable: editable, value: issuerList },
					{ id: "reviewRemark", label: "msg.cr.reviewRemark", type: "textarea", size: 4096, rows:10, cols:80, editable: editable, value: (this.model.reviewRemark ? this.model.reviewRemark : "") }
				]
			});

		}
	} else {
		this.form = CertificateRequestModel.prototype.getForm.call(this, user)
	}
	return this.form;
}



ESTRequestModel.prototype.updateOriginator = function() {
	var subject = this.service.getSubject(this.model.subjectId);
	var srDAO = this.service.daof.getServiceRequestDAO();
	srDAO.updateOriginatorId(this.bo, subject.bo);
	srDAO.updateDetails(this.bo, subject.bo.name);
	delete this.originator;
}



ESTRequestModel.prototype.updateCommonName = function(id) {
	var subject = this.service.getSubject(id);
	this.model.commonName = subject.getName();
	this.model.subjectDN = this.transformSubjectDN();
	this.vetoDN = true;
}



ESTRequestModel.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.approve":
			this.updateOriginator();
			this.setStatusInfo("Assigned to subject");
			break;
		case "action.save":
			this.setStatusInfo("Data saved");
			break;
	}

	this.handleAction(user, action);

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



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

	switch(fld.id) {
		case "subjectId":
			// subject dn is dependent on the issuer
			if (this.model.issuer && this.model.subjectId != val) {
				this.updateCommonName(val);
			}
			break;
		case "issuer":
			// changing the issuer will affect the subject dn
			if (this.model.issuer != val) {
				this.model.issuer = val;
				this.updateCommonName(this.model.subjectId);
				return true;
			}
			break;
		case "dn":
			if (this.vetoDN) {	// Skip updating the DN, if the subject was changed and the DN fresly transformed.
				return true;
			}
	}
	return CertificateRequestModel.prototype.updateModel.call(this, fld, val);
}



ESTRequestModel.prototype.createSystemSubject = function() {
	var template = {
		type: "System",
		name: this.model.commonName,
		managedByRoleId: this.getRecipient().getRARoleId(),
		serviceRequestId: this.getId()
	};

	var subjectDAO = this.service.daof.getSubjectDAO();
	var subject = subjectDAO.newSubject(template);

	this.addMessage("Create new subject " + subject);

	return subject;
}



ESTRequestModel.prototype.autoEnroll = function(req, res) {
	GPSystem.log(GPSystem.DEBUG, module.id, "autoenroll()");

	var subjectDAO = this.service.daof.getSubjectDAO();
	var subject = subjectDAO.getSubjectByName(this.model.commonName);
	if (subject == null) {
		var template = {
			type: "System",
			name: this.model.commonName,
			managedByRoleId: this.getRecipient().getRARoleId(),
			serviceRequestId: this.getId()
		};

		subject = subjectDAO.newSubject(template);

		this.addMessage("Created new subject " + subject);
	} else {
		if (subject.managedByRoleId != this.getRecipient().getRARoleId()) {
			GPSystem.log(GPSystem.ERROR, module.id, "Subject already exists, but is managed by a different trustcenter");
			res.setStatus(HttpResponse.SC_BAD_REQUEST);
			this.addMessage("Subject already exists, but is managed by a different trustcenter");
			return;
		}
	}

	this.model.subjectId = subject.id;
	this.updateOriginator();

	this.model.subjectDN = this.transformSubjectDN();
	this.model.reviewRemark = "Automatically approved";
	this.issueCertificate();
}



/**
 * Perform periodic or ad-hoc activities
 */
ESTRequestModel.prototype.ping = function(req, res) {
	GPSystem.log(GPSystem.DEBUG, module.id, "ping()");

	if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_SUBMIT) {
		if (this.model.autoEnroll && this.isCATokenInHSMService()) {
			this.autoEnroll(req, res);
		} else {
			this.toApproval();
		}
		this.commit(0);
	} else if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_PRODUCE) {
		if (this.isCATokenInHSMService()) {

			if (this.model.subjectDN == undefined) {
				this.model.subjectDN = this.transformSubjectDN();
			}

			this.model.reviewRemark = "Automatically approved";
			this.issueCertificate();
			this.commit(0);
		} else {
			GPSystem.log(GPSystem.INFO, module.id, "CA token not online or available");
			res.setStatus(HttpResponse.SC_ACCEPTED);
		}
	} else if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_DELIVER) {
		this.activateCertificate();
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_INUSE);
		this.setStatusInfo("Certificate published");
		this.commit(0);
	}
}



ESTRequestModel.prototype.getRequest = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "getRequest()");

	if (this.model.creq.byteAt(0) != 0x30) {
		var pem = this.model.creq.toString(ASCII);
		GPSystem.log(GPSystem.DEBUG, module.id, "Converting PEM " + pem);
		var req = PKIXCommon.parsePEM("CERTIFICATE REQUEST", pem);
		return new PKCS10(req);
	}

	GPSystem.log(GPSystem.DEBUG, module.id, this.model.creq);
	return new PKCS10(this.model.creq);
}
