/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2020 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 A service request creating and managing a TrustCenter
 */

var ServiceRequestModel	= require('pki-as-a-service/service/ServiceRequestModel').ServiceRequestModel;
var SubjectDAO		= require('scsh/pki-db/db/SubjectDAO').SubjectDAO;
var SmartCardHSM	= require('scsh/sc-hsm/SmartCardHSM').SmartCardHSM;



/**
 * Data model for creating a trust center
 *
 * @constructor
 */
function CreateTrustCenterRequestModel(service, bo, type) {
	ServiceRequestModel.call(this, service, bo);
	this.validationResult = [];
	if (type) {
		this.subjectType = type;
	} else {
		this.subjectType = SubjectDAO.TYPE_TRUST_CENTER;
	}
}

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

exports.CreateTrustCenterRequestModel = CreateTrustCenterRequestModel;



CreateTrustCenterRequestModel.prototype.getForm = function(user) {
	if (this.form == undefined) {
		var editable = (user.id == this.getOriginatorId()) &&
			((this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_NEW) ||
			(this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_EDIT) ||
			(this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_SUBMIT));

		var lcPriv = { id: SubjectDAO.LIFECYCLE_PRIVATE, value: "Private" };
		var lcPub = { id: SubjectDAO.LIFECYCLE_PUBLIC, value: "Public" };
		var lifecycles = [lcPriv, lcPub];
		var editableLC = true
		if (this.bo.lifecycle <= ServiceRequestModel.LIFECYCLE_PRODUCE) {
				GPSystem.log(GPSystem.DEBUG, module.id, "this.model.lc="+this.model.lc);
			if (this.model.lc == SubjectDAO.LIFECYCLE_PUBLIC) {
				GPSystem.log(GPSystem.DEBUG, module.id, "public");
				lcPub.selected = true;
			} else {
				GPSystem.log(GPSystem.DEBUG, module.id, "private");
				lcPriv.selected = true;
			}
		} else {
			var ca = this.getRecipient();
			var lcSusp = { id: SubjectDAO.LIFECYCLE_SUSPENDED, value: "Suspended" };
			var lcTerm = { id: SubjectDAO.LIFECYCLE_TERMINATED, value: "Terminated" };
			var lifecycles = [lcPriv, lcPub, lcSusp, lcTerm];

			if (ca.getLifeCycle() == SubjectDAO.LIFECYCLE_PUBLIC) {
				lcPub.selected = true;
			} else if (ca.getLifeCycle() == SubjectDAO.LIFECYCLE_SUSPENDED) {
				lcSusp.selected = true;
			} else if (ca.getLifeCycle() == SubjectDAO.LIFECYCLE_TERMINATED) {
				lcTerm.selected = true;
				editableLC = false;
			} else {
				lcPriv.selected = true;
			}
		}

		this.form = [{
			id: "subjectData",
			legend: "msg.casubject.subjectData",
			fields: [
				{ id: "name", label: "msg.casubject.name", type: "text", size: 50, required: true, editable: editable, value: (this.model.name ? this.model.name : "") },
				{ id: "lc", label: "msg.casubject.lc", type: "select", editable: editableLC, value: lifecycles }
			]},
		];

		if (this.getRecipientId()) {
			var tokenDAO = this.service.daof.getTokenDAO();
			var tokenList = tokenDAO.getTokenListBySubjectId(this.getRecipientId());

			if (tokenList.length > 0) {
				var selectionList = [];
				for (var i = 0; i < tokenList.length; i++) {
					selectionList.push( { id: i, value: tokenList[i].path, selected: this.model.token == i } );
				}

				this.form[0].fields.push( { id: "token", label: "msg.casubject.token", type: "select", multiselect: true, editable: true, required: false, value: selectionList } );
			}
		}
	}

	return this.form;
}



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

	this.actionList = [];

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

		if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_EDIT) {
			this.actionList.push("action.submit");
		}
	}

	if (this.getRecipientId()) {
		var ca = this.getRecipient();

		if (ca.isCertificationOfficer(user)) {
			if ((this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_PRODUCE) ||
				(this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_INUSE)) {
				this.actionList.push("action.cas.addToken");
			}

			var tokenDAO = this.service.daof.getTokenDAO();
			var tokenList = tokenDAO.getTokenListBySubjectId(this.getRecipientId());

			if (tokenList.length > 0) {
				this.actionList.push("action.cas.removeToken");
			}

			if (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_INUSE ||
				this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_COMPLETED ||
				this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_SUSPENDED) {
				this.actionList.push("action.cas.changeMode");
			}
		}
	}

	return this.actionList;
}



CreateTrustCenterRequestModel.prototype.checkSize = function(fld, val) {
	if (val.length > fld.size) {
		fld.message = "You can use at most " + fld.size + " characters";
		return false;
	}
	return true;
}



CreateTrustCenterRequestModel.prototype.checkCAName = function(fld, val) {
	if (!this.validateFieldDefault(fld, val)) {
		return false;
	}

	var subjectDAO = this.service.daof.getSubjectDAO();
	var subject = subjectDAO.getSubjectByTypeAndName(this.subjectType, val);

	if (subject) {
		fld.message = "A TrustCenter with that name does already exist";
		return false;
	}

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



CreateTrustCenterRequestModel.prototype.validateField = function(fld, val) {
	switch(fld.id) {
		case "name":
			if (!this.checkSize(fld, val)) {
				return false;
			}
			if (!this.checkCAName(fld, val)) {
				return false;
			}
			break;
		default:
			return this.validateFieldDefault(fld, val);
	}
	return true;
}



CreateTrustCenterRequestModel.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) {
		GPSystem.log(GPSystem.DEBUG, module.id, "action list :" + actionList);
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Action " + action + " unknown");
	}

	switch(action) {
		case "action.save":
		case "action.cas.changeMode":
			this.save()
			break;
		case "action.submit":
			this.createCASubject(user);
			break;
		case "action.cas.removeToken":
			this.removeCAToken();
			break;
	}

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



CreateTrustCenterRequestModel.prototype.save = function() {
	var commitChanges = false;

	if (this.getLifeCycle() == ServiceRequestModel.LIFECYCLE_NEW) {
		this.setLifeCycle(ServiceRequestModel.LIFECYCLE_EDIT);
		this.setStatusInfo("Name entered");
		commitChanges = true;
	}

	commitChanges |= this.updateDetails(this.model.name);

	if (this.getRecipientId()) {
		var updated = this.getRecipient().setLifeCycle(this.model.lc);
		GPSystem.log(GPSystem.DEBUG, module.id,
			"updated=" + updated
			+ ", subject lifecycle=" + this.model.lc
			+ ", sr lifecycle=" + this.bo.lifecycle);

		if (this.model.lc == SubjectDAO.LIFECYCLE_TERMINATED && updated) {
			GPSystem.log(GPSystem.DEBUG, module.id,"Terminate...");
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_TERMINATED);
			this.setStatusInfo("TrustCenter terminated");
		} else if (this.model.lc == SubjectDAO.LIFECYCLE_SUSPENDED && updated) {
			GPSystem.log(GPSystem.DEBUG, module.id,"Suspend...");
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_SUSPENDED);
			this.setStatusInfo("TrustCenter suspended");
		} else if (updated && (this.bo.lifecycle == ServiceRequestModel.LIFECYCLE_SUSPENDED)) { // lifecycle public or private
			GPSystem.log(GPSystem.DEBUG, module.id,"Resume...");
			this.setLifeCycle(ServiceRequestModel.LIFECYCLE_INUSE);
			this.setStatusInfo("TrustCenter enabled");
		} else if (this.model.lc == SubjectDAO.LIFECYCLE_PRIVATE && updated) {
			this.setStatusInfo("TrustCenter set to private operation mode");
		} else if (this.model.lc == SubjectDAO.LIFECYCLE_PUBLIC && updated) {
			this.setStatusInfo("TrustCenter set to private operation mode");
		} else {
			this.setStatusInfo("Operation mode hasn't changed");
		}

		commitChanges |= updated
	}

	return commitChanges;
}



CreateTrustCenterRequestModel.prototype.createCASubject = function(user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "Create TrustCenter subject");

	if (this.getRecipientId()) {
		GPSystem.log(GPSystem.DEBUG, module.id, "Subject already created");
		return;
	}

	var roleDAO = this.service.daof.getRoleDAO();

	var roleName = this.model.name + " TrustCenter Manager";
	var managerRole = roleDAO.newRole(roleName, this.bo.assignedToRole);

	var roleName = this.model.name + " TrustCenter Operator";
	var operatorRole = roleDAO.newRole(roleName, managerRole.id);

	var roleName = this.model.name + " Registration Officer";
	var raRole = roleDAO.newRole(roleName, managerRole.id);

	var roleName = this.model.name + " Certification Officer";
	var caRole = roleDAO.newRole(roleName, managerRole.id);

	var content = {
		managerRoleId: managerRole.id,
		raRoleId: raRole.id,
		caRoleId: caRole.id,
	};

	var template = {
		name: this.model.name,
		type: this.subjectType,
		lifecycle: this.model.lc,
		managedByRoleId: operatorRole.id,
		content: JSON.stringify(content, null, '\t')
	}

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

	var srDAO = this.service.daof.getServiceRequestDAO();
	srDAO.updateRecipientId(this.bo, subject);

	roleDAO.newAssignedRole(this.getOriginatorId(), operatorRole.id, this.bo.id);
	roleDAO.newAssignedRole(this.getOriginatorId(), raRole.id, this.bo.id);
	roleDAO.newAssignedRole(this.getOriginatorId(), caRole.id, this.bo.id);
	roleDAO.newAssignedRole(this.getOriginatorId(), managerRole.id, this.bo.id);
	user.roles = roleDAO.getAssignedRoleIds(user.id);

	this.assignToRole(caRole.id);

	this.updateDetails(this.model.name);
	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_PRODUCE);
	this.setStatusInfo("TrustCenter Subject created");
}



CreateTrustCenterRequestModel.prototype.addCAToken = function(sc, user, chain) {
	GPSystem.log(GPSystem.DEBUG, module.id, "addCAToken");

	var tokenDAO = this.service.daof.getTokenDAO();
	var token = tokenDAO.getToken(chain.path);

	if (token) {
		if (token.subjectId) {
			GPSystem.log(GPSystem.DEBUG, module.id, "Token " + chain.path + " is already registered for subject " + token.subjectId);
			this.setStatusInfo("Token already assigned to another subject");
			this.commit(user.id);
			return;
		}

		tokenDAO.updateSubjectId(token, this.getRecipientId());
		tokenDAO.updateServiceRequestId(token, this.getId());
	} else {
		var template = {
			subjectId: this.getRecipientId(),
			lastSeen: new Date(),
			serviceRequestId: this.getId()
		}
		tokenDAO.newToken(chain.path, template);
	}

	GPSystem.log(GPSystem.DEBUG, module.id, "Add new token" + chain.path);
	this.assignToRole(null);
	this.setStatusInfo("Added Token");
	this.setLifeCycle(ServiceRequestModel.LIFECYCLE_INUSE);

	this.commit(user.id);
}



CreateTrustCenterRequestModel.prototype.removeCAToken = function() {
	GPSystem.log(GPSystem.DEBUG, module.id, "removeCAToken");
	var tokenDAO = this.service.daof.getTokenDAO();
	var tokenList = tokenDAO.getTokenListBySubjectId(this.getRecipientId());

	if (this.model.token.length == 0) {
		this.setStatusInfo("No removable token selected");
	}

	for (var i = 0; i < this.model.token.length; i++) {
		var idx = this.model.token[i];
		for (var j = 0; j < tokenList.length; j++) {
			if (j == idx) {
				var token = tokenList[idx];
				tokenDAO.deregisterToken(token.path);
				this.setStatusInfo("Removed Token");
				break;
			}
		}
	}
}



CreateTrustCenterRequestModel.prototype.handleSmartCardHSM = function(sc, chain, user) {
	GPSystem.log(GPSystem.DEBUG, module.id, "handleCardAction for status: " + this.bo.lifecycle);
	switch(this.bo.lifecycle) {
		case ServiceRequestModel.LIFECYCLE_PRODUCE:
		case ServiceRequestModel.LIFECYCLE_INUSE:
			this.addCAToken(sc, user, chain);
			break;
		default:
			GPSystem.log(GPSystem.ERROR, module.id, "Unexpected handleCard in state " + this.bo.lifecycle);
	}
}



CreateTrustCenterRequestModel.prototype.handleCard = function(card, session, reqinfo) {
	var user = session.user;

	var sc = new SmartCardHSM(card);
	var devAutCert = sc.readBinary(SmartCardHSM.C_DevAut);
	var crypto = new Crypto();
	var chain = SmartCardHSM.validateCertificateChain(crypto, devAutCert);
	if (chain == null) {
		this.addMessage("Could not authenticate SmartCard-HSM");
		return;
	}

	sc.openSecureChannel(crypto, chain.publicKey);

	this.handleSmartCardHSM(sc, chain, user);
}
