jscape.keystore = jscape.keystore || {};

/** @class */
jscape.keystore.HostKeysModule = jscape.keystore.KeystoreModule.extend(
    /** @lends {jscape.keystore.HostKeysModule.prototype} */
{
    /** @constructor */
    initialize: function () {
        this.controller = new jscape.keystore.HostKeysController();
    },

    onStart: function() {
        this._super();

        this.controller.start(this.managementService);
    },

    onResume: function() {
        this.controller.onResume();
    }
});

/** @class */
jscape.keystore.HostKeysController = Class.extend(
    /** @lends {jscape.keystore.HostKeysController.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.view = new jscape.keystore.HostKeysView();

        this.importController = new jscape.keystore.ImportHostKeyController();
        this.importTslController = new jscape.keystore.ImportHostKeyTslController();
        this.exportPublicKeyController = new jscape.keystore.ExportHostPublicKeyController();
        this.exportCertificateController = new jscape.keystore.ExportHostCertificateController();
        this.crlController = new jscape.keystore.VerifyHostCertificateRevocationСontroller();
        this.viewController = new jscape.keystore.ViewHostCertificateController();
        this.revokeController = new jscape.keystore.RevokeHostCertificateController();
        this.deleteController = new jscape.keystore.DeleteHostKeyController();

        this._initValidators();
    },

    start: function(managementService) {
        this.managementService = managementService;
        this.view.show(this);
    },

    onResume: function() {
        this.view.refresh();
    },

    onRefresh: function () {
        return $.when(this.managementService.getHostPublicKeys(), this.managementService.getHostCertificates());
    },

    onImportFile: function() {
        this.importController.start(this.managementService)
                .done($.proxy(function (entry) {
                          if (entry instanceof jscape.CertificateSummary) {
                              this._certificateImported(entry);
                          } else if (entry instanceof jscape.PublicKeySummary) {
                              this._publicKeyImported(entry);
                          }
                      }, this));
    },

    onImportTsl: function() {
        this.importTslController.start(this.managementService)
                .done($.proxy(function() { this.view.refresh(); }, this));
    },

    onExportCertificate: function(certificate) {
        this.exportCertificateController.start(this.managementService, certificate.name);
    },

    onExportCertificatePublicKey: function(certificate) {
        this.exportPublicKeyController.start(this.managementService, certificate.name, true);
    },

    onExportPublicKey: function(key) {
        this.exportPublicKeyController.start(this.managementService, key.name, false);
    },

    onVerifyRevocation: function() {
        this.crlController.start(this.managementService)
                .done($.proxy(function() { this.view.refresh(); }, this));
    },

    onViewCertificate: function(certificate) {
        this.viewController.start(this.managementService, certificate.name);
    },

    onRevokeCertificate: function(certificate) {
        this.revokeController.start(this.managementService, certificate)
                .done($.proxy(this._certificateRevoked, this));
    },

    onDeletePublicKey: function(key) {
        this.deleteController.start(this.managementService, key.name, false)
                .done($.proxy(this._publicKeyDeleted, this, key));
    },

    onDeleteCertificate: function(certificate) {
        this.deleteController.start(this.managementService, certificate.name, true)
                .done($.proxy(this._certificateDeleted, this, certificate));
    },

    _publicKeyImported: function(key) {
        this.view.getPublicKeys().push(key);
        this.view.addItem(key);
        this.view.selectItem(key);
    },

    _certificateImported: function(certificate) {
        this.view.getCertificates().push(certificate);
        this.view.addItem(certificate);
        this.view.selectItem(certificate);
    },

    _publicKeyDeleted: function(key) {
        var items = this.view.getPublicKeys();
        var index = $.inArray(key, items);
        if (index != -1) {
            items.splice(index, 1);
            this.view.deleteItem(key);

            if (items.length) {
                index = Math.max(0, Math.min(index, items.length - 1));
                this.view.selectItem(items[index]);
            } else {
                items = this.view.getCertificates();
                if (items.length) {
                    this.view.selectItem(items[items.length - 1]);
                }
            }
        }
    },

    _certificateDeleted: function(certificate) {
        var items = this.view.getCertificates();
        var index = $.inArray(certificate, items);
        if (index != -1) {
            items.splice(index, 1);
            this.view.deleteItem(certificate);

            if (items.length) {
                index = Math.max(0, Math.min(index, items.length - 1));
                this.view.selectItem(items[index - 1]);
            } else {
                items = this.view.getPublicKeys();
                if (items.length) {
                    this.view.selectItem(items[items.length - 1]);
                }
            }
        }
    },

    _certificateRevoked: function() {
        this.view.refresh();
    },

    _initValidators: function() {
        var v = new jscape.Validator();
        v.nonEmptyRule("hostkey_nonempty", window.R.string.KEY_MANAGER_HOST_KEYS_ERROR_BAD_ALIAS);
        v.rule("hostkey_notexists", $.proxy(this._keyAliasNotExists, this), window.R.string.KEY_MANAGER_HOST_KEYS_ERROR_ALIAS_EXISTS);
    },

    _keyAliasNotExists: function(value, excludes) {
        var keys = [].concat(this.view.getPublicKeys(), this.view.getCertificates());
        var aliases = $.map(keys, function(entry) { return excludes && $.inArray(value, excludes) != -1 ? null : entry.name; });
        return $.inArray(value, aliases) == -1;
    }
});

/** @class */
jscape.keystore.HostKeysView = Class.extend(
    /** @lends {jscape.keystore.HostKeysView.prototype} */
{
    DATE_FORMAT: new jscape.DateFormat("{MM}-{dd}-{yy}"),

    selection: null,

    /** @constructor */
    initialize: function() {
        var initializing = true;

        this.fieldset = $("fieldset[name=hostkeysfields]");

        this.data = $("table[role=grid][aria-label=hostkeys]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            remoteSort: false,
            singleSelect: true,
            idField: "name",
            sortName: "name",
            columns: [[
                    {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: 120},
                    {field: "keyAlgorithm", title: $("thead th:nth-child(2)", this.data).text(), sortable: true, width: 70},
                    {field: "keySize", title: $("thead th:nth-child(3)", this.data).text(), sortable: true, width: 70},
                    {field: "certificateSerialNumber", title: $("thead th:nth-child(4)", this.data).text(), sortable: true, width: 80},
                    {field: "certificateIssuer", title: $("thead th:nth-child(5)", this.data).text(), sortable: false, width: 140},
                    {field: "certificateSubject", title: $("thead th:nth-child(6)", this.data).text(), sortable: false, width: 140},
                    {field: "certificateBeginDate", title: $("thead th:nth-child(7)", this.data).text(), sortable: true, width: 80, formatter: $.proxy(this._formatDate, this), styler: $.proxy(this._styleDate, this, true)},
                    {field: "certificateEndDate", title: $("thead th:nth-child(8)", this.data).text(), sortable: true, width: 80, formatter: $.proxy(this._formatDate, this), styler: $.proxy(this._styleDate, this, false)},
                    {field: "revoked", title: $("thead th:nth-child(9)", this.data).text(), sortable: true, width: 60, align: "center", formatter: function(value) { return "<input type=\"checkbox\" onclick=\"return false\"" + (value ? " checked=\"checked\" />" : "/>"); }}
            ]],
            onSelect: $.proxy(this._adjustButtonState, this),
            onUnselect: $.proxy(this._adjustButtonState, this),
            onDblClickRow: $.proxy(this._onClick$ViewButton, this),
            loader: $.proxy(this._onLoad$Data, this),
            onBeforeLoad: function() { return !initializing; },
            onLoadSuccess: $.proxy(this._adjustButtonState, this)
        });

        this.importButton = $("a[role=button][name=import]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ImportButton, this)});
        this.importMenu = $("div[role=menu][aria-label=import-menu]", this.fieldset).menu({onClick: $.proxy(this._onClick$ImportMenu, this)});
        this.exportButton = $("a[role=button][name=export]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ExportButton, this)});
        this.exportMenu = $("div[role=menu][aria-label=export-menu]", this.fieldset).menu({onClick: $.proxy(this._onClick$ExportMenu, this)});
        this.crlButton = $("a[role=button][name=verifycrl]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$CrlButton, this)});
        this.viewButton = $("a[role=button][name=view]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ViewButton, this)});
        this.revokeButton = $("a[role=button][name=revoke]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$RevokeButton, this)});
        this.deleteButton = $("a[role=button][name=delete]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$DeleteButton, this)});

        initializing = false;
    },

    show: function(listener) {
        this.listener = listener || jscape.keystore.HostKeysView.LISTENER;
        this._adjustButtonState();
    },

    refresh: function() {
        this.data.datagrid("reload");
    },

    addItem: function(value) {
        this.data.datagrid("appendRow", value);
    },

    deleteItem: function(value) {
        var index = this.data.datagrid("getRowIndex", value);
        if (index >= 0) {
            this.data.datagrid("deleteRow", index);
        }
        this._adjustButtonState();
    },

    selectItem: function(value) {
        this.data.datagrid("selectRecord", value);
    },

    getPublicKeys: function () {
        return this.data.datagrid("getData").publicKeys;
    },

    getCertificates: function() {
        return this.data.datagrid("getData").certificates;
    },

    _formatDate: function(value) {
        return $.isNumeric(value) ? this.DATE_FORMAT.format(new Date(value)) : null;
    },

    _styleDate: function(before, value) {
        if ($.isNumeric(value)) {
            var now = new Date().getTime();
            return before && value > now || !before && value < now ? "color:red" : "color:green";
        }
        return null;
    },

    _onLoad$Data: function (_, success, error) {
        this.data.datagrid("unselectAll");
        this.listener.onRefresh()
                .then(function (publicKeys, certificates) {
                          var rows = [].concat(publicKeys, certificates);
                          var data = {total: rows.length, rows: rows, publicKeys: publicKeys, certificates: certificates};
                          return $.Deferred().resolve(data).promise();
                      })
                .done(success)
                .fail(error);
    },

    _onClick$ImportButton: function() {
        this.importMenu.menu("show", {alignTo: this.importButton});
        this.importButton.blur();
    },

    _onClick$ImportMenu: function(item) {
        var type = $(item.target).attr("aria-label");
        if ("menu-file" == type) {
            this.listener.onImportFile();
        } else if ("menu-tsl" == type) {
            this.listener.onImportTsl();
        }
    },

    _onClick$ExportButton: function () {
        var value = this.selection;
        if (this._isPublicKey(value)) {
            this.listener.onExportPublicKey(value);
        } else if (this._isCertificate(value)) {
            this.exportMenu.menu("show", {alignTo: this.exportButton});
            this.exportButton.blur();
        }
    },

    _onClick$ExportMenu: function(item) {
        var type = $(item.target).attr("aria-label");
        if ("menu-crt" == type) {
            this.listener.onExportCertificate(this.selection);
        } else if ("menu-pubkey" == type) {
            this.listener.onExportCertificatePublicKey(this.selection);
        }
    },

    _onClick$CrlButton: function() {
        this.listener.onVerifyRevocation();
    },

    _onClick$ViewButton: function() {
        if (this._isCertificate(this.selection)) {
            this.listener.onViewCertificate(this.selection);
        }
    },

    _onClick$RevokeButton: function() {
        if (this._isCertificate(this.selection)) {
            this.listener.onRevokeCertificate(this.selection);
        }
    },

    _onClick$DeleteButton: function() {
        var value = this.selection;
        if (this._isPublicKey(value)) {
            this.listener.onDeletePublicKey(value);
        } else if (this._isCertificate(value)) {
            this.listener.onDeleteCertificate(value);
        }
    },

    _adjustButtonState: function() {
        this.selection = this.data.datagrid("getSelected");
        var enabled = this.selection != null;
        this.exportButton.linkbutton(enabled ? "enable" : "disable");
        this.deleteButton.linkbutton(enabled ? "enable" : "disable");
        this.viewButton.linkbutton(enabled && this._isCertificate(this.selection) ? "enable" : "disable");
        this.revokeButton.linkbutton(enabled && this._isCertificate(this.selection) ? "enable" : "disable");
    },

    _isPublicKey: function(value) {
        return $.inArray(value, this.getPublicKeys()) != -1;
    },

    _isCertificate: function(value) {
        return $.inArray(value, this.getCertificates()) != -1;
    }
});
jscape.keystore.HostKeysView.LISTENER = {
    onRefresh: $.noop,
    onImportFile: $.noop,
    onImportTsl: $.noop,
    onExportPublicKey: $.noop,
    onExportCertificate: $.noop,
    onExportCertificatePublicKey: $.noop,
    onViewCertificate: $.noop,
    onVerifyRevocation: $.noop,
    onRevokeCertificate: $.noop,
    onDeletePublicKey: $.noop,
    onDeleteCertificate: $.noop
};

/** @class */
jscape.keystore.ImportHostKeyController = Class.extend(
    /** @lends {jscape.keystore.ImportHostKeyController.prototype} */
{
    deferred: null,

    start: function(managementService) {
        this.managementService = managementService;
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function (dialog) {
        this.managementService.importHostKey(dialog.getAlias(), dialog.getFile())
                .done($.proxy(function (key) { this.deferred.resolve(key); }, this))
                .fail($.proxy(function () { this.deferred.reject(); }, this))
                .always($.proxy(function () { dialog.destroy(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        return new jscape.keystore.ImportHostKeyDialog();
    }
});

/** @class */
jscape.keystore.ImportHostKeyDialog = jscape.keystore.ImportPublicKeyDialog.extend(
    /** @lends {jscape.keystore.ImportHostKeyDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this._super();

        this.alias.textbox({required: true, validType: ["hostkey_nonempty", "hostkey_notexists"], missingMessage: window.R.string.KEY_MANAGER_HOST_KEYS_ERROR_BAD_ALIAS, width: "60%"});

        this.file = $("input[name=file]", this.fieldset).filebox({required: true, missingMessage: window.R.string.KEY_MANAGER_HOST_KEYS_ERROR_BAD_FILE, width: "60%"});
    },

    getFile: function () {
        return $("#" + this.file.filebox("options").fileboxId)[0].files[0];
    }
});

/** @class */
jscape.keystore.ImportHostKeyTslController = Class.extend(
    /** @lends {jscape.keystore.ImportHostKeyTslController.prototype} */
{
    deferred: null,

    start: function(managementService) {
        this.managementService = managementService;
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function (dialog) {
        this.managementService.importHostKeyTsl(dialog.getUrl())
                .done($.proxy(function () { this.deferred.resolve(); }, this))
                .fail($.proxy(function () { this.deferred.reject(); }, this))
                .always($.proxy(function () { dialog.destroy(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        return new jscape.keystore.ImportPublicKeyTslDialog();
    }
});

/** @class */
jscape.keystore.ExportHostPublicKeyController = Class.extend(
    /** @lends {jscape.keystore.ExportHostPublicKeyController.prototype} */
{
    deferred: null,

    start: function(managementService, alias, isCertificate) {
        this.managementService = managementService;
        this.alias = alias;
        this.isCertificate = !!isCertificate;
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var format = dialog.getFormat();
        var filename = dialog.getFilename();
        dialog.destroy();

        var promise = this.isCertificate
                ? this.managementService.exportHostCertificatePublicKey(this.alias, format, filename)
                : this.managementService.exportHostPublicKey(this.alias, format, filename);
        promise.done($.proxy(function () { this.deferred.resolve(); }, this))
                .fail($.proxy(function () { this.deferred.reject(); }, this))
                .always($.proxy(function () { this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        var dialog = new jscape.keystore.ExportPublicKeyDialog();
        dialog.setFilename(this.alias + ".pub");
        return dialog;
    }
});

/** @class */
jscape.keystore.ExportHostCertificateController = Class.extend(
    /** @lends {jscape.keystore.ExportHostCertificateController.prototype} */
{
    deferred: null,

    start: function(managementService, alias) {
        this.managementService = managementService;
        this.alias = alias;
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var format = dialog.getFormat();
        var filename = dialog.getFilename();
        dialog.destroy();

        this.managementService.exportHostCertificate(this.alias, format, filename)
                .done($.proxy(function() { this.deferred.resolve(); }, this))
                .fail($.proxy(function() { this.deferred.reject(); }, this))
                .always($.proxy(function() { this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        var dialog = new jscape.keystore.ExportCertificateDialog();
        dialog.setFilename(this.alias + ".crt");
        return dialog;
    }
});

/** @class */
jscape.keystore.VerifyHostCertificateRevocationСontroller = Class.extend(
    /** @lends {jscape.keystore.VerifyHostCertificateRevocationСontroller.prototype} */
{
    deferred: null,

    start: function(managementService) {
        this.managementService = managementService;
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        this.managementService.verifyCertificateRevocation(dialog.getFile(), "host")
                .done($.proxy(function() { this.deferred.resolve(); }, this))
                .fail($.proxy(function() { this.deferred.reject(); }, this))
                .always($.proxy(function() { dialog.destroy(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function () {
        return new jscape.keystore.VerifyHostCertificateRevocationDialog();
    }
});

/** @class */
jscape.keystore.VerifyHostCertificateRevocationDialog = Class.extend(
    /** @lends {jscape.keystore.VerifyHostCertificateRevocationDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-hostkey-verifycrl").clone().attr("id", "");
        this.dialog.dialog({
            width: 530, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this)},
                {text: window.R.string.DIALOG_BUTTON_CANCEL || "Cancel", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=verifyhostkeycrlfields]", this.dialog);

        this.file = $("input[type=file][name=file]", this.fieldset)
                .validatebox({required: true, validType: "non_empty", missingMessage: window.R.string.KEY_MANAGER_ERROR_BAD_FILE})
                .change(function() { $(this).validatebox("validate"); });
    },

    getFile: function () {
        var files = this.file[0].files;
        return files && files.length ? files[0] : null;
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.file.focus();
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});

/** @class */
jscape.keystore.RevokeHostCertificateController = Class.extend(
    /** @lends {jscape.keystore.RevokeHostCertificateController.prototype} */
{
    start: function (managementService, certificate) {
        if (certificate.revoked) {
            return managementService.setHostCertificateRevocationStatus(certificate.name, false);
        } else {
            return this._confirmRevoke(certificate.name)
                    .done(function () { return managementService.setHostCertificateRevocationStatus(certificate.name, true); })
        }
    },

    _confirmRevoke: function (alias) {
        return jscape.ConfirmDialog.confirm(window.R.string.KEY_MANAGER_REVOKE_CERTIFICATE_TITLE,
                                            window.R.string.KEY_MANAGER_REVOKE_CERTIFICATE_MESSAGE.supplant({0: alias}));
    }
});

/** @class */
jscape.keystore.DeleteHostKeyController = Class.extend(
    /** @lends {jscape.keystore.DeleteHostKeyController.prototype} */
{
    deferred: null,

    start: function(managementService, alias, isCertificate) {
        this.managementService = managementService;
        this.deferred = $.Deferred();
        this._confirmDelete(alias, isCertificate)
                .done($.proxy(this._deleteConfirmed, this, alias, isCertificate))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
        return this.deferred.promise();
    },

    _confirmDelete: function(alias, isCertificate) {
        if (!!isCertificate) {
            return jscape.ConfirmDialog.confirm(window.R.string.KEY_MANAGER_HOST_KEYS_DELETE_CERTIFICATE_TITLE,
                                                window.R.string.KEY_MANAGER_HOST_KEYS_DELETE_CERTIFICATE_MESSAGE.supplant({0: alias}));
        } else {
            return jscape.ConfirmDialog.confirm(window.R.string.KEY_MANAGER_HOST_KEYS_DELETE_PUBLIC_KEY_TITLE,
                                                window.R.string.KEY_MANAGER_HOST_KEYS_DELETE_PUBLIC_KEY_MESSAGE.supplant({0: alias}));
        }
    },

    _deleteConfirmed: function (alias, isCertificate) {
        var promise = !!isCertificate
                      ? this.managementService.deleteHostCertificate(alias)
                      : this.managementService.deleteHostPublicKey(alias);
        promise.done($.proxy(function () { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function () { this.deferred.reject(); this.deferred = null; }, this));
    }
});

/** @class */
jscape.keystore.ViewHostCertificateController = jscape.keystore.ViewKeySummaryController.extend(
    /** @lends {jscape.keystore.ViewHostCertificateController.prototype} */
{
    /** @override */
    _loadData: function(alias) {
        return this.managementService.getHostCertificateView(alias);
    }
});