/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 2016 CardContact Systems GmbH
 * |'##> <##'|  Schuelerweg 38, 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 Controller for certificates
 */


var X509CertificateRenderer = require('scsh/srv-cc1/X509CertificateRenderer').X509CertificateRenderer;
var CommonUI = require('scsh/srv-cc1/CommonUI').CommonUI;
var CVC = require('scsh/eac/CVC').CVC;
var Holder = require('scsh/pki-db/Holder').Holder;
var RoleDAO = require('scsh/pki-db/db/RoleDAO').RoleDAO;



function CertificateView(ui) {
	this.ui = ui;
	this.x509renderer = new X509CertificateRenderer();
}
CertificateView.prototype = Object.create(X509CertificateRenderer.prototype);
CertificateView.constructor = CertificateView;

exports.CertificateView = CertificateView;



CertificateView.prototype.getCertificate = function(req, res) {
	var id = parseInt(req.url[2]);
	if (typeof(id) != "number" || isNaN(id)) {
		res.setStatus(HttpResponse.SC_BAD_REQUEST);
		return;
	}

	var srdao = this.ui.service.daof.getCertificateDAO();
	var bo = srdao.getCertificateById(id);
	if (!bo) {
		res.setStatus(HttpResponse.SC_NOT_FOUND);
	}

	return bo;
}



CertificateView.prototype.renderCVCertificate = function(req, res, cert) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var cvc = new CVC(cert.bytes);

	var homeURL = this.ui.homeURL(req.url);

	var div = <div id="x509"></div>;
	div.appendChild(<h2>Certificate Info</h2>);
	var t =
		<table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;">
		</table>;

	if (cert.serviceRequestId) {
		var tr =
			<tr>
				<th>Service Request</th>
				<td><a href={homeURL + "/sr/" + cert.serviceRequestId}>{cert.serviceRequestId}</a></td>
			</tr>
		t.appendChild(tr);
	}

	var holderDAO = this.ui.service.daof.getHolderDAO();
	var holder = holderDAO.getHolderById(cert.holderId);
	subjectLink = homeURL + "/subject/" + holder.subjectId;

	if (holder.parentId) {
		var parent = holderDAO.getHolderById(holder.parentId);
		var issuer = <a href={homeURL + "/cert/" + parent.certId}>{cvc.getCAR()}</a>;
	} else {
		var issuer = cvc.getCAR();
	}

	if (holder.subjectId) {
		var subject = <a href={subjectLink}>{cvc.getCHR()}</a>;
	} else {
		var subject = cvc.getCHR();
	}

	if (cvc.isAuthenticatedRequest()) {
		t.tr +=
			<tr>
				<th>Outer CAR</th>
				<td>{cvc.getOuterCAR().toString()}</td>
			</tr>
	}

	t.tr +=
		<tr>
			<th>CAR</th>
			<td>{issuer}</td>
		</tr>
	t.tr +=
		<tr>
			<th>CHR</th>
			<td>{subject}</td>
		</tr>

	if (!cvc.isAuthenticatedRequest()) {
		t.tr +=
			<tr>
				<th>Effective Date</th>
				<td>{cvc.getCED().toDateString() }</td>
			</tr>
		t.tr +=
			<tr>
				<th>Expiration Date</th>
				<td>{cvc.getCXD().toDateString() }</td>
			</tr>
	}

	t.tr +=
		<tr>
			<th>Subject Key Identifier</th>
			<td>{cvc.determineKeyIdentifier()}</td>
		</tr>
// 	t.appendChild(tr);

	div.appendChild(t);

	var t =
		<table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;">

			<tr>
				<th>ASN1</th>
			</tr>
			<tr>
				<td><pre>{cvc.getASN1().toString()}</pre></td>
			</tr>
			<tr>
				<th>Binary</th>
			</tr>
			<tr>
				<td>{cert.bytes.toString(HEX)}</td>
			</tr>
		</table>;

	div.appendChild(<h2>Encoded Certificate</h2>);
	div.appendChild(t);

	return div;
}



CertificateView.prototype.renderCertificate = function(req, res, certbo) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	var homeURL = this.ui.homeURL(req.url);

	var div = <div id="x509"></div>;
	div.appendChild(<h2>Certificate Info</h2>);
	var t =
		<table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;">
			<tr>
				<th>Service Request</th>
				<td><a href={homeURL + "/sr/" + certbo.serviceRequestId}>{certbo.serviceRequestId}</a></td>
			</tr>
			<tr>
				<th>Status</th>
				<td>{this.getOCSPDescription(certbo.status)}</td>
			</tr>
		</table>;

	if (certbo.invalidityDate) {
		var tr =
			<tr>
				<th>Invalidity Date</th>
				<td>{certbo.invalidityDate}</td>
			</tr>
		t.appendChild(tr);
	}

	if (certbo.revocationDate) {
		var tr =
			<tr>
				<th>Revocation Date</th>
				<td>{certbo.revocationDate}</td>
			</tr>
		t.appendChild(tr);
	}

	div.appendChild(t);

	var cert = new X509(certbo.bytes);
	var native = cert.getNative();

	var version = native.getVersion();
	var serial = cert.getSerialNumberString();
	var notBefore = cert.getNotBefore();
	var notAfter = cert.getNotAfter();
	var issuer = cert.getIssuerDNString();
	var subject = cert.getSubjectDNString();

	var holderDAO = this.ui.service.daof.getHolderDAO();
	var holder = holderDAO.getHolderById(certbo.holderId);
	subjectLink = homeURL + "/subject/" + holder.subjectId;

	if (holder.parentId) {
		var parent = holderDAO.getHolderById(holder.parentId);
		issuer = <a href={homeURL + "/cert/" + parent.certId}>{issuer}</a>;
	}

	var sigAlg = native.getSigAlgName();
	var sig = native.getSignature();

	var t =
		<table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;">

			<tr>
				<th>Version</th>
				<td>{version}</td>
			</tr>
			<tr>
				<th>Serial</th>
				<td>{serial}</td>
			</tr>
			<tr>
				<th>Not Before</th>
				<td>{notBefore}</td>
			</tr>
			<tr>
				<th>Not After</th>
				<td>{notAfter}</td>
			</tr>
			<tr>
				<th>Issuer Distinguished Name</th>
				<td>{issuer}</td>
			</tr>
			<tr>
				<th>Subject Distinguished Name</th>
				<td><a href={subjectLink}>{subject}</a></td>
			</tr>
			<tr>
				<th>Signature Algorithm</th>
				<td>{sigAlg}</td>
			</tr>
			<tr>
				<th>Signature</th>
				<td>{sig}</td>
			</tr>
			<tr>
				<th>Public Key</th>
				<td>{this.getPublicKeyInfo(cert)}</td>
			</tr>
		</table>;

	div.appendChild(<h2>Basic Certificate Fields</h2>);
	div.appendChild(t);

	var critOIDs = native.getCriticalExtensionOIDs();
	var nonCritOIDs = native.getNonCriticalExtensionOIDs();

	div.appendChild(<h2>Critical Extensions</h2>);
	var critTable = <table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;"/>
	var critIterator = critOIDs.iterator();
	while (critIterator.hasNext()) {
		var oid = critIterator.next();
		var oidByteString = new ByteString(oid, OID);
		var r = this.createExtensionTableRow(req, res, oidByteString, cert, parent);

		critTable.appendChild(r);
	}
	div.appendChild(critTable);

	div.appendChild(<h2>Non-Critical Extensions</h2>);
	var nonCritTable = <table class="pure-table pure-table-horizontal form-table" style="word-wrap: break-word;"/>
	var nonCritIterator = nonCritOIDs.iterator();
	while (nonCritIterator.hasNext()) {
		var oid = nonCritIterator.next();
		var oidByteString = new ByteString(oid, OID);
		var r = this.createExtensionTableRow(req, res, oidByteString, cert, parent);

		nonCritTable.appendChild(r);
	}
	div.appendChild(nonCritTable);

	return div;
}



CertificateView.prototype.getOCSPDescription= function(statusCode) {
	switch (statusCode) {
		case OCSPQuery.GOOD:
			return "Certificate is valid";
		case OCSPQuery.UNKNOWN:
			return "Certificate is unknown to the responder";
		case OCSPQuery.REVOKED:
			return "Certificate was revoked";
		case OCSPQuery.KEYCOMPROMISE:
			return "Certificate was revoked because the key was compromised";
		case OCSPQuery.CACOMPROMISE:
			return "Certificate was revoked because the CA key was compromised";
		case OCSPQuery.AFFILIATIONCHANGED:
			return "Certificate was revoked because the affiliation changed";
		case OCSPQuery.SUPERSEDED:
			return "Certificate was revoked because a new certificate was issued";
		case OCSPQuery.CESSATIONOFOPERATION:
			return "Certificate was revoked because the CA discontinued operation";
		case OCSPQuery.CERTIFICATEHOLD:
			return "Certificate is on hold";
		case OCSPQuery.REMOVEFROMCRL:
			return "Certificate was revoked and can now be removed form CRL";
		case OCSPQuery.PRIVILEGEWITHDRAWN:
			return "Certificate was revoked because the privileges granted by to the owner were withdrawn";
		case OCSPQuery.AACOMPROMISE:
			return "Certificate was revoked (AACOMPROMISE)";
		default:
			return "Undefined OCSP Status Code";
	}
}



/**
 * Create a HTML table row containing the decoded name and value
 * for a specific certificate extension.
 *
 * @param{ByteString} oid the extension specified by the oid
 * @param{X509} cert
 * @returns  HTML table row
 * @type XML
 */
CertificateView.prototype.createExtensionTableRow = function(req, res, oid, cert, parentHolder) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	if (oid.equals(X509CertificateRenderer.KEY_USAGE_OID)) {
		var value = this.createKeyUsageTable(cert);
	} else if (oid.equals(X509CertificateRenderer.EXT_KEY_USAGE_OID)) {
		var value = this.createExtendedKeyUsageTable(cert);
	} else if (oid.equals(X509CertificateRenderer.BASIC_CONSTRAINTS_OID)) {
		var value = this.createBasicConstraintsTable(cert);
	} else if (oid.equals(X509CertificateRenderer.CRL_DISTRIBUTION_POINTS_OID)) {
		var value = this.createCRLDistributionPointsTable(cert);
	} else if (oid.equals(X509CertificateRenderer.CA_INFORMATION_ACCESS_OID)) {
		var value = this.createAuthorityInformationAccessTable(cert);
	} else if (oid.equals(X509CertificateRenderer.CERTIFICATE_POLICIES)) {
		var value = this.createCertificatePoliciesTable(cert);
	} else if (oid.equals(X509CertificateRenderer.PRIVATE_KEY_USAGE_PERIOD_OID)) {
		var value = this.createPrivateKeyUsagePeriodTable(cert);
	} else if (oid.equals(X509CertificateRenderer.AUTHORITY_KEY_IDENTIFIER_OID) && parentHolder) {
		var homeURL = this.ui.homeURL(req.url);
		var native = cert.getNative();
		var value = <a href={homeURL + "/cert/" + parentHolder.certId}>{native.getExtensionValue(oid.toString(OID))}</a>;
	} else {
		var native = cert.getNative();
		var value = native.getExtensionValue(oid.toString(OID));
	}

	var r =
		<tr>
			<th>{this.getExtensionName(oid)}</th>
			<td>{value}</td>
		</tr>;

	return r
}



CertificateView.prototype.getView = function(req, res, certbo) {
	default xml namespace = "http://www.w3.org/1999/xhtml";

	if (certbo.bytes.byteAt(0) == 0x30) {
		var x = new X509(certbo.bytes);
// 		var c = this.render(x);
		var c = this.renderCertificate(req, res, certbo);

		var div = c..div.(@id =="x509");

	} else { // CVC
		var c = this.renderCVCertificate(req, res, certbo);
	}

	c.appendChild(
			<p>
				<form class="pure-form" action="" method="get" enctype="multipart/form-data">
					<input name="op" type="hidden" value="download"/>
					<button type="submit" class="pure-button">Download Certificate</button>
				</form>
			</p>
		);

	return c;
}



CertificateView.prototype.handleDownloadRequest = function(req, res, cert) {
	var holderDAO = this.ui.service.daof.getHolderDAO();
	var holder = holderDAO.getHolderById(cert.holderId);

	if (holder.certificateType == Holder.CVC) {
		var filename = cert.serial + ".cvcert";
	} else {
		var xcert = new X509(cert.bytes);
		var filename = xcert.getSubjectDNString() + ".cer";
	}
	var reqbin = cert.bytes;
	res.setContentType("application/octet-stream");
	res.setContentLength(reqbin.length);

	res.addHeaderField("Content-Disposition", "attachment; filename=\"" + filename + "\"");
	res.write(reqbin);
}



CertificateView.prototype.handleRequest = function(req, res) {
	default xml namespace = "http://www.w3.org/1999/xhtml";
//	print("req.method:" + req.method);
//	print("req.queryString:" + req.queryString);

	if (req.url.length < 3) {
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Invalid URL");
	}

	var cert = this.getCertificate(req, res);
	if (!cert) {
		return;
	}

	var operation = CommonUI.parseQueryString(req.queryString);
	if (operation.op == "download") {
		this.handleDownloadRequest(req,res, cert);
		return;
	}

	if (typeof(cert.serviceRequestId) == "undefined") {
		if (this.ui.session.user.roles.indexOf(RoleDAO.ID_Auditor) < 0) {
			this.ui.serveNotAuthorizedPage(req, res, req.url);
			return;
		}
	} else {
		var sr = this.ui.service.getServiceRequestById(cert.serviceRequestId);
		if (!sr.canRead(this.ui.session.user)) {
			this.ui.serveNotAuthorizedPage(req, res, req.url);
			return;
		}
	}

	var page = this.getView(req, res, cert);
	var t = this.ui.generateTemplate(req.url);
	var c = t..div.(@id == "content");
	c.div = page;

	this.ui.sendPage(req, res, req.url, t);
}



CertificateView.prototype.handleRestRequest = function(req, res) {
//	print("req.method:" + req.method);
//	print("req.queryString:" + req.queryString);

	if (req.url.length < 3) {
		throw new GPError(module.id, GPError.INVALID_DATA, 0, "Invalid URL");
	}

	res.setContentType("application/json");
	res.setStatus(HttpResponse.SC_NOT_IMPLEMENTED);
}
