/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2016 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 Base class for all service request models
 */

var RoleDAO = require('scsh/pki-db/db/RoleDAO').RoleDAO;
var SubjectDAO = require('scsh/pki-db/db/SubjectDAO').SubjectDAO;
var Form = require('scsh/srv-cc1/Form').Form;
var Person = require('pki-as-a-service/subjects/Person').Person;



/**
 * Create a new service request
 *
 * @class Class This is the base class for all service requests models.
 * @constructor
 *
 * @param {Service} service the service object in which context the service request lives
 * @param {ServiceRequest} bo the business object in the persistance layer
 */
function ServiceRequestModel(service, bo) {
	this.service = service;
	this.bo = bo;

	// this.model is the deserialized JSON structure in which requests specific data is stored
	this.model = this.bo.getContent();
}

exports.ServiceRequestModel = ServiceRequestModel;


// The generic life-cycle model. Derived classes can define additional state, but are encouraged to reuse the
// existing ones.
ServiceRequestModel.LIFECYCLE_CLOSED = 0;			// Service request is no longer active
ServiceRequestModel.LIFECYCLE_NEW = 1;				// Service request is newly created
ServiceRequestModel.LIFECYCLE_EDIT = 2;				// Service request can be edited
ServiceRequestModel.LIFECYCLE_SUBMIT = 3;			// Service request in preparation for submission
ServiceRequestModel.LIFECYCLE_VALIDATE = 4;			// Service request is in automatic validation
ServiceRequestModel.LIFECYCLE_APPROVE = 5;			// Service request requires approval
ServiceRequestModel.LIFECYCLE_PRODUCE = 6;			// Service request object is in production
ServiceRequestModel.LIFECYCLE_DELIVER = 7;			// Service request object is in delivery
ServiceRequestModel.LIFECYCLE_ACCEPT = 8;			// Service request object pending acceptance
ServiceRequestModel.LIFECYCLE_RETURNED = 9;			// Service request object not accepted
ServiceRequestModel.LIFECYCLE_ACTIVATE = 10;			// Service request object pending activation
ServiceRequestModel.LIFECYCLE_INUSE = 11;			// Service request object in use
ServiceRequestModel.LIFECYCLE_SUSPENDED = 12;			// Service request object suspended
ServiceRequestModel.LIFECYCLE_TERMINATED = 13;			// Service request object terminated

ServiceRequestModel.LIFECYCLE_REJECTED = 17;			// Service request rejected
ServiceRequestModel.LIFECYCLE_ERROR = 18;			// Service request in error state
ServiceRequestModel.LIFECYCLE_COMPLETE = 19;			// Service request completed

ServiceRequestModel.LIFECYCLE_STATES = [
	"Closed",
	"New",
	"Edit",
	"Submit",
	"Validate",
	"Approve",
	"Produce",
	"Deliver",
	"Accept",
	"Returned",
	"Activate",
	"In Use",
	"Suspended",
	"Terminated",
	"RFU14",
	"RFU15",
	"RFU16",
	"Rejected",
	"Error",
	"Completed"
];



/**
 * Return a human readable string for a given life-cycle id
 *
 * @param {Number} id
 * @type String
 * @return the readable string
 */
ServiceRequestModel.getLifeCycleStringForId = function(id) {
	if (id >= ServiceRequestModel.LIFECYCLE_STATES.length) {
		return "Application lifecycle";
	}
	return ServiceRequestModel.LIFECYCLE_STATES[id];
}



/**
 * Gets the unique id of the service request as defined by the database
 * The id is only available after the service request has been added to the
 * inbound / outbound queue.
 *
 * @returns the service request id as assigned by the database
 * @type String
 */
ServiceRequestModel.prototype.getId = function() {
	return this.bo.id;
}



/**
 * Gets creation time
 *
 * @returns the timestamp at which this service request was created or null
 * @type Date
 */
ServiceRequestModel.prototype.getCreationTime = function() {
	return this.bo.created;
}



/**
 * Gets the service request type
 *
 * @returns the service request type
 * @type String
 */
ServiceRequestModel.prototype.getProcess = function() {
	return this.bo.process;
}



/**
 * Gets the status information for the last processing
 *
 * @returns the last status information which may be undefined
 * @type String
 */
ServiceRequestModel.prototype.getStatusInfo = function() {
	return this.bo.state;
}



/**
 * Return a description related to actions available for this service request.
 *
 * @returns the last action information which may be undefined
 * @type String
 */
ServiceRequestModel.prototype.getActionInfo = function() {
	return;
}



/**
 * Gets the service request life-cycle
 *
 * @type Number
 */
ServiceRequestModel.prototype.getLifeCycle = function() {
	return this.bo.lifecycle;
}



/**
 * Gets the service request life-cycle as text
 *
 * @type String
 */
ServiceRequestModel.prototype.getLifeCycleString = function() {
	return ServiceRequestModel.getLifeCycleStringForId(this.bo.lifecycle);
}



/**
 * Gets the originator id referencing the originating subject
 *
 * @returns the originator id
 * @type Number
 */
ServiceRequestModel.prototype.getOriginatorId = function() {
	return this.bo.originatorId;
}



/**
 * Gets the id of the recipient subject
 *
 * @returns the recipient id
 * @type Number
 */
ServiceRequestModel.prototype.getRecipientId = function() {
	return this.bo.recipientId;
}



/**
 * Get the originator
 *
 * @returns the originator
 * @type Subject
 */
ServiceRequestModel.prototype.getOriginator = function() {
	if (this.originator) {
		return this.originator;
	}

	this.originator = this.service.getSubject(this.bo.originatorId);
	return this.originator;
}



/**
 * Get the recipient
 *
 * @returns the recipient
 * @type Subject
 */
ServiceRequestModel.prototype.getRecipient = function() {
	if (this.recipient) {
		return this.recipient;
	}

	this.recipient = this.service.getSubject(this.bo.recipientId);
	return this.recipient;
}



/**
 * Sets the status information for this request
 *
 * @param {String} statusInfo the last status information
 */
ServiceRequestModel.prototype.setStatusInfo = function(statusInfo) {
	GPSystem.log(GPSystem.DEBUG, module.id, "setStatusInfo(" + statusInfo + ")");
	assert(typeof(statusInfo) == "string", "Argument statusInfo must be string");

	this.bo.state = statusInfo;
}



/**
 * Sets the service request life-cycle
 *
 * @param {Number} lifeCycle the life-cycle state
 */
ServiceRequestModel.prototype.setLifeCycle = function(lifeCycle) {
	GPSystem.log(GPSystem.DEBUG, module.id, "setLifeCycle(" + lifeCycle + ")");
	assert(typeof(lifeCycle) == "number", "Argument lifeCycle must be number");
	this.bo.lifecycle = lifeCycle;
}



/**
 * Assign this service request to the given role id
 *
 * @param {Number} roleId the role id
 */
ServiceRequestModel.prototype.assignToRole = function(roleId) {
	GPSystem.log(GPSystem.DEBUG, module.id, "assignToRole(" + roleId + ")");
	assert(roleId == null || typeof(roleId) == "number", "Argument roleId must be number or null");
	if (roleId != this.bo.assignedToRole) {
		this.assignedRoleChanged = true;
	}
	this.bo.assignedToRole = roleId;
}



/**
 * Determine if the current user has the role that this request has been assigned to.
 *
 * @type boolean
 */
ServiceRequestModel.prototype.isAuthorizedByRole = function(user) {
	if (!this.bo.assignedToRole) {
		return false;
	}

	if (this.bo.assignedToRole && (user.roles.indexOf(this.bo.assignedToRole) >= 0)) {
		return true;
	}

	return false;
}



/**
 * Add a message related to this service request
 *
 * @param {String} message the message
 */
ServiceRequestModel.prototype.addMessage = function(message) {
	if (typeof(this.message) == "undefined") {
		this.message = message;
	} else {
		this.message += "\n" + message;
	}
}



/**
 * Determine if current user can read from this service request
 *
 * The default is, that the the following entities are allowed to read:
 *
 * <ol>
 * <li>The originator</li>
 * <li>The recipient</li>
 * <li>A person with the role RoleDAO.ID_Auditor</li>
 * <li>A person with the role this request is assigned to for processing</li>
 * <li>A person which has been involved in processing this request</li>
 * </ol>
 *
 * @type boolean
 */
ServiceRequestModel.prototype.canRead = function(user) {
	if ((user.id == this.bo.originatorId) || (user.id == this.bo.recipientId)) {
		return true;
	}

	var srDAO = this.service.daof.getServiceRequestDAO();

	if (typeof(user.roles) != "undefined") {
		// Auditor Access
		if (user.roles.indexOf(RoleDAO.ID_Auditor) >= 0) {
			return true;
		}

		// Assigned to Role
		if (this.bo.assignedToRole && (user.roles.indexOf(this.bo.assignedToRole) >= 0)) {
			return true;
		}

		// Feature https://cdn.cardcontact.de/issues/246
		var srlist = srDAO.listServiceRequestsInvolvedByRoles(user.roles, { id: this.bo.id }, 0, 1);
		if (srlist.length > 0) {
			return true;
		}
	}

	var srlist = srDAO.listServiceRequestsInvolvedBySubject(user.id, { id: this.bo.id }, 0, 1);
	if (srlist.length > 0) {
		return true;
	}

	return false;
}



/**
 * Update ServiceRequest object model
 */
ServiceRequestModel.prototype.update = function() {
	var srDAO = this.service.daof.getServiceRequestDAO();
	srDAO.updateContent(this.bo);
}



/**
 * Update the details field in the service request
 *
 * The details field is used to store an unique identifier visible in the user
 * interface for the user to associate the request to an entity.
 */
ServiceRequestModel.prototype.updateDetails = function(details) {
	if (details == this.bo.details) {
		return false;
	}

	var srDAO = this.service.daof.getServiceRequestDAO();
	srDAO.updateDetails(this.bo, details);
	return true;
}



/**
 * Commit changes to the ServiceRequest object
 */
ServiceRequestModel.prototype.commit = function(subjectId, roleId) {
	var srDAO = this.service.daof.getServiceRequestDAO();

	var c = {};
	if (this.message) {
		c.message = this.message;
	}
	var t = {
		content: JSON.stringify(c, null, '\t')
	}

	if (subjectId) {
		t.caseHandlerId = subjectId;
	}

	if (roleId) {
		t.caseHandlerRole = roleId;
	}

	srDAO.addLifeCycleTransition(this.bo, t);

	if (this.assignedRoleChanged) {
		this.assignedRoleChanged = false;
		this.notifyRoleOwner(subjectId);
	}

	this.notifyParent();
}



/**
 * Send a notification mail to all persons that are in possession of
 * the role this service request has been assigned to.
 * If the assigned role has been cleared the originator will be notified.
 */
ServiceRequestModel.prototype.notifyRoleOwner = function(subjectId) {
	var roleDAO = this.service.daof.getRoleDAO();

	if (!this.bo.assignedToRole) {
		// Notify originator
		var originator = this.getOriginator();

		// Notification by e-mail should be supressed,
		// if the user working on a request is the originator.
		if (originator.getId() == subjectId) {
			return;
		}

		if (originator instanceof Person) {
			this.sendServiceRequestNotifcation(originator.bo);
		}
	} else {
		var subjects = roleDAO.getAssignedSubject(this.bo.assignedToRole);
		for (var i = 0; i < subjects.length; i++) {
			var recipient = subjects[i];

			// Notification by e-mail should be supressed,
			// if the user working on a request has the assigned role.
			if (recipient.id == subjectId) {
				return;
			}
		}

		for (var i = 0; i < subjects.length; i++) {
			var recipient = subjects[i];

			if (recipient.type == SubjectDAO.TYPE_PERSON) {
				this.sendServiceRequestNotifcation(recipient);
			}
		}
	}
}



/**
 * Send a notification e-mail
 *
 * @param {Subject} recipient the recipient user
 */
ServiceRequestModel.prototype.sendServiceRequestNotifcation = function(recipient) {
	if (recipient.getContent().allowSRNotification != true) {
		return
	}

	GPSystem.log(GPSystem.DEBUG, module.id, "send notification at " + recipient.email);

	var url = ApplicationServer.instance.serverURL;
	url += "/se/paas/sr/";

	var srURL = url + this.getId();

	var srDAO = this.service.daof.getServiceRequestDAO();
	var filter = {
		originatorId: recipient.id,
		process: "RegisterMyTokenServiceRequest"
	};
	var srList = srDAO.listServiceRequestsByFilter(filter);
	var sr = srList.pop();
	var settingsURL = url + sr.id;


	var subject = "A Service Request has been assigned to you";

	var text =	"Dear user,\n\n" +
			"a service request has been assigned to a role you have in the system.\n\n" +
			"To work on it please visit " + srURL + " .\n\n" +
			"If you like to unsubscribe from service request notifications " +
			"you can update your notification settings in " + settingsURL +
			" or forward this e-mail to " + this.service.emailAdmin + "\n\n" +
			"Kind regards,\n\n" +
			this.service.name + "\n\n";

	this.service.sendMail(recipient.email, subject, text);
}



/**
 * Notify the parent service request is one exists.
 */
ServiceRequestModel.prototype.notifyParent = function() {
	if (this.bo.parentServiceRequestId == undefined
		|| this.bo.parentServiceRequestId <= 0) {
		return;
	}

	var parent = this.service.getServiceRequestById(this.bo.parentServiceRequestId);

	if (parent.notify != undefined) {
		parent.notify(this);
	}
}



/**
 * Provided for backward compatibility. New derived classed should overwrite updateModel() instead
 */
ServiceRequestModel.prototype.validateFieldDefault = function(fld, val) {
	GPSystem.log(GPSystem.DEBUG, module.id, "validateFieldDefault(" + fld.id + "," + val + ")");
	return true;
}



/**
 * Provided for backward compatibility. New derived classed should overwrite updateModel() instead
 */
ServiceRequestModel.prototype.validateField = function(fld, val) {
	GPSystem.log(GPSystem.DEBUG, module.id, "validateField(" + fld.id + "," + val + ")");
	return this.validateFieldDefault(fld, val);
}



/**
 * Callback from forms processing in Form.setFields()
 */
ServiceRequestModel.prototype.updateModel = function(fld, val) {
	GPSystem.log(GPSystem.DEBUG, module.id, "updateModel(" + fld.id + "," + val + ")");

	var r = this.validateField(fld, val);

	if (r) {
		this.model[fld.id] = val;
	}
	return r;
}



/**
 * Set fields in the model with values passed in a POST or REST request
 */
ServiceRequestModel.prototype.setFields = function(user, fields) {
	GPSystem.log(GPSystem.DEBUG, module.id, "setFields()");

	var form = this.getForm(user);

	if (!(form instanceof Form)) {
		form = new Form(form);
	}

	return form.setFields(fields, this);
}



/**
 * Create a describing string
 *
 * @returns the string
 * @type String
 */
ServiceRequestModel.prototype.toString = function() {
	return this.bo.toString();
}
