jscape.domain = jscape.domain || {};

/** @class */
jscape.Trigger = Class.extend(
    /** @lends {jscape.Trigger.prototype} */
{
    CURRENT_TIME_EVENT: "com.jscape.inet.mft.common.CurrentTimeEvent",

    /** @constructor */
    initialize: function(a, b, c, d, e, f, g, h, i, j, k, l) {
        this.name = a || "";
        this.description = b || "";
        this.eventType = c || null;
        this.expression = d || "";
        this.contexts = e || [];
        this.allowErrorEvent = !!f;
        this.enabled = !!g;
        this.asynchronous = !!h;
        this.asynchronousActions = !!i;
        this.singleInstanceRequired = !!j;
        this.ignoredDomainState = k || null;
        this.tags = l || [];
    },

    clone: function() {
        return jscape.Trigger.fromJSON(this);
    },

    runnable: function() {
        return this.eventType === this.CURRENT_TIME_EVENT;
    }
});
jscape.Trigger.fromJSON = function(data) {
    return data ? $.extend(new jscape.Trigger(), data, {contexts: $.map(data.contexts, jscape.ActionContext.fromJSON)}) : null;
};
jscape.Trigger.Status = {QUEUED: "QUEUED", RUNNING: "RUNNING", PAUSED: "PAUSED", CANCELLED: "CANCELLED", COMPLETED: "COMPLETED", FAILED: "FAILED"};
jscape.Trigger.Status.values = function() { return [jscape.Trigger.Status.QUEUED, jscape.Trigger.Status.RUNNING, jscape.Trigger.Status.PAUSED, jscape.Trigger.Status.CANCELLED, jscape.Trigger.Status.COMPLETED, jscape.Trigger.Status.FAILED]; };

/** @class */
jscape.TriggerState = Class.extend(
    /** @lends {jscape.TriggerState.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d,e, f, g, h, i, j, k) {
        this.id = parseInt(a) || 0;
        this.triggerName = b || "";
        this.eventId = c || 0;
        this.eventClassName = d || "";
        this.eventParameters = e || {};
        this.singleInstanceRequired = !!f;
        this.status = g || jscape.Trigger.Status.QUEUED;
        this.startTime = parseInt(h) || 0;
        this.endTime = parseInt(i) || 0;
        this.runTime = parseInt(j) || 0;
        this.actionStates = k || [];
    }
});
jscape.TriggerState.fromJSON = function(data) {
    return data ? $.extend(new jscape.TriggerState(), data, {actionStates: $.map(data.actionStates || [], jscape.ActionState.fromJSON)}) : null;
};

/** @class */
jscape.TriggerContext = Class.extend(
    /** @lends {jscape.TriggerContext.prototype} */
{
    /** @constructor */
    initialize: function(events, actions, functions, operators) {
        this.events = events || [];
        this.actions = actions || [];
        this.functions = functions || [];
        this.operators = operators;
    }
});

/** @class */
jscape.TriggerFunction = Class.extend(
    /** @lends {jscape.TriggerFunction.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c) {
        this.name = a || "";
        this.templates = b || {};
        this.description = c || "";
    }
});
jscape.TriggerFunction.fromJSON = function(data) {
    return data ? $.extend(new jscape.TriggerFunction(), data) : null;
};

/** @class */
jscape.TriggerEvent = Class.extend(
    /** @lends {jscape.TriggerEvent.prototype} */
{
    /** @constructor */
    initialize: function(a, b,c) {
        this.type = a || "";
        this.name = b || "";
        this.description = c || "";
    }
});
jscape.TriggerEvent.fromJSON = function(data) {
    return data ? $.extend(new jscape.TriggerEvent(), data) : null;
};

/** @class */
jscape.TriggerAction = Class.extend(
    /** @lends {jscape.TriggerAction.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c) {
        this.name = a || "";
        this.actionClass = b || "";
        this.description = c || "";
    }
});
jscape.TriggerAction.fromJSON = function(data) {
    return data ? $.extend(new jscape.TriggerAction(), data) : null;
};

/** @class */
jscape.ActionContext = Class.extend(
    /** @lends {jscape.ActionContext.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c) {
        this.actionClass = a || null;
        this.parametersMap = b || {};
        this.notes = c || "";
    },

    hasParameter: function(name) {
        return this.parametersMap.hasOwnProperty(name);
    },

    getParameter: function(name) {
        if (this.hasParameter(name)) {
            var value = this.parametersMap[name];
            if (/^(true|false)$/i.test(value)) {
                return "true" === value.toLowerCase();
            } else if (/^null$/.test(value)) {
                return null;
            } else if (/\d+/.test(value)) {
                var num = Number(value);
                return isNaN(num) ? value : num;
            } else {
                return value;
            }
        }
        return null;
    },

    setParameter: function(name, value) {
        this.parametersMap[name + ""] = value != null ? value + "" : null;
    }
});
jscape.ActionContext.fromJSON = function(data) {
    return data ? new jscape.ActionContext(data.actionClass, data.parametersMap, data.notes) : null;
};

/** @class */
jscape.ActionState = Class.extend(
    /** @lends {jscape.ActionState.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e) {
        this.actionClass = a || null;
        this.startTime = parseInt(b) || 0;
        this.endTime = parseInt(c) || 0;
        this.runTime = parseInt(d) || 0;
        this.status = e || jscape.ActionState.Status.QUEUED;
    }
});
jscape.ActionState.fromJSON = function(data) {
    return data ? new jscape.ActionState(data.actionClass, data.startTime, data.endTime, data.runTime, jscape.ActionState.Status.fromCode(data.status, jscape.ActionState.Status.QUEUED)) : null;
};
jscape.ActionState.Status = {QUEUED: "QUEUED", RUNNING: "RUNNING", INTERRUPTED: "INTERRUPTED", COMPLETED: "COMPLETED", FAILED: "FAILED", CANCELLED: "CANCELLED", OMITTED: "OMITTED"};
jscape.ActionState.Status.fromCode = function(code, defaultValue) {
    var codes = {
        1: jscape.ActionState.Status.QUEUED,
        2: jscape.ActionState.Status.RUNNING,
        3: jscape.ActionState.Status.INTERRUPTED,
        4: jscape.ActionState.Status.COMPLETED,
        5: jscape.ActionState.Status.FAILED,
        6: jscape.ActionState.Status.CANCELLED,
        7: jscape.ActionState.Status.OMITTED
    };
    var status = codes[code + ""];
    return status ? status : defaultValue;
};

/** @class */
jscape.TriggerOrder = Class.extend(
    /** @lends {jscape.TriggerOrder.prototype} */
{
    /** @constructor */
    initialize: function(a, b) {
        this.eventType = a || "";
        this.triggers = b || [];
    }
});
jscape.TriggerOrder.fromJSON = function(data) {
    return data ? new jscape.TriggerOrder(data.eventType, data.triggers) : null;
};

/** @class */
jscape.TriggerServiceConfiguration = Class.extend(
    /** @lends {jscape.TriggerServiceConfiguration.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e) {
        this.maxThreadCount = parseInt(a) || null;
        this.maxEventQueueSize = parseInt(b) || null;
        this.maxTransferCount = parseInt(c) || null;
        this.recentTriggerExpirationPeriodMillis = parseInt(d) || null;
        this.globalVariables = e || {};
    },

    toJSON: function() {
        if (this.maxThreadCount == null && this.maxEventQueueSize == null) {
            var obj = $.extend({executionParameters: null}, this);
            delete obj.maxThreadCount;
            delete obj.maxEventQueueSize;
            return obj;
        }
        return this;
    }
});
jscape.TriggerServiceConfiguration.fromJSON = function(data) {
    return data ? new jscape.TriggerServiceConfiguration(data.maxThreadCount, data.maxEventQueueSize, data.maxTransferCount, data.recentTriggerExpirationPeriodMillis, data.globalVariables) : null;
};

/** @class */
jscape.RemoteTriggerServiceConfiguration = Class.extend(
    /** @lends {jscape.RemoteTriggerServiceConfiguration.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f, g, h, i) {
        this.enabled = a !== undefined ? !!a : false;
        this.brokerUrl = b || "";
        this.queueName = c || "";
        this.username = d || "";
        this.password = e || "";
        this.keyProvider = f || null;
        this.hostKeyProvider = g || null;
        this.events = h || [];
        this.maxQueueSize = parseInt(i) || 0;
    }
});
jscape.RemoteTriggerServiceConfiguration.fromJSON = function(data) {
    function keyProviderOf(provider) {
        return provider ? (provider.keyAlias ? jscape.ServerKeyProvider.fromJSON(provider) : (provider.file ? jscape.FileKeyProvider.fromJSON(provider) : null)) : null;
    }
    return $.extend(new jscape.RemoteTriggerServiceConfiguration(), data || {}, data ? {
        keyProvider: keyProviderOf(data.keyProvider),
        hostKeyProvider: keyProviderOf(data.hostKeyProvider)
    } : {});
};

/** @class */
jscape.domain.TriggersModule = jscape.domain.DomainModule.extend(
    /** @lends {jscape.domain.TriggersModule.prototype} */
{
    onCreate: function () {
        this._super.apply(this, arguments);

        this.triggersController = new jscape.domain.TriggersController();
        this.recentTriggersController = new jscape.domain.RecentTriggersController();
        this.settingsController = new jscape.domain.TriggersSettingsController();
        this.remoteSettingsController = new jscape.domain.RemoteTriggerSettingsController();
        this.actionsController = new jscape.domain.ActionsController();
        this.functionsController = new jscape.domain.FunctionsController();

        $.when(this.managementService.getTriggerEvents(this.domainName),
               this.managementService.getTriggerActions(this.domainName),
               this.managementService.getTriggerFunctions(this.domainName),
               this.managementService.getTriggerOperators(this.domainName))
                .then(function (events, actions, functions, operators) { return new jscape.TriggerContext(events, actions, functions, operators); })
                .done($.proxy(function (context) { this.triggerContext = context; }, this))
                .fail($.proxy(function () { this.triggerContext = new jscape.TriggerContext(); }, this))
                .always($.proxy(function () {
                            this.triggersController.onCreate(this.managementService, this.domainName, this.triggerContext);
                            this.recentTriggersController.onCreate(this.managementService, this.domainName);
                            this.settingsController.onCreate(this.managementService, this.domainName, this.triggerContext);
                            this.remoteSettingsController.onCreate(this.managementService, this.domainName, this.triggerContext);
                            this.actionsController.onCreate(this.managementService, this.domainName, this.triggerContext);
                            this.functionsController.onCreate(this.managementService, this.domainName, this.triggerContext);
                        }, this));
    },

    onStart: function () {
        this.triggersController.onStart();
        this.recentTriggersController.onStart();
        this.settingsController.onStart();
        this.remoteSettingsController.onStart();
        this.actionsController.onStart();
        this.functionsController.onStart();
    },

    onPause: function() {
        this._confirmAndApply($.grep([
                    this.triggersController,
                    this.recentTriggersController,
                    this.settingsController,
                    this.remoteSettingsController,
                    this.actionsController,
                    this.functionsController
                ],
                function (controller) {
                    return controller.hasChanged();
                }));
    },

    onResume: function() {
        this.triggersController.onResume();
        this.recentTriggersController.onResume();
        this.settingsController.onResume();
        this.remoteSettingsController.onResume();
        this.actionsController.onResume();
        this.functionsController.onResume();
    }
});
jscape.domain.TriggersModule.displayClassNameOf = function(className) {
    return (className || "").replace(/.+\.([^\.]+)(Event|Action)$/, "$1").replace(/([a-z])([A-Z])/g, "$1 $2");
};

/** @class */
jscape.domain.TriggersController = jscape.ui.DatagridController.extend(
    /** @lends {jscape.domain.TriggersController.prototype} */
{
    view: null,

    /** @constructor */
    initialize: function() {
        this._initValidators();
    },

    onCreate: function (managementService, domainName, triggerContext) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.triggerContext = triggerContext;

        this.addController = new jscape.domain.AddTriggerController();
        this.editController = new jscape.domain.EditTriggerController();
        this.copyController = new jscape.domain.CopyTriggerController();
        this.runController = new jscape.domain.RunTriggerController();
        this.deleteController = new jscape.domain.DeleteTriggerController();
        this.orderController = new jscape.domain.OrderTriggerController();
        this.importController = new jscape.domain.ImportTriggerController();
        this.exportController = new jscape.domain.ExportTriggerController();
        this.promoteController = new jscape.domain.PromoteTriggerController();
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.TriggersView();
        }
        this.view.show(this);
    },

    hasChanged: function() {
        return false;
    },

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

    onRefresh: function(params) {
        params = jscape.Page.requestParameters(params);
        return this.managementService.listTriggers(this.domainName, params.startIndex, params.count, params.query)
                .then(jscape.Page.datagridFormatter());
    },

    onTriggerOrder: function() {
        this.orderController.start(this.managementService, this.domainName, this.triggerContext)
                .done($.proxy(this._fireApplySuccess, this));
    },

    onImportTrigger: function() {
        return this.importController.start(this.managementService, this.domainName)
                .always($.proxy(function() { this.view.refresh(); }, this));
    },

    onExportTrigger: function(triggers) {
        var names = $.map(triggers, function(trigger) { return trigger.name; });
        return this.exportController.start(this.managementService, this.domainName, names);
    },

    onPromoteTrigger: function(triggers) {
        var names = $.map(triggers, function(trigger) { return trigger.name; });
        return this.promoteController.start(this.managementService, this.domainName, names)
                .done($.proxy(this._fireApplySuccess, this));
    },

    onAddTrigger: function() {
        this.addController.start(this.managementService, this.domainName, this.triggerContext)
                .done($.proxy(this._triggerAdded, this));
    },

    onEditTrigger: function(trigger, force) {
        var index = $.inArray(trigger, this.view.getTriggers());
        if (index != -1 || force === true) {
            this.editController.start(this.managementService, this.domainName, this.triggerContext, trigger)
                    .done($.proxy(this._triggerEdited, this, index));
        }
    },

    onCopyTrigger: function(trigger) {
        this.copyController.start(this.managementService, this.domainName, trigger)
                .done($.proxy(this._triggerCopied, this))
                .done($.proxy(this._fireApplySuccess, this));
    },

    onRunTrigger: function(triggers, runWithData) {
        var names = $.map(triggers, function(trigger) { return trigger.name; });
        return this.runController.start(this.managementService, this.domainName, names, runWithData);
    },

    onDeleteTrigger: function(triggers) {
        this.deleteController.start(this.managementService, this.domainName, triggers)
                .done($.proxy(this._triggerDeleted, this, triggers))
                .done($.proxy(this._fireApplySuccess, this));
    },

    _triggerAdded: function(trigger) {
        this._recordAdded(trigger);
    },

    _triggerEdited: function(index, trigger) {
        this._recordEdited(trigger);
    },

    _triggerCopied: function(trigger, editAfterDone) {
        if (editAfterDone) {
            this.view.refresh($.proxy(function() {
                this.view.selectRecord(trigger);
                this.onEditTrigger(trigger, true);
            }, this));
        } else {
            this._triggerAdded(trigger);
        }
    },

    _triggerDeleted: function (triggers) {
        this._recordDeleted(triggers);
    },

    _initValidators: function() {
        var v = new jscape.Validator();
        v.nonEmptyRule("trigger_name_nonempty", window.R.string.TRIGGERS_ERROR_BAD_TRIGGER_NAME);
        v.rule("trigger_unique", $.proxy(this._triggerNotExists, this), window.R.string.TRIGGERS_ERROR_ALREADY_EXISTS);
    },

    _triggerNotExists: function(name) {
        var values = $.map(this.view.getTriggers(), function(trigger) { return trigger.name; });
        return $.inArray(name, values) == -1;
    }
});

/** @class */
jscape.domain.TriggersView = jscape.ui.DatagridView.extend(
    /** @lends {jscape.domain.TriggersView.prototype} */
{
    selection: null,

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

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

        this.data = $("table[role=grid][aria-label=triggers]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            ctrlSelect: true,
            pagination: true,
            search: true,
            remoteSearch: true,
            remoteSort: true,
            sortName: "name",
            idField: "name",
            columns: [[
                {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: 80},
                {field: "eventType", title: $("thead th:nth-child(2)", this.data).text(), sortable: true, width: 100, formatter: $.proxy(this._formatEvent, this), sorter: $.proxy(this._sortEvent, this)},
                {field: "enabled", title: $("thead th:nth-child(3)", this.data).text(), sortable: true, width: 50, finder: {type: "combobox", options: {data: $.map([true, false], function(value) { return {text: value, value: value}; }), panelHeight: "auto"}}},
                {field: "expression", title: $("thead th:nth-child(4)", this.data).text(), sortable: false, searchable: false, width: 200},
                {field: "actions", title: $("thead th:nth-child(5)", this.data).text(), sortable: false, searchable: false, width: 100, formatter: $.proxy(function(_, rowData) { return this._formatActions(rowData); },  this)},
                {field: "tags", title: $("thead th:nth-child(6)", this.data).text(), width: 80, formatter: function(value) { return String($.isArray(value) ? value.join(", ") : value).escapeXml(); }}
            ]],
            onSelect: $.proxy(this._adjustRowSelection, this),
            onUnselect: $.proxy(this._adjustRowSelection, this),
            onDblClickRow: $.proxy(this._onClick$EditButton, this),
            loader: $.proxy(this._onLoad$Data, this),
            onBeforeLoad: function () { return !initializing; },
            onLoadSuccess: $.proxy(this._adjustRowSelection, this)
        });

        this.orderButton = $("a[role=button][name=order]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$OrderButton, this)});
        this.importButton = $("a[role=button][name=import]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ImportButton, this)});
        this.exportButton = $("a[role=button][name=export]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ExportButton, this)});
        this.promoteButton = $("a[role=button][name=promote]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$PromoteButton, this)});
        this.addButton = $("a[role=button][name=add]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$AddButton, this)});
        this.editButton = $("a[role=button][name=edit]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$EditButton, this)});
        this.copyButton = $("a[role=button][name=copy]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$CopyButton, this)});
        this.runButton = $("a[role=button][name=run]", this.fieldset)
                .splitbutton({disabled: true, plain: false, menu: "#runtriggermenu", onClick: $.proxy(this._onClick$RunButton, this)});
        var runButtonOpts = this.runButton.splitbutton("options");
        $(runButtonOpts.menu).menu({onClick: runButtonOpts.onClick});

        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.domain.TriggersView.LISTENER;
        this._adjustRowSelection();
    },

    getTriggers: function() {
        return this.getRecords();
    },

    _formatEvent: function(value) {
        return jscape.domain.TriggersModule.displayClassNameOf(value).escapeXml();
    },

    _sortEvent: function(a, b) {
        a = jscape.domain.TriggersModule.displayClassNameOf(a);
        b = jscape.domain.TriggersModule.displayClassNameOf(b);
        return a == b ? 0 : (a > b ? 1 : -1);
    },

    _formatActions: function(trigger) {
        return $.map(trigger.contexts, function(entry) { return jscape.domain.TriggersModule.displayClassNameOf(entry.actionClass).escapeXml(); }).join(", ");
    },

    _onLoad$Data: function(params, success, error) {
        this.listener.onRefresh(params).done(success).fail(error);
    },

    _onClick$OrderButton: function() {
        this.listener.onTriggerOrder();
    },

    _onClick$ImportButton: function() {
        this.importButton.linkbutton("disable");
        this.listener.onImportTrigger()
                .always($.proxy(function() { this.importButton.linkbutton("enable"); }, this));
    },

    _onClick$ExportButton: function() {
        this.exportButton.linkbutton("disable");
        this.listener.onExportTrigger(this.selection)
                .always($.proxy(function() { this.exportButton.linkbutton("enable"); }, this));
    },

    _onClick$PromoteButton: function() {
        this.promoteButton.linkbutton("disable");
        this.listener.onPromoteTrigger(this.selection)
                        .always($.proxy(function() { this.promoteButton.linkbutton("enable"); }, this));
    },

    _onClick$AddButton: function() {
        this.listener.onAddTrigger();
    },

    _onClick$EditButton: function() {
        this.listener.onEditTrigger(this.selection[0]);
    },

    _onClick$CopyButton: function() {
        this.listener.onCopyTrigger(this.selection[0]);
    },

    _onClick$RunButton: function (item) {
        this.runButton.splitbutton("disable");
        this.listener.onRunTrigger(this.selection, item && "run-data" == item.name)
            .always($.proxy(function () { this.runButton.splitbutton("enable"); }, this));
    },

    _onClick$DeleteButton: function() {
        this.listener.onDeleteTrigger(this.selection);
    },

    _adjustRowSelection: function () {
        this.selection = this.data.datagrid("getSelections");
        this._adjustButtonsState();
    },

    _adjustButtonsState: function() {
        var count = this.data.datagrid("getRows").length;
        var runnable = this.selection.length != 0;
        $.each(this.selection, function(_, trigger) { if (!trigger.runnable()) { runnable = false; return false; }});

        this.orderButton.linkbutton(count > 0 ? "enable" : "disable");
        this.exportButton.linkbutton(this.selection.length != 0 ? "enable" : "disable");
        this.promoteButton.linkbutton(this.selection.length != 0 ? "enable" : "disable");
        this.editButton.linkbutton(this.selection.length == 1 ? "enable" : "disable");
        this.copyButton.linkbutton(this.selection.length == 1 ? "enable" : "disable");
        this.runButton.linkbutton(runnable ? "enable" : "disable");
        this.deleteButton.linkbutton(this.selection.length != 0 ? "enable" : "disable");
    }
});
jscape.domain.TriggersView.LISTENER = {
    onRefresh: $.noop,
    onTriggerOrder: $.noop,
    onImportTrigger: $.noop,
    onExportTrigger: $.noop,
    onPromoteTrigger: $.noop,
    onAddTrigger: $.noop,
    onEditTrigger: $.noop,
    onCopyTrigger: $.noop,
    onRunTrigger: $.noop,
    onDeleteTrigger: $.noop
};

/** @class */
jscape.domain.BaseTriggerController = Class.extend(
    /** @lends {jscape.domain.BaseTriggerController.prototype} */
{
    eventType: null,

    onPageSelected: function(dialog) {
        var page = dialog.getItem(dialog.getCurrentItem());
        if (page != null && page == this.expressionController) {
            var eventType = this.parametersController.getEventType();
            if (this.eventType != eventType) {
                this._loadEventProperties(eventType);
            }
        }
    },

    _loadEventProperties: function (eventType) {
        return this.managementService.getTriggerEvent(this.domainName, eventType)
                .done($.proxy(function (properties) {
                    this.eventType = eventType;
                    this.expressionController.setEventProperties(eventType, properties);
                    this.actionsController.setEventProperties(properties);
                }, this));
    }
});

/** @class */
jscape.domain.AddTriggerController = jscape.domain.BaseTriggerController.extend(
    /** @lends {jscape.domain.AddTriggerController.prototype} */
{
    deferred: null,

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

    onSubmit: function(dialog) {
        var trigger = this._createTrigger(dialog);
        dialog.destroy();
        this.managementService.addTrigger(this.domainName, trigger)
                .done($.proxy(function(trigger) { this.deferred.resolve(trigger); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    _createTrigger: function (dialog) {
        var trigger = this.parametersController.createTrigger();
        trigger.expression = this.expressionController.createExpression();
        trigger.contexts = this.actionsController.createActionContexts();
        return trigger;
    },

    _setupDialog: function () {
        var dialog = new jscape.domain.AddTriggerDialog();
        var container = dialog.container();

        this.parametersController = new jscape.domain.AddTriggerParametersController(container);
        this.expressionController = new jscape.domain.TriggerExpressionController(container);
        this.actionsController = new jscape.domain.TriggerActionsController(container);
        dialog.addItem(this.parametersController);
        dialog.addItem(this.expressionController);
        dialog.addItem(this.actionsController);

        this.parametersController.start(this.managementService, this.domainName, this.triggerContext);
        this.expressionController.start(this.managementService, this.domainName, this.triggerContext);
        this.actionsController.start(this.managementService, this.domainName, this.triggerContext);

        return dialog;
    }
});

/** @class */
jscape.domain.EditTriggerController = jscape.domain.BaseTriggerController.extend(
    /** @lends {jscape.domain.EditTriggerController.prototype} */
{
    deferred: null,

    start: function(managementService, domainName, triggerContext, trigger) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.triggerName = trigger.name;
        this.triggerContext = triggerContext;
        this.deferred = $.Deferred();
        this.eventType = null;
        this.managementService.getTrigger(this.domainName, this.triggerName)
                .done($.proxy(function (trigger) {
                          this._setupDialog(trigger).show(this);
                      }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var trigger = this._createTrigger(dialog);
        dialog.destroy();
        this.managementService.storeTrigger(this.domainName, this.triggerName, trigger)
                .done($.proxy(function(trigger) { this.deferred.resolve(trigger); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this))
    },

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

    _createTrigger: function (dialog) {
        var trigger = this.parametersController.createTrigger();
        trigger.expression = this.expressionController.createExpression();
        trigger.contexts = this.actionsController.createActionContexts();
        return trigger;
    },

    _setupDialog: function (trigger) {
        var dialog = new jscape.domain.EditTriggerDialog();
        dialog.setTriggerName(this.triggerName);

        var container = dialog.container();
        this.parametersController = new jscape.domain.EditTriggerParametersController(container);
        this.expressionController = new jscape.domain.TriggerExpressionController(container);
        this.actionsController = new jscape.domain.TriggerActionsController(container);
        dialog.addItem(this.parametersController);
        dialog.addItem(this.expressionController);
        dialog.addItem(this.actionsController);

        this.parametersController.start(this.managementService, this.domainName, this.triggerContext, trigger);
        this.expressionController.start(this.managementService, this.domainName, this.triggerContext, trigger.expression);
        this.actionsController.start(this.managementService, this.domainName, this.triggerContext, trigger.contexts);

        return dialog;
    }
});

/** @class */
jscape.domain.AddTriggerDialog = jscape.view.WizardDialog.extend(
    /** @lends {jscape.domain.AddTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this._super($("#d-triggers-add"), {height: 460});
    }
});

/** @class */
jscape.domain.EditTriggerDialog = jscape.view.WizardDialog.extend(
    /** @lends {jscape.domain.EditTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this._super($("#d-triggers-edit"), {height: 430});
    },

    setTriggerName: function(value) {
        this.triggerName = value || "";
    },

    /** @override */
    _adjustDialogTitle: function() {
        this.dialog.dialog("setTitle", this.title.supplant({0: this.triggerName, 1: this.index + 1, 2: this.items.length}));
    }
});

/** @class */
jscape.domain.TriggerParametersView = Class.extend(
    /** @lends {jscape.domain.TriggerParametersView.prototype} */
{
    TRIGGER_ERROR_EVENT: "com.jscape.inet.mft.workflow.TriggerErrorEvent",

    /** @constructor */
    initialize: function (fieldset) {
        this.fieldset = fieldset.hide();

        this.event = $("select[name=eventtype]", this.fieldset).combobox({
            required: true,
            editable: true,
            limitToList: true,
            width: 270,
            textField: "name",
            valueField: "type",
            onChange: $.proxy(this._onChange$Event, this)
        });
        this.eventHelp = $("#trigger-event-help", this.fieldset).tooltip({
            position: "bottom", showDelay: 200, onShow: function() {
                $(this).tooltip("tip").addClass("tooltip-tip-black");
                $(this).tooltip("reposition");
            }
        });
        this.description = $("textarea[name=description]", this.fieldset).textbox({multiline: true, width: "60%", height: 40});

        this.tags = $("select[name=tags]", this.fieldset).tagbox({limitToList: true, hasDownArrow: true, panelHeight: "auto", width: "60%", onLoadSuccess: function() { $(this).tagbox("clear"); }});

        this.ignoreDomainState = $("input[name=ignoredomainstate]:checkbox", this.fieldset);
        this.ignoreDomainState.change($.proxy(function () {
            var enabled = this.ignoreDomainState.is(":checked");
            this.domainState.combobox(enabled ? "enable" : "disable").combobox(enabled ? "enableValidation" : "disableValidation");
        }, this));
        this.domainState = $("select[name=domainstate]", this.fieldset);
        this.domainState.combobox({
            required: true,
            editable: false,
            width: 170,
            panelHeight: "auto",
            onLoadSuccess: $.proxy(function (data) {
                if (data.length == 0) {
                    this.ignoreDomainState.prop("disabled", true).prop("checked", false)
                }
                this.ignoreDomainState.change();
            }, this)});

        this.enabled = $("input[name=enabled]:checkbox", this.fieldset);
        this.triggerSingleInstance = $("input[name=singleinstance]:checkbox", this.fieldset);
        this.triggerAsynchronous = $("input[name=triggerasync]:checkbox", this.fieldset);
        this.actionsAsynchronous = $("input[name=actionsasync]:checkbox", this.fieldset);
        this.fireError = $("input[name=fireerror]:checkbox", this.fieldset);
    },

    getEvent: function () {
        var data = this.event.combobox("getData");
        var value = this.event.combobox("getValue");
        for (var i = 0; i < data.length; i++) {
            var event = data[i];
            if (event.type == value) {
                return event;
            }
        }
        return null;
    },

    setEvent: function (value) {
        this.event.combobox("select", value);
    },

    setAvailableEvents: function (values) {
        this.event.combobox("loadData", values);
    },

    getDescription: function () {
        return this.description.textbox("getValue");
    },

    setDescription: function (value) {
        this.description.textbox("setValue", value);
    },

    getIgnoredDomainState: function () {
        return this.ignoreDomainState.is(":checked") ? this.domainState.combobox("getValue") : null;
    },

    setIgnoredDomainState: function (value) {
        this.ignoreDomainState.prop("checked", value != null).change();
        if (value != null) {
            this.domainState.combobox("select", value);
        }
    },

    isEnabled: function () {
        return this.enabled.is(":checked");
    },

    setEnabled: function (value) {
        this.enabled.prop("checked", !!value);
    },

    isSingleInstanceRequired: function() {
        return this.triggerSingleInstance.is(":checked");
    },

    setSingleInstanceRequired: function(value) {
        this.triggerSingleInstance.prop("checked", !!value);
    },

    isTriggerAsynchronous: function() {
        return this.triggerAsynchronous.is(":checked");
    },

    setTriggerAsynchronous: function(value) {
        this.triggerAsynchronous.prop("checked", !!value);
    },

    isActionsAsynchronous: function() {
        return this.actionsAsynchronous.is(":checked");
    },

    setActionsAsynchronous: function(value) {
        this.actionsAsynchronous.prop("checked", !!value);
    },

    isFireError: function () {
        return this.fireError.is(":checked");
    },

    setFireError: function (value) {
        this.fireError.prop("checked", !!value);
    },

    setAvailableTags: function (values) {
        this.tags.tagbox("loadData", values);
    },

    getTags: function () {
        return this.tags.tagbox("getValues");
    },

    setTags: function (values) {
        this.tags.tagbox("setValues", values);
    },

    show: function() {
        this.fieldset.show();
        this._onChange$Event();
    },

    hide: function() {
        this.fieldset.hide();
    },

    valid: function() {
        return this.fieldset.form("validate");
    },

    _onChange$Event: function () {
        var event = this.getEvent();
        if (event != null) {
            this._adjustEventHelp(event);
            this._adjustFireErrorState(event);
        }
    },

    _adjustEventHelp: function (event) {
        if (event && event.description) {
            var content = "<div class=\"tooltip-title\">" + event.name +"</div>" + event.description;
            this.eventHelp.tooltip("update", content).removeClass("disabled");
        } else {
            this.eventHelp.tooltip("update", function () { return "..."; }).addClass("disabled");
        }
    },

    _adjustFireErrorState: function(event) {
        this.fireError.prop("disabled", this.TRIGGER_ERROR_EVENT === event.type)
                .prop("checked", !this.fireError.is(":disabled"));
    }
});

/** @class */
jscape.domain.AddTriggerParametersController = Class.extend(
    /** @lends {jscape.domain.AddTriggerParametersController.prototype} */
{
    /** @constructor */
    initialize: function(container) {
        this.view = new jscape.domain.AddTriggerParametersView(container);
    },

    start: function(managementService, domainName, triggerContext) {
        this.managementService = managementService;
        this.domainName = domainName;
        this._setupView(triggerContext.events);
    },

    pause: function() {
        this.view.hide();
    },

    resume: function() {
        this.view.show(this);
    },

    valid: function() {
        return this.view.valid();
    },

    getEventType: function() {
        return this.view.getEvent().type;
    },

    createTrigger: function() {
        return new jscape.Trigger(this.view.getName(),
                                  this.view.getDescription(),
                                  this.getEventType(),
                                  null,
                                  null,
                                  this.view.isFireError(),
                                  this.view.isEnabled(),
                                  this.view.isTriggerAsynchronous(),
                                  this.view.isActionsAsynchronous(),
                                  this.view.isSingleInstanceRequired(),
                                  this.view.getIgnoredDomainState(),
                                  this.view.getTags());
    },

    _setupView: function(events) {
        this.view.setAvailableEvents(events);
        this.view.setEnabled(true);
        this.view.setFireError(true);

        this.managementService.getDomainTags(this.domainName)
                .done($.proxy(function(tags) { this.view.setAvailableTags(tags); }, this));
    }
});
/** @class */
jscape.domain.EditTriggerParametersController = Class.extend(
    /** @lends {jscape.domain.EditTriggerParametersController.prototype} */
{
    /** @constructor */
    initialize: function(container) {
        this.view = new jscape.domain.EditTriggerParametersView(container);
    },

    start: function(managementService, domainName, triggerContext, trigger) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.trigger = trigger;
        this._setupView(trigger, triggerContext.events);
    },

    pause: function() {
        this.view.hide();
    },

    resume: function() {
        this.view.show(this);
    },

    valid: function() {
        return this.view.valid();
    },

    getEventType: function() {
        return this.view.getEvent().type;
    },

    createTrigger: function() {
        return new jscape.Trigger(this.trigger.name,
                                  this.view.getDescription(),
                                  this.getEventType(),
                                  this.trigger.expression,
                                  this.trigger.contexts,
                                  this.view.isFireError(),
                                  this.view.isEnabled(),
                                  this.view.isTriggerAsynchronous(),
                                  this.view.isActionsAsynchronous(),
                                  this.view.isSingleInstanceRequired(),
                                  this.view.getIgnoredDomainState(),
                                  this.view.getTags());
    },

    _setupView: function (trigger, events) {
        this.view.setAvailableEvents(events);

        this.managementService.getDomainTags(this.domainName)
                .done($.proxy(function (tags) {
                          this.view.setAvailableTags(tags);
                          trigger && this.view.setTags(trigger.tags);
                      }, this));

        if (trigger) {
            this.view.setDescription(trigger.description);
            this.view.setEvent(trigger.eventType);
            this.view.setEnabled(trigger.enabled);
            this.view.setSingleInstanceRequired(trigger.singleInstanceRequired);
            this.view.setTriggerAsynchronous(trigger.asynchronous);
            this.view.setActionsAsynchronous(trigger.asynchronousActions);
            this.view.setFireError(trigger.allowErrorEvent);
            this.view.setIgnoredDomainState(trigger.ignoredDomainState);
        }
    }
});

/** @class */
jscape.domain.AddTriggerParametersView = jscape.domain.TriggerParametersView.extend(
    /** @lends {jscape.domain.AddTriggerParametersView.prototype} */
{
    /** @constructor */
    initialize: function(container) {
        this._super($("fieldset[name=addtriggerparametersfields]", container));

        this.name = $("input[name=name]", this.fieldset).textbox({required: true, validType: ["trigger_unique", "non_empty"], missingMessage: window.R.string.TRIGGERS_ERROR_BAD_TRIGGER_NAME, invalidMessage: window.R.string.TRIGGERS_ERROR_BAD_TRIGGER_NAME, width: "60%"});
    },

    getName: function () {
        return this.name.textbox("getValue");
    }
});

/** @class */
jscape.domain.EditTriggerParametersView = jscape.domain.TriggerParametersView.extend(
    /** @lends {jscape.domain.EditTriggerParametersView.prototype} */
{
    /** @constructor */
    initialize: function(container) {
        this._super($("fieldset[name=edittriggerparametersfields]", container));

        this.event.combobox({disabled: true});
    }
});

/** @class */
jscape.domain.TriggerExpressionController = jscape.view.WizardDialog.Page.extend(
    /** @lends {jscape.domain.TriggerExpressionController.prototype} */
{
    OPERATOR: {
        LOGICAL: {AND: "AND", OR: "OR"}
    },

    eventType: null,
    eventProperties: null,

    /** @constructor */
    initialize: function(container) {
        this.view = new jscape.domain.TriggerExpressionView(container);
        this.autoCompleteController = new jscape.AutoCompleteController();
        this.constantsController = new jscape.domain.ConstantsController();
        this.timeExpressionController = new jscape.domain.TimeExpressionController();
        this.testController = new jscape.domain.ExpressionTestController();
    },

    start: function(managementService, domainName, triggerContext, expression) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.eventProperties = [];
        this.functions = triggerContext.functions;
        this.operators = triggerContext.operators;

        this.view.setExpression(expression || "");
        this.view.show(this);
    },

    setEventProperties: function(eventType, eventProperties) {
        this.eventType = eventType;
        this.eventProperties = eventProperties || [];
    },

    /** @override */
    pause: function() {
        this.view.hide();
    },

    /** @override */
    resume: function() {
        this.view.show(this);
    },

    /** @override */
    valid: function() {
        var response = this.managementService.validateTriggerExpression(this.domainName, this.view.getExpression(), this.eventType);
        return response && response.trim().toLowerCase() === "true";
    },

    onInsertVariable: function(input) {
        var variables = $.map(this.eventProperties, function(entry) {
            return {value: entry.property, text: entry.property, description: entry.description};
        });
        this.autoCompleteController.start(input, variables);
    },

    onInsertFunction: function (input) {
        var functions = [];
        for (var i = 0; i < this.functions.length; i++) {
            $.each(this.functions[i].templates, function(key, value) {
                functions.push({value: key, text: key.replace(/(.*)(\(.*)/, "$1 $2"), description: value});
            });
        }
        this.autoCompleteController.start(input, functions);
    },

    onInsertConstant: function(input) {
        this.constantsController.start()
                .done($.proxy(function(value) { this.view.insertText(value); }, this));
    },

    onInsertOperator: function(input) {
        var operators = [];
        $.each(this.operators, function(key, value) { return operators.push({value: key, text: key, description: value}); });
        this.autoCompleteController.start(input, operators);
    },

    onInsertAndOperator: function() {
        this.view.insertText(this.OPERATOR.LOGICAL.AND + " ");
    },

    onInsertOrOperator: function() {
        this.view.insertText(this.OPERATOR.LOGICAL.OR + " ");
    },

    onInsertTimeExpression: function() {
        this.timeExpressionController.start(this.eventProperties)
                .done($.proxy(function(expression) { this.view.insertText(expression); }, this));
    },

    onTestExpression: function() {
        if (this.valid()) {
            this.testController.start(this.managementService, this.domainName, this.eventProperties, this.createExpression());
        }
    },

    createExpression: function() {
        return this.view.getExpression();
    }
});

/** @class */
jscape.domain.TriggerExpressionView = Class.extend(
    /** @lends {jscape.domain.TriggerExpressionView.prototype} */
{
    /** @constructor */
    initialize: function(container) {
        this.fieldset = $("fieldset[name=triggerexpressionfields]", container);

        this.expression = $("textarea[name=expression]", this.fieldset).textbox({multiline: true, width: "100%", height: 118});
        this.expression.textbox("textbox").on("input change", $.proxy(function() {
            var enabled = this.expression.textbox("textbox").val().trim().length != 0;
            this.testButton.linkbutton(enabled ? "enable" : "disable");
        }, this));
        this.expressionHelp = $("#trigger-expression-help", this.fieldset);
        this.expressionHelp.tooltip({position: "right", showDelay: 0, content: this.expressionHelp.html(), onShow: function() { $(this).tooltip("tip").addClass("tooltip-tip-black");}});
        this.expressionHelp.empty();

        this.variablesButton = $("a[role=button][name=variables]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$VariablesButton, this)});
        this.functionsButtons = $("a[role=button][name=functions]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$FunctionsButtons, this)});
        this.constantsButton = $("a[role=button][name=constants]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ConstantsButton, this)});
        this.timeExpressionButton = $("a[role=button][name=timeexpression]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$TimeExpressionButton, this)});
        this.operatorsButton = $("a[role=button][name=operators]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$OperatorsButton, this)});
        this.operatorAndButton = $("a[role=button][name=operatorand]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$OperatorAndButton, this)});
        this.operatorOrButton = $("a[role=button][name=operatoror]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$OperatorOrButton, this)});
        this.testButton = $("a[role=button][name=testexpression]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$TestButton, this)});
    },

    getExpression: function() {
        return this.expression.textbox("getValue").trim();
    },

    setExpression: function(value) {
        this.expression.textbox("setValue", value);
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.TriggerExpressionView.LISTENER;
        this.fieldset.show();
        this.expression.textbox("textbox").select().focus();
    },

    hide: function() {
        this.fieldset.hide();
    },

    insertText: function(value) {
        var input = this.expression.textbox("textbox");
        var text = input.val();
        text = text.substring(0, input[0].selectionStart) + value + text.substring(input[0].selectionEnd);
        this.expression.textbox("setText", text);
        input[0].selectionStart = input[0].selectionEnd = input[0].selectionStart + value.length;
        return input.focus();
    },

    _onClick$VariablesButton: function() {
        this.listener.onInsertVariable(this.expression.textbox("textbox"));
    },

    _onClick$FunctionsButtons: function() {
        this.listener.onInsertFunction(this.expression.textbox("textbox"));
    },

    _onClick$ConstantsButton: function() {
        this.listener.onInsertConstant(this.expression.textbox("textbox"));
    },

    _onClick$OperatorsButton: function() {
        this.listener.onInsertOperator(this.expression.textbox("textbox"));
    },

    _onClick$OperatorAndButton: function() {
        this.listener.onInsertAndOperator();
    },

    _onClick$OperatorOrButton: function() {
        this.listener.onInsertOrOperator();
    },

    _onClick$TimeExpressionButton: function() {
        this.listener.onInsertTimeExpression();
    },

    _onClick$TestButton: function() {
        this.listener.onTestExpression();
    }
});
jscape.domain.TriggerExpressionView.LISTENER = {
    onInsertVariable: $.noop,
    onInsertFunction: $.noop,
    onInsertConstant: $.noop,
    onInsertOperator: $.noop,
    onInsertAndOperator: $.noop,
    onInsertOrOperator: $.noop,
    onInsertTimeExpression: $.noop,
    onTestExpression: $.noop
};

/** @class */
jscape.domain.TimeExpressionController = Class.extend(
    /** @lends {jscape.domain.TimeExpressionController.prototype} */
{
    FIELD: {HOUR: "Hour", MINUTE: "Minute", DAY_OF_WEEK: "DayOfWeek", DAY_OF_MONTH: "DayOfMonth", DAY_OF_YEAR: "DayOfYear", WEEK_OF_MONTH: "WeekOfMonth", WEEK_OF_YEAR: "WeekOfYear", MONTH: "Month", YEAR: "Year"},
    VALUE_SEPARATOR_REGEXP: /\s*,\s*/,

    deferred: null,
    eventProperties: null,

    /** @constructor */
    initialize: function() {
        this._initValidators();
    },

    start: function(eventProperties) {
        this.eventProperties = eventProperties || {};
        this.deferred = $.Deferred();

        this._setupDialog().show(this);

        return this.deferred.promise();
    },

    onHelpRequested: function(name) {
        var help = null;
        $.each(this.eventProperties, function(_, entry) {
            if ((entry.property || "").toLowerCase() == name.toLowerCase()) {
                help = entry.description;
                return false;
            }
            return true;
        });
        return help;
    },

    onSubmit: function(dialog) {
        var expression = this._createExpression(dialog);
        dialog.destroy();
        this.deferred.resolve(expression);
    },

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

    _setupDialog: function() {
        return new jscape.domain.TimeExpressionDialog();
    },

    _createExpression: function(dialog) {
        return $.map(
                $.grep([
                           this._fieldExpression(this.FIELD.HOUR, dialog.getHour()),
                           this._fieldExpression(this.FIELD.MINUTE, dialog.getMinute()),
                           this._fieldExpression(this.FIELD.DAY_OF_WEEK, dialog.getDayOfWeek()),
                           this._fieldExpression(this.FIELD.DAY_OF_MONTH, dialog.getDayOfMonth()),
                           this._fieldExpression(this.FIELD.DAY_OF_YEAR, dialog.getDayOfYear()),
                           this._fieldExpression(this.FIELD.WEEK_OF_MONTH, dialog.getWeekOfMonth()),
                           this._fieldExpression(this.FIELD.WEEK_OF_YEAR, dialog.getWeekOfYear()),
                           this._fieldExpression(this.FIELD.MONTH, dialog.getMonth()),
                           this._fieldExpression(this.FIELD.YEAR, dialog.getYear())
                       ],
                       function (entry) { return entry != null; }),
                function (expression) { return "(" + expression + ")"}
        ).join(" AND ");
    },

    _fieldExpression: function(field, value) {
        var values = value.trim().split(this.VALUE_SEPARATOR_REGEXP);
        return value.trim().length != 0 ? $.map(values, function(value) { return field + " = " + parseInt(value, 10); }).join(" OR ") : null;
    },

    _initValidators: function(value) {
        var v = new jscape.Validator();
        v.rule("timeexpression_comma_separated_numbers", $.proxy(this._commaSeparatedNumbers, this));
    },

    _commaSeparatedNumbers: function(value) {
        var values = value.trim().split(this.VALUE_SEPARATOR_REGEXP);
        return $.grep(values, function(value) { return /[^\d]+/.test(value) || isNaN(parseInt(value, 10)); }).length == 0;
    }
});

/** @class */
jscape.domain.TimeExpressionDialog = Class.extend(
    /** @lends {jscape.domain.TimeExpressionDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-timexpression").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=timeexpressionfields]", this.dialog);

        this.hour = $("input[name=hour]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_HOURS, width: "60%"});
        this.minute = $("input[name=minute]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_MINUTES, width: "60%"});
        this.dayOfWeek = $("input[name=dayofweek]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_DAYSOFWEEK, width: "60%"});
        this.dayOfMonth = $("input[name=dayofmonth]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_DAYSOFMONTH, width: "60%"});
        this.dayOfYear = $("input[name=dayofyear]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_DAYSOFYEAR, width: "60%"});
        this.weekOfMonth = $("input[name=weekofmonth]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_WEEKSOFMONTH, width: "60%"});
        this.weekOfYear = $("input[name=weekofyear]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_WEEKSOFYEAR, width: "60%"});
        this.month = $("input[name=month]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_MONTHS, width: "60%"});
        this.year = $("input[name=year]", this.fieldset).textbox({required: false, validType: "timeexpression_comma_separated_numbers", invalidMessage: window.R.string.TIME_EXPRESSION_ERROR_BAD_YEARS, width: "60%"});

        this.helps = this.fieldset.find("span.help");
        this.helps.tooltip({position: "right", showDelay: 0, onShow: function () {
            $(this).tooltip("tip").addClass("tooltip-tip-black");
            $(this).tooltip("reposition");
        }});
        this.helps.one("mouseenter", $.proxy(this._adjustPropertyHelp, this));
    },

    getHour: function() {
        return this.hour.textbox("getValue");
    },

    getMinute: function() {
        return this.minute.textbox("getValue");
    },

    getDayOfWeek: function() {
        return this.dayOfWeek.textbox("getValue");
    },

    getDayOfMonth: function() {
        return this.dayOfMonth.textbox("getValue");
    },

    getDayOfYear: function() {
        return this.dayOfYear.textbox("getValue");
    },

    getWeekOfMonth: function() {
        return this.weekOfMonth.textbox("getValue");
    },

    getWeekOfYear: function() {
        return this.weekOfYear.textbox("getValue");
    },

    getMonth: function() {
        return this.month.textbox("getValue");
    },

    getYear: function() {
        return this.year.textbox("getValue");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:visible:first").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;
    },

    _adjustPropertyHelp: function(e) {
        var target = $(e.target);
        var input = this.fieldset.find("[aria-describedby=" + target.attr("id") + "]:input");
        if (input && input.length) {
            var name = input.attr("textboxName");
            if (name) {
                target.tooltip("update", this.listener.onHelpRequested(name) || "");
            }
        }
    }
});
jscape.domain.TimeExpressionDialog.LISTENER = {
    onHelpRequested: $.noop
};

/** @class */
jscape.domain.ConstantsController = Class.extend(
    /** @lends {jscape.domain.ConstantsController.prototype} */
{
    /** @const */
    BOOLEAN: {TRUE: "TRUE", FALSE: "FALSE"},

    deferred: null,

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

    onSubmit: function(dialog) {
        var value = this._createConstant(dialog);
        dialog.destroy();
        this.deferred.resolve(value);
    },

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

    _createConstant: function(dialog) {
        if (dialog.isString()) {
            return '"' + dialog.getString() + '"';
        } else if (dialog.isBoolean()) {
            return dialog.getBoolean() ? this.BOOLEAN.TRUE : this.BOOLEAN.FALSE;
        } else if (dialog.isNumber()) {
            return dialog.getNumber();
        }
        return "";
    },

    _setupDialog: function() {
        return new jscape.domain.ConstantsDialog();
    }
});

/** @class */
jscape.domain.ConstantsDialog = Class.extend(
    /** @lends {jscape.domain.ConstantsDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-constant").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=triggerexpressionconstantsfields]", this.dialog);

        this.type = $("input[name=type]", this.fieldset).change($.proxy(this._adjustFieldState, this));
        this.stringValue = $("input[name=string]", this.fieldset).textbox({validType: "non_empty", width: "60%"});
        this.numberValue = $("input[name=number]", this.fieldset).numberspinner({value: 0, width: "50%"});
        this.booleanValue = $("input[name=boolean]:radio", this.fieldset);
    },

    isString: function() {
        return this.type.filter("[value=string]").is(":checked");
    },

    getString: function() {
        return this.stringValue.textbox("getValue");
    },

    isNumber: function() {
        return this.type.filter("[value=number]").is(":checked");
    },

    getNumber: function() {
        return parseInt(this.numberValue.numberspinner("getValue"));
    },

    isBoolean: function() {
        return this.type.filter("[value=boolean]").is(":checked");
    },

    getBoolean: function() {
        return this.booleanValue.filter("[value=true]").is(":checked");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.type.first().prop("checked", true).change();
    },

    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;
    },

    _adjustFieldState: function() {
        this.stringValue.textbox(this.isString() ? "enable" : "disable");
        this.numberValue.numberspinner(this.isNumber() ? "enable" : "disable");
        this.booleanValue.prop("disabled", !this.isBoolean());

        if (this.isString()) {
            this.stringValue.textbox("textbox").focus();
        } else if (this.isNumber()) {
            this.numberValue.focus();
        } else if (this.isBoolean()) {
            this.booleanValue.filter(":checked").focus();
        }
    }
});

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

    start: function(managementService, domainName, eventProperties, expression) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.eventProperties = eventProperties;
        this.expression = expression;
        this.deferred = $.Deferred();

        this._setupDialog().show(this);

        return this.deferred.promise();
    },

    onTest: function(dialog) {
        var parameters = this._createParameters(dialog);
        this.managementService.testTriggerExpression(this.domainName, this.expression, parameters)
                .done($.proxy(function(response) {
                    this._onComplete((response || "").toLowerCase() === "true");
                }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
    },

    _setupDialog: function() {
        var dialog = new jscape.domain.ExpressionTestDialog();
        dialog.setExpression(this.expression);
        dialog.setProperties(this.eventProperties);
        return dialog;
    },

    _createParameters: function(dialog) {
        var parameters = {};
        $.each(this.eventProperties, function(_, entry) {
            var name = entry.property;
            parameters[name] = dialog.getParameter(name);
        });
        return parameters;
    },

    _onComplete: function (success) {
        $.messager.alert(window.R.string.TRIGGERS_TEST_EXPRESSION_DIALOG_TITLE,
                         success ? window.R.string.TRIGGERS_SUCCESS_EXPRESSION_TEST_PASSED : window.R.string.TRIGGERS_ERROR_EXPRESSION_TEST_FAILED,
                         success ? "info" : "error");
    }
});
/** @class */
jscape.domain.ExpressionTestDialog = Class.extend(
    /** @lends {jscape.domain.ExpressionTestDialog.prototype} */
{
    panels: null,

    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-expressiontest").clone().attr("id", "");
        this.dialog.dialog({
            width: 650, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_TEST || "Test", handler: $.proxy(this._onTest, this)},
                {text: window.R.string.DIALOG_BUTTON_CLOSE || "Close", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=triggerexpressiontestfields]", this.dialog);
        this.expression = $("textarea[name=expression]", this.fieldset).textbox({multiline: true, width: "100%", height: 80, readonly: true});
        this.parameters = $("fieldset[name=expressionparametersfields]", this.dialog);
    },

    setExpression: function(value) {
        this.expression.textbox("setValue", value);
    },

    setProperties: function(properties) {
        this.panels = [];
        this.parameters.empty();
        $.each(properties, $.proxy(function(_, entry) {
            var panel = this._createValuePanel(entry);
            panel.appendTo(this.parameters);
            this.panels.push(panel);
        }, this));
    },

    getParameter: function(name) {
        for (var i = 0; i < this.panels.length; i++) {
            var panel = this.panels[i];
            if (name == panel.getName()) {
                return panel.getValue();
            }
        }
        return null;
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").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");
    },

    _onTest: function () {
        if (this.fieldset.form("validate")) {
            this.listener.onTest(this);
        }
    },

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

    _createValuePanel: function(eventProperty) {
        return new jscape.domain.ExpressionTestDialog.ValuePanel(eventProperty.type, eventProperty.property);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});
/** @class */
jscape.domain.ExpressionTestDialog.ValuePanel = Class.extend(
    /** @lends {jscape.domain.ExpressionTestDialog.ValuePanel.prototype} */
{
    type: null,
    input: null,

    /** @constructor */
    initialize: function(type, name) {
        this.type = type;
        this.name = name;
    },

    appendTo: function (parent) {
        var row;
        if (this.isBoolean()) {
            this.input = $('<div class="radio"><span style="width:16em;float:left">' + this.name + '</span>'
                           + '<label><input type="radio" name="' + this.name + '" value="true" />true</label>'
                           + '&nbsp;&nbsp;<label><input type="radio" name="' + this.name + '" value="false" />false</label>')
                    .appendTo(parent)
                    .find(":input");
        } else if (this.isNumber()) {
            this.input = $('<div><label>' + this.name + '</label><input type="text" name="' + this.name + '"/></div>')
                    .appendTo(parent)
                    .find(":input")
                    .numberspinner({value: 0, width: "25%"});
        } else {
            this.input = $('<div><label>' + this.name + '</label><input type="text" name="' + this.name + '" maxlength="50"/></div>')
                    .appendTo(parent)
                    .find(":input")
                    .textbox({width: "60%"});
        }
    },

    getName: function() {
        return this.name;
    },

    getValue: function() {
        if (this.isBoolean()) {
            return this.input.filter("[value=true]").is(":checked");
        } else if (this.isNumber()) {
            return parseInt(this.input.numberspinner("getValue"));
        } else {
            return this.input.val();
        }
    },

    isBoolean: function() {
        return this.type === "boolean";
    },

    isNumber: function() {
        return $.inArray(this.type, ["byte", "short", "int", "long", "number"]) != -1;
    }
});

/** @class */
jscape.domain.OrderTriggerController = Class.extend(
    /** @lends {jscape.domain.OrderTriggerController.prototype} */
{
    eventType: null,
    triggers: null,
    deferred: null,

    start: function (managementService, domainName, triggerContext) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.eventType = null;
        this.triggers = [];
        this.deferred = $.Deferred();
        this._selectTriggerEvent(triggerContext.events)
                .then($.proxy(function (eventType) {
                          this.eventType = eventType;
                          return this.managementService.getTriggerOrder(this.domainName, eventType);
                      }, this))
                .done($.proxy(function (order) {
                          this.triggers = order.triggers;
                          var event = $.grep(triggerContext.events, $.proxy(function(event) { return event.type == this.eventType; }, this))
                          this._setupDialog(event.length ? event[0].name : "", this.triggers).show(this);
                      }, this))
                .fail($.proxy(this._cancel, this));
        return this.deferred.promise();
    },

    onTriggerUp: function (dialog, item) {
        var index = $.inArray(item, this.triggers);
        if (index > 0) {
            this.triggers.splice(index - 1, 2, this.triggers[index], this.triggers[index - 1]);
            dialog.setTriggers(this.triggers);
            dialog.selectTrigger(item);
        }
    },

    onTriggerDown: function (dialog, item) {
        var index = $.inArray(item, this.triggers);
        if (index != -1 && index < this.triggers.length - 1) {
            this.triggers.splice(index, 2, this.triggers[index + 1], this.triggers[index]);
            dialog.setTriggers(this.triggers);
            dialog.selectTrigger(item);
        }
    },

    onSubmit: function(dialog) {
        dialog.destroy();
        this.managementService.setTriggerOrder(this.domainName, this.eventType, new jscape.TriggerOrder(this.eventType, this.triggers))
                .done($.proxy(function(order) { this.deferred.resolve(order); this.deferred = null; }, this))
                .fail($.proxy(this._cancel, this));
    },

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

    _cancel: function() {
        this.deferred.reject();
        this.deferred = null;
    },

    _selectTriggerEvent: function (events) {
        var deferred = $.Deferred();
        var dialog = new jscape.domain.TriggerEventTypeDialog();
        dialog.setEvents(events);
        dialog.show({
            onSubmit: $.proxy(function (dialog) {
                var event = dialog.getEvent();
                dialog.destroy();
                deferred.resolve(event ? event.type : null);
                deferred = null;
            }, this),
            onCancel: $.proxy(function (dialog) {
                dialog.destroy();
                deferred.reject();
                deferred = null;
            }, this)});
        return deferred.promise();
    },

    _setupDialog: function(event, triggers) {
        var dialog = new jscape.domain.OrderTriggerDialog();
        dialog.setEvent(event);
        dialog.setTriggers(triggers);
        return dialog;
    }
});

/** @class */
jscape.domain.TriggerEventTypeDialog = Class.extend(
    /** @lends {jscape.domain.TriggerEventTypeDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-eventtype").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=triggereventtypefields]", this.dialog);

        this.event = $("select[name=event]", this.fieldset);
        this.event.combobox({
            required: true,
            editable: true,
            limitToList: true,
            width: 310,
            textField: "name",
            valueField: "type",
            formatter: function(row) { return '<span title="' + String(row.description || "").escapeXml() + '">' + String(row.name || "").escapeXml() + '</span>'; },
            onChange: $.proxy(this._adjustEventHelp, this)
        });

        this.eventHelp = $("#trigger-event-help", this.fieldset).tooltip({position: "right", showDelay: 0});
    },

    getEvent: function () {
        var value = this.event.combobox("getValue");
        var events = $.grep(this.event.combobox("getData"), function(event) { return event.type == value; });
        return events.length ? events[0] : null;
    },

    setEvents: function(values) {
        this.event.combobox("loadData", values);
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this._adjustEventHelp();
        this.event.combobox("textbox").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;
    },

    _adjustEventHelp: function () {
        var event = this.getEvent();
        this.eventHelp.tooltip("update", event ? event.description : "");
    }
});

/** @class */
jscape.domain.OrderTriggerDialog = Class.extend(
    /** @lends {jscape.domain.OrderTriggerDialog.prototype} */
{
    selection: null,

    /** @constructor */
    initialize: function(resources) {
        this.selection = [];

        this.dialog = $("#d-triggers-order").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=triggersorderfields]", this.dialog);

        this.event = $("input[name=eventtype]", this.fieldset).textbox({readonly: true, width: "75%"});
        this.data = $("table[role=grid][aria-label=triggersorder]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            remoteSort: false,
            singleSelect: true,
            sortOrder: "asc",
            columns: [[{field: "_name", formatter: function(_, rowData) { return String(rowData).escapeXml(); }, sortable: false, width: 200}]],
            onSelect: $.proxy(this._adjustRowSelection, this),
            onUnselect: $.proxy(this._adjustRowSelection, this),
            onLoadSuccess: $.proxy(this._adjustRowSelection, this)
        });

        this.moveUpButton = $("a[role=button][name=moveup]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$MoveUpButton, this)});
        this.moveDownButton = $("a[role=button][name=movedown]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$MoveDownButton, this)});
    },

    setEvent: function(value) {
        this.event.textbox("setValue", value);
    },

    setTriggers: function(values) {
        this.data.datagrid("loading").datagrid("loadData", values).datagrid("loaded");
    },

    selectTrigger: function (value) {
        var index = $.inArray(value, this.data.datagrid("getRows"));
        this.data.datagrid("selectRow", index);
    },

    show: function (listener) {
        this.listener = listener || jscape.domain.OrderTriggerDialog.LISTENER;
        this.dialog.dialog("center").dialog("open");
        this._adjustRowSelection();
    },

    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;
    },

    _onClick$MoveUpButton: function() {
        this.listener.onTriggerUp(this, this.selection[0]);
    },

    _onClick$MoveDownButton: function() {
        this.listener.onTriggerDown(this, this.selection[0]);
    },

    _adjustRowSelection: function () {
        this.selection = this.data.datagrid("getSelections");
        this._adjustButtonsState();
    },

    _adjustButtonsState: function () {
        var rows = this.data.datagrid("getRows");
        var index = this.selection.length == 1 ? $.inArray(this.selection[0], rows) : -1;

        this.moveUpButton.linkbutton(index > 0 && index <= rows.length - 1 ? "enable" : "disable");
        this.moveDownButton.linkbutton(index >= 0 && index < rows.length - 1 ? "enable" : "disable");
    }
});
jscape.domain.OrderTriggerDialog.LISTENER = {
    onTriggerUp: $.noop,
    onTriggerDown: $.noop
};

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

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

    onSubmit: function(dialog) {
        var targetName = dialog.getName();
        var editAfterDone = dialog.isEditAfterDone();
        dialog.destroy();
        this.managementService.copyTrigger(this.domainName, this.triggerName, targetName)
                .done($.proxy(function(trigger) { this.deferred.resolve(trigger, editAfterDone); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    _setupDialog: function () {
        var dialog = new jscape.domain.CopyTriggerDialog();
        dialog.setName(this.triggerName);
        dialog.setEditAfterDone(true);
        return dialog;
    }
});

/** @class */
jscape.domain.CopyTriggerDialog = Class.extend(
    /** @lends {jscape.domain.CopyTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-copy").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=copytriggerfields]", this.dialog);
        this.title = this.dialog.dialog("header").text() || "";

        this.name = $("input[name=name]", this.fieldset).textbox({required: true, validType: ["trigger_unique", "non_empty"], missingMessage: window.R.string.TRIGGERS_ERROR_BAD_TRIGGER_NAME, invalidMessage: window.R.string.TRIGGERS_ERROR_BAD_TRIGGER_NAME, width: "60%"});
        this.editAfterDone = $("input[name=editafterdone]:checkbox", this.fieldset);
    },

    getName: function() {
        return this.name.textbox("getValue");
    },

    setName: function(value) {
        this.dialog.dialog("setTitle", this.title.supplant({0: value}));
    },

    isEditAfterDone: function() {
        return this.editAfterDone.is(":checked");
    },

    setEditAfterDone: function(value) {
        this.editAfterDone.prop("checked", !!value);
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.name.textbox("textbox").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.domain.RunTriggerController = Class.extend(
    /** @lends {jscape.domain.RunTriggerController.prototype} */
{
    deferred: null,

    start: function(managementService, domainName, triggerNames, runWithData) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.deferred = $.Deferred();
        this._triggerParameters(runWithData)
                .done($.proxy(function(data) { this._runTrigger(triggerNames, data); }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
        return this.deferred.promise();
    },

    _triggerParameters: function(runWithData) {
        var deferred = $.Deferred();
        if (!runWithData) {
            deferred.resolve({});
        } else {
            new jscape.domain.RunTriggerDialog().show({
                onSubmit: function(dialog) {
                    var data = dialog.getData();
                    dialog.destroy();
                    deferred.resolve(data);
                },
                onCancel: function(dialog) {
                    dialog.destroy();
                    deferred.reject();
                }
            });
        }
        return deferred.promise();
    },

    _runTrigger: function (triggerNames, data) {
        $.when.apply(jQuery, $.map(triggerNames, $.proxy(function (name) { return this.managementService.runTrigger(this.domainName, name, data); }, this)))
                .done($.proxy(function () { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function () { this.deferred.reject(); this.deferred = null; }, this));
    }
});

/** @class */
jscape.domain.RunTriggerDialog = Class.extend(
    /** @lends {jscape.domain.RunTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-run").clone().attr("id", "");
        this.dialog.dialog({
            width: 650, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", disabled: true, id: "d-triggers-run-okbtn", 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=runtriggerfields]", this.dialog);

        this.data = $("table[role=grid][aria-label=runparameteres]", this.fieldset);
        this.data.propertygrid({
            fit: true,
            toolbar: [
                {iconCls: "icon-add", handler: $.proxy(this._onClick$AddRow, this)},
                {iconCls: "icon-remove", handler: $.proxy(this._onClick$RemoveRow, this)},
            ],
            columns: [[
                    {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: "55%"},
                    {field: "value", title: $("thead th:nth-child(2)", this.data).text(), resizable: false, width: "45%"},
            ]],
            onDblClickRow: function(index) {
                $(this).propertygrid("beginEdit", index);
            },
            onBeginEdit: function(index) {
                var pg = $(this);
                var ed = pg.propertygrid("getEditor", {index: index, field: "value"});
                var input;
                if ($(ed.target).data("textbox")) {
                    input = $(ed.target).textbox("textbox");
                } else if ($(ed.target).data("numberbox")) {
                    input = $(ed.target).numberbox("textbox");
                }
                input && input.on("keydown", function (e) {
                    if (e.keyCode == 13) {
                        pg.propertygrid("endEdit", index);
                    }
                }).focus();
            }
        });
    },

    getData: function() {
        this.data.datagrid("acceptChanges");
        var data = {};
        $.each(this.data.datagrid("getRows"), function(_, row) {
            data[row.name] = row.value;
        });
        return data;
    },

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

    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");
    },

    _onClick$AddRow: function () {
        this._showPromptDialog()
                .done($.proxy(function (data) {
                          var row;
                          switch (data.type) {
                              case "int":
                                  row = {
                                      name: data.name,
                                      value: 0,
                                      editor: {type: "numberbox", options: {required: true}}
                                  };
                                  break;
                              case "boolean":
                                  row = {
                                      name: data.name, value: true, editor: {
                                          type: "combobox",
                                          options: {
                                              editable: false,
                                              data: [{text: "true", value: true}, {text: false, value: false}],
                                              panelHeight: "auto"
                                          }
                                      }
                                  };
                                  break;
                              default:
                                  row = {name: data.name, value: "", editor: {type: "textbox"}};
                          }
                          this.data.propertygrid("appendRow", row)
                                  .propertygrid("selectRow", this.data.propertygrid("getRowIndex", row));
                          this._onChange$Data();
                      }, this));
    },

    _showPromptDialog: function() {
        var deferred = $.Deferred();
        var name, type;
        var dialog = $.messager.prompt({
            title: "",
            content: "<div>Enter a new variable</div><br/>"
                     + "<div class='messager-input'> <input type='text' aria-label='name' class='messager-input' style='width:65%;padding-left:.6em;outline-style:none;border-color:transparent'/>"
                     + "<select aria-label='type' style='outline-style:none;border-color:transparent'>"
                     + "<option value='string'>String</option>"
                     + "<option value='int'>Integer</option>"
                     + "<option value='boolean'>Boolean</option>"
                     + "</select></div>",
            buttons: [
                {
                    text: $.messager.defaults.ok, onClick: function () {
                    dialog.dialog("options").fn.call(this);
                    dialog.dialog("close");
                }},
                {
                    text: $.messager.defaults.cancel, onClick: function () {
                    name = type = null;
                    dialog.dialog("close");
                }},
            ],
            fn: function () {
                name = dialog.find("input[aria-label=name]").val();
                type = dialog.find("select[aria-label=type]").val();
            },
            onDestroy: function () {
                if (name && name.trim().length) {
                    deferred.resolve({name: name, type: type});
                } else {
                    deferred.reject();
                }
                name = type = deferred = null;
            }
        });
        dialog.find(":input:first").focus();
        return deferred.promise();
    },

    _onChange$Data: function() {
        var enabled = this.data.propertygrid("getChanges").length != 0;
        $("#d-triggers-run-okbtn").linkbutton(enabled ? "enable" : "disable");
    },

    _onClick$RemoveRow: function() {
        var index = -1;
        var rows = [].concat(this.data.propertygrid("getSelections"));
        while (rows.length) {
            index = this.data.propertygrid("getRowIndex", rows.pop());
            this.data.propertygrid("deleteRow", index);
        }
        index = Math.min(Math.max(0, index), this.data.propertygrid("getRows").length - 1);
        if (index >= 0) {
            this.data.propertygrid("selectRow", index);
        }
        this._onChange$Data();
    },

    _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.domain.DeleteTriggerController = Class.extend(
    /** @lends {jscape.domain.DeleteTriggerController.prototype} */
{
    deferred: null,

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

    _confirmDelete: function (triggers) {
        var text = $.map(triggers, function (trigger) { return trigger.name; }).join(", ");
        return jscape.ConfirmDialog.confirm(window.R.string.TRIGGERS_CONFIRM_DELETE_TITLE,
                                            window.R.string.TRIGGERS_CONFIRM_DELETE_MESSAGE.supplant({0: text}));
    },

    _deleteConfirmed: function (triggers) {
        $.when.apply(jQuery, $.map(triggers, $.proxy(function (trigger) { return this.managementService.deleteTrigger(this.domainName, trigger.name); }, this)))
                .done($.proxy(function () { this.deferred.resolve(triggers); this.deferred = null; }, this))
                .fail($.proxy(function () { this.deferred.reject(); this.deferred = null; }, this));
    }
});

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

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

    onSubmit: function (dialog) {
        this.managementService.importTriggers(this.domainName, dialog.getFile(), dialog.isOverwrite())
                .done($.proxy(function () {
                          this._showSuccessMessage();
                          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.domain.ImportTriggerDialog();
    },

    _showSuccessMessage: function() {
        $(document).triggerHandler("broadcast", window.R.string.TRIGGERS_SUCCESS_IMPORT);
    }
});

/** @class */
jscape.domain.ImportTriggerDialog = Class.extend(
    /** @lends {jscape.domain.ImportTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-import").clone().attr("id", "");
        this.dialog.dialog({
            width: "50%", 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=triggersimportfields]", this.dialog);

        this.file = $("input[name=file]", this.fieldset).validatebox({required: true}).change(function() { $(this).validatebox("validate"); });
        this.overwrite = $("input[name=overwrite]:checkbox", this.fieldset);
    },

    getFile: function() {
        return this.file[0].files[0];
    },

    isOverwrite: function() {
        return this.overwrite.is(":checked");
    },

    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.domain.ExportTriggerController = Class.extend(
    /** @lends {jscape.domain.ExportTriggerController.prototype} */
{
    deferred: null,

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

    onSubmit: function(dialog) {
        var filename = dialog.getFilename();
        dialog.destroy();
        this.managementService.exportTriggers(this.domainName, this.triggerNames, filename)
                .done($.proxy(function() { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    _setupDialog: function () {
        var dialog = new jscape.domain.ExportTriggerDialog();
        dialog.setFilename("triggers.txt");
        return dialog;
    }
});

/** @class */
jscape.domain.ExportTriggerDialog = Class.extend(
    /** @lends {jscape.domain.ExportTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-export").clone().attr("id", "");
        this.dialog.dialog({
            width: "50%", 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=triggersexportfields]", this.dialog);

        this.filename = $("input[name=filename]", this.fieldset).textbox({required: true, validType: "non_empty", width: "60%"});
    },

    getFilename: function() {
        return this.filename.textbox("getValue");
    },

    setFilename: function(value) {
        this.filename.textbox("setValue", value).textbox("textbox").select().focus();
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.filename.textbox("textbox").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.domain.PromoteTriggerController = Class.extend(
    /** @lends {jscape.domain.PromoteTriggerController.prototype} */
{
    DEFAULT_PORT: 10880,

    deferred: null,

    start: function(managementService, domainName, names) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.triggerNames = names || [];
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var data = this._createData(dialog);
        dialog.destroy();
        this.managementService.promoteTriggers(this.domainName, data)
                .done($.proxy(function() { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this))
    },

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

    _createData: function(dialog) {
        return {
            names: this.triggerNames,
            host: dialog.getHost(),
            port: dialog.getPort(),
            username: dialog.getUsername(),
            password: dialog.getPassword(),
            domain: dialog.getDomain(),
            overwrite: dialog.isOverwrite()
        };
    },

    _setupDialog: function () {
        var dialog = new jscape.domain.PromoteTriggerDialog();
        dialog.setDomain(this.domainName);
        dialog.setPort(this.DEFAULT_PORT);
        return dialog;
    }
});

/** @class */
jscape.domain.PromoteTriggerDialog = Class.extend(
    /** @lends {jscape.domain.ExportTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-promote").clone().attr("id", "");
        this.dialog.dialog({
            width: "50%", 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=triggerspromotefields]", this.dialog);

        this.host = $("input[name=host]", this.fieldset).textbox({required: true, validType: "non_empty", width: "60%"});
        this.port = $("input[name=port]", this.fieldset).numberspinner({required: true, min: 1, max: 65535, width: 80});
        this.domain = $("input[name=domain]", this.fieldset).textbox({required: true, validType: "non_empty", validateOnBlur: true, width: "50%"});
        this.login = $("input[name=login]", this.fieldset).textbox({required: true, validType: "non_empty", validateOnBlur: true, width: "50%"});
        this.password = $("input[name=pwd]", this.fieldset).passwordbox({lastDelay: 0, validateOnBlur: true, width: "50%"});
        this.overwrite = $("input[name=overwrite]:checkbox", this.fieldset);
    },

    getHost: function() {
        return this.host.textbox("getValue");
    },

    getPort: function() {
        return parseInt(this.port.numberspinner("getValue"));
    },

    setPort: function(value) {
        this.port.numberspinner("setValue", parseInt(value));
    },

    getDomain: function() {
        return this.domain.textbox("getValue");
    },

    setDomain: function(value) {
        this.domain.textbox("setValue", value);
    },

    getUsername: function() {
        return this.login.textbox("getValue");
    },

    getPassword: function() {
        return this.password.passwordbox("getValue");
    },

    isOverwrite: function() {
        return this.overwrite.is(":checked");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.host.textbox("textbox").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.domain.RecentTriggersController = Class.extend(
    /** @lends {jscape.domain.RecentTriggersController.prototype} */
{
    view: null,

    onCreate: function (managementService, domainName) {
        this.managementService = managementService;
        this.domainName = domainName;

        this.clearController = new jscape.domain.ClearTriggerStatesController();
        this.viewController = new jscape.domain.ViewTriggerStateController();
        this.rerunController = new jscape.domain.RerunTriggerController();
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.RecentTriggersView();
        }
        this.view.show(this);
    },

    hasChanged: function() {
        return false;
    },

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

    onRefresh: function(params) {
        params = jscape.Page.requestParameters(params);
        return this.managementService.listTriggerStates(this.domainName, params.startIndex, params.count, params.query)
                .then(jscape.Page.datagridFormatter());
    },

    onPurgeTriggerStates: function() {
        return this.clearController.start(this.managementService, this.domainName)
                .done($.proxy(this.onResume, this));
    },

    onViewTriggerState: function(state) {
        return this.viewController.start(state);
    },

    onPauseTrigger: function(state) {
        return this.managementService.pauseTrigger(this.domainName, state.id)
                .done($.proxy(this.onResume, this));
    },

    onResumeTrigger: function(state) {
        return this.managementService.resumeTrigger(this.domainName, state.id)
                .done($.proxy(this.onResume, this));
    },

    onRerunTrigger: function(state) {
        return this.rerunController.start(this.managementService, this.domainName, state.id)
                .done($.proxy(this.onResume, this));
    },

    onKillTrigger: function(state) {
        return this.managementService.killTrigger(this.domainName, state.id);
    }
});

/** @class */
jscape.domain.RecentTriggersView = Class.extend(
    /** @lends {jscape.domain.RecentTriggersView.prototype} */
{
    selection: null,

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

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

        this.data = $("table[role=grid][aria-label=recenttriggers]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            singleSelect: true,
            pagination: true,
            search: true,
            remoteSearch: true,
            remoteSort: true,
            sortName: "eventId",
            sortOrder: "asc",
            columns: [[
                {field: "eventId", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: "12%"},
                {field: "triggerName", title: $("thead th:nth-child(2)", this.data).text(), sortable: true, width: "8%"},
                {field: "status", title: $("thead th:nth-child(3)", this.data).text(), sortable: true, width: "8%", finder: {type: "combobox", options: {data: $.map(jscape.Trigger.Status.values(), $.proxy(function(value) { return {text: this._formatStatus(value), value: value}; }, this)), panelHeight: "auto"}}, formatter: $.proxy(this._formatStatus, this), styler: $.proxy(this._styleStatus, this)},
                {field: "startTime", title: $("thead th:nth-child(4)", this.data).text(), sortable: true, width: "17%", align: "right", formatter: $.proxy(this._formatDate, this), sorter: function(a, b) { return a < b ? -1 : a > b ? 1 : 0; }},
                {field: "endTime", title: $("thead th:nth-child(5)", this.data).text(), width: "17%", align: "right", formatter: $.proxy(this._formatDate, this)},
                {field: "runTime", title: $("thead th:nth-child(6)", this.data).text(), width: "12%", align: "right", searchable: false, formatter: $.proxy(function(_, row) { return this._formatRuntime(row); }, this)},
                {field: "action", title: $("thead th:nth-child(7)", this.data).text(), width: "26%", searchable: false, formatter: $.proxy(function(_, row) { return this._formatAction(row); }, this)}
            ]],
            onSelect: $.proxy(this._adjustRowSelection, this),
            onUnselect: $.proxy(this._adjustRowSelection, this),
            onDblClickRow: $.proxy(this._onClick$ViewButton, this),
            loader: $.proxy(this._onLoad$Data, this),
            onBeforeLoad: function() { return !initializing; },
            onLoadSuccess: $.proxy(this._adjustRowSelection, this)
        });

        this.purgeButton = $("a[role=button][name=purge]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$PurgeButton, this)});
        this.viewButton = $("a[role=button][name=view]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ViewButton, this)});
        this.pauseButton = $("a[role=button][name=pause]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$PauseButton, this)});
        this.resumeButton = $("a[role=button][name=resume]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ResumeButton, this)});
        this.rerunButton = $("a[role=button][name=rerun]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$RerunButton, this)});
        this.killButton = $("a[role=button][name=kill]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$KillButton, this)});

        initializing = false;
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.RecentTriggersView.LISTENER;
        this._adjustRowSelection();
    },

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

    _formatDate: function(value) {
        return $.isNumeric(value) && value != 0 ? new Date(value).toLocaleString() : "";
    },

    _formatRuntime: function (triggerState) {
        var date = $.isNumeric(triggerState.endTime) && triggerState.endTime > 0
                   ? new Date(0, 0, 1, 0, 0, 0, triggerState.runTime)
                   : null;
        return date != null
               ? new jscape.DateFormat((date.getHours() != 0 ? "{h} hr. " : "")
                                       + (date.getMinutes() != 0 ? "{m} min. " : "")
                                       + (date.getSeconds() != 0 ? "{s} sec. " : "")
                                       + "{S} ms.")
                .format(date)
                : "";
    },

    _formatAction: function(triggerState) {
        var names = [];
        var executed = 0;
        $.each(triggerState.actionStates, function(_, actionState) {
            var status = actionState.status;
            if (jscape.ActionState.Status.RUNNING == status) {
                names.push(jscape.domain.TriggersModule.displayClassNameOf(actionState.actionClass));
            } else if (jscape.ActionState.Status.COMPLETED == status) {
                executed++;
            }
        });
        return window.R.string.TRIGGERS_TEMPLATE_COLUMN_ACTION.supplant({0: names.join(", ").escapeXml(), 1: executed, 2: triggerState.actionStates.length});
    },

    _formatStatus: function(status) {
        return window.R.string[("TRIGGERS_STATE_" + status).toUpperCase()] || "";
    },

    _styleStatus: function(status) {
        switch (status) {
            case jscape.Trigger.Status.COMPLETED: return "color:green;font-weight:600";
            case jscape.Trigger.Status.FAILED: return "color:red;font-weight:600";
            case jscape.Trigger.Status.CANCELLED: return "color:gray;font-weight:600";
            case jscape.Trigger.Status.PAUSED: return "color:blue;font-weight:600";
            default: return "color:black";
        }
    },

    _onLoad$Data: function(params, success, error) {
        this.listener.onRefresh(params).done(success).fail(error);
    },

    _onClick$PurgeButton: function() {
        this.purgeButton.linkbutton("disable");
        this.listener.onPurgeTriggerStates()
                .always($.proxy(function() { this.purgeButton.linkbutton("enable"); }, this));
    },

    _onClick$ViewButton: function() {
        this.listener.onViewTriggerState(this.selection[0]);
    },

    _onClick$PauseButton: function() {
        this.listener.onPauseTrigger(this.selection[0]);
    },

    _onClick$ResumeButton: function() {
        this.listener.onResumeTrigger(this.selection[0]);
    },

    _onClick$RerunButton: function() {
        this.listener.onRerunTrigger(this.selection[0]);
    },

    _onClick$KillButton: function() {
        this.listener.onKillTrigger(this.selection[0]);
    },

    _adjustRowSelection: function() {
        this.selection = this.data.datagrid("getSelections");
        this._adjustButtonsState();
    },

    _adjustButtonsState: function() {
        var state = this.selection.length == 1 ? this.selection[0] : null;
        var canPause = state && (state.status == jscape.Trigger.Status.QUEUED || state.status == jscape.Trigger.Status.RUNNING);
        var canResume = state && (state.status == jscape.Trigger.Status.PAUSED);
        var canKill = state && (state.status == jscape.Trigger.Status.QUEUED || state.status == jscape.Trigger.Status.RUNNING || state.status == jscape.Trigger.Status.PAUSED);
        var canRerun = state && (state.status == jscape.Trigger.Status.COMPLETED || state.status == jscape.Trigger.Status.CANCELLED || state.status == jscape.Trigger.Status.FAILED);
        this.viewButton.linkbutton(state ? "enable" : "disable");
        this.pauseButton.linkbutton(canPause ? "enable" : "disable");
        this.resumeButton.linkbutton(canResume ? "enable" : "disable");
        this.killButton.linkbutton(canKill ? "enable" : "disable");
        this.rerunButton.linkbutton(canRerun ? "enable" : "disable");
    }
});
jscape.domain.RecentTriggersView.LISTENER = {
    onRefresh: $.noop,
    onPurgeTriggerStates: $.noop,
    onViewTriggerState: $.noop,
    onPauseTrigger: $.noop,
    onResumeTrigger: $.noop,
    onRerunTrigger: $.noop,
    onKillTrigger: $.noop
};

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

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

    onSubmit: function(dialog) {
        var period = dialog.getExpirationPeriod();
        dialog.destroy();
        this.managementService.clearTriggerStates(this.domainName, period)
                .done($.proxy(function() { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    _setupDialog: function() {
        return new jscape.domain.ClearTriggerStatesDialog();
    }
});

/** @class */
jscape.domain.ClearTriggerStatesDialog = Class.extend(
    /** @lends {jscape.domain.ClearTriggerStatesDialog.prototype} */
{
    TIME_FACTOR: 24 * 60 * 60 * 1000,

    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-trigger-state-clear").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=cleartriggerstatefields]", this.dialog);

        this.period = $("input[name=period]", this.fieldset).numberspinner({required: true, min: 0, max: 1000, value: 0, width: 80});
    },

    getExpirationPeriod: function () {
        return parseInt(this.period.numberspinner("getValue")) * this.TIME_FACTOR;
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").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.domain.ViewTriggerStateController = Class.extend(
    /** @lends {jscape.domain.ViewTriggerStateController.prototype} */
{
    deferred: null,

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

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

    _setupDialog: function(state) {
        var dialog = new jscape.domain.TriggerStateDialog();
        dialog.setData(state);
        return dialog;
    }
});

/** @class */
jscape.domain.TriggerStateDialog = Class.extend(
    /** @lends {jscape.domain.TriggerStateDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-trigger-recent").clone().attr("id", "");
        this.dialog.dialog({
            width: 750, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [{text: window.R.string.DIALOG_BUTTON_CLOSE || "Close", handler: $.proxy(this._onCancel, this)}],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=recenttriggerstatefields]", this.dialog);
        this.eventId = $("code[aria-label=event-id]", this.fieldset);
        this.eventType = $("code[aria-label=event-type]", this.fieldset);
        this.triggerName = $("code[aria-label=trigger-name]", this.fieldset);
        this.status = $("code[aria-label=status]", this.fieldset);
        this.startTime = $("code[aria-label=start-time]", this.fieldset);
        this.endTime = $("code[aria-label=end-time]", this.fieldset);
        this.actionHistory = $("table[role=grid][aria-label=action-history]", this.fieldset);
        this.actionHistory.datagrid({
            fit: true,
            fitColumns: true,
            remoteSort: false,
            columns: [[
                    {field: "actionClass", title: $("thead th:nth-child(1)", this.actionHistory).text(), sortable: true, width: 100, formatter: $.proxy(this._formatAction, this)},
                    {field: "status", title: $("thead th:nth-child(2)", this.actionHistory).text(), sortable: true, width: 80, formatter: $.proxy(this._formatStatus, this), styler: $.proxy(this._styleActionStatus, this)},
                    {field: "startTime", title: $("thead th:nth-child(3)", this.actionHistory).text(), sortable: true, width: 110, align: "right", formatter: $.proxy(this._formatDate, this), sorter: function(a, b) { return a < b ? -1 : a > b ? 1 : 0; }},
                    {field: "endTime", title: $("thead th:nth-child(4)", this.actionHistory).text(), sortable: true, width: 110, align: "right", formatter: $.proxy(this._formatDate, this), sorter: function(a, b) { return a < b ? -1 : a > b ? 1 : 0; }}

            ]]
        });
        this.eventParameters = $("table[role=grid][aria-label=event-parameters]", this.fieldset);
        this.eventParameters.propertygrid({
            fit: true,
            showGroup: false,
            scrollbarSize: 0,
            sortName: "name",
            sortOrder: "asc"
        });
    },

    setData: function(state) {
        this.eventId.text(state.eventId);
        this.eventType.text(jscape.domain.TriggersModule.displayClassNameOf(state.eventClassName));
        this.triggerName.text(state.triggerName);
        this.status.text(this._formatStatus(state.status)).css(this._styleTriggerStatus(state.status));
        this.startTime.text(this._formatDate(state.startTime));
        this.endTime.text(this._formatDate(state.endTime));
        this.endTime.text(this._formatDate(state.endTime));
        this.actionHistory.datagrid("loadData", state.actionStates);
        this.eventParameters.propertygrid("loadData", $.map(state.eventParameters, function(value, key) { return {name: key, value: value}; }));
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").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");
    },

    _formatAction: function(value) {
        return jscape.domain.TriggersModule.displayClassNameOf(value).escapeXml();
    },

    _formatStatus: function(status) {
        return window.R.string[("TRIGGERS_STATE_" + status).toUpperCase()] || "";
    },

    _styleActionStatus: function(status) {
        switch (status) {
            case jscape.ActionState.Status.COMPLETED: return "color:green";
            case jscape.ActionState.Status.FAILED: return "color:red";
            case jscape.ActionState.Status.CANCELLED: return "color:gray";
            case jscape.ActionState.Status.INTERRUPTED: return "color:blue";
            default: return "color:black";
        }
    },

    _styleTriggerStatus: function(status) {
        switch (status) {
            case jscape.Trigger.Status.COMPLETED: return {color: "green"};
            case jscape.Trigger.Status.FAILED: return {color: "red"};
            case jscape.Trigger.Status.CANCELLED: return {color: "gray"};
            case jscape.Trigger.Status.INTERRUPTED: return {color: "blue"};
            default: return {color: "black"};
        }
    },

    _formatDate: function(value) {
        return $.isNumeric(value) && value > 0 ? new Date(value).toLocaleString() : "";
    },

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

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

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

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

    onSubmit: function(dialog) {
        var data = {
            restartAllActions: dialog.isRerunAllActions(),
            maintainEventDate: dialog.isMaintainVariables()
        };
        this.managementService.restartTrigger(this.domainName, this.id, data)
                .done($.proxy(function() { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this))
                .always(function() { dialog.destroy(); });
    },

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

    _setupDialog: function() {
        var dialog = new jscape.domain.RerunTriggerDialog();
        dialog.setRerunAllActions(true);
        return dialog;
    }
});

/** @class */
jscape.domain.RerunTriggerDialog = Class.extend(
    /** @lends {jscape.domain.RerunTriggerDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-rerun").clone().attr("id", "");
        this.dialog.dialog({
            width: 600, 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=reruntriggerfields]", this.dialog);

        this.restartMode = $("input[name=restartmode]:radio", this.fieldset);
        this.maintainVariables = $("input[name=maintainvars]:checkbox", this.fieldset);
    },

    isRerunAllActions: function() {
        return /^all$/i.test(this.restartMode.filter(":checked").val());
    },

    setRerunAllActions: function(value) {
        this.restartMode.filter("[value=all]").prop("checked", !!value).change();
    },

    isMaintainVariables: function() {
        return this.maintainVariables.is(":checked");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").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.domain.TriggersSettingsController = Class.extend(
    /** @lends {jscape.domain.TriggersSettingsController.prototype} */
{
    view: null,
    config: null,
    copy: null,
    variables: null,

    /** @constructor */
    initialize: function() {
        this._initValidators();
    },

    onCreate: function (managementService, domainName) {
        this.managementService = managementService;
        this.domainName = domainName;
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.TriggerSettingsView();
        }
        this.view.show(this);
    },

    hasChanged: function() {
        return this.copy && !Object._equals(this.copy, this._createConfiguration());
    },

    onResume: function() {
        this.managementService.getTriggerServiceConfiguration(this.domainName)
                .done($.proxy(function(config) { this.config = config; }, this))
                .fail($.proxy(function() { this.config = this.DEFAULT_CONFIG; }, this))
                .always($.proxy(function() {
                            this.copy = this._copyConfiguration();
                            this.variables = this._copyVariables(this.config);
                            this._setupView(this.config, this.variables);
                        }, this));
    },

    onApply: function() {
        var config = this._createConfiguration();
        return this.managementService.setTriggerServiceConfiguration(this.domainName, config)
                .done($.proxy(function() {
                    this.config = config;
                    this.copy = this._copyConfiguration();
                    this.variables = this._copyVariables(this.config);
                    this._setupView(this.config, this.variables);
                }, this))
                .done($.proxy(this._fireApplySuccess, this));
    },

    onDiscard: function() {
        this.config = this.copy;
        this.copy = this._copyConfiguration();
        this.variables = this._copyVariables(this.config);
        this._setupView(this.config, this.variables);
    },

    onAddVariable: function() {
        var dialog = new jscape.domain.AddGlobalVariableDialog();
        dialog.show({
            onSubmit: $.proxy(function(d) {
                var name = d.getName();
                var value = d.getValue();
                d.destroy();
                this._variableAdded(name, value);
            }, this),
            onCancel: function(d) { d.destroy(); }
        });
    },

    onDeleteVariable: function(name) {
        jscape.ConfirmDialog.confirm(window.R.string.TRIGGERS_CONFIRM_DELETE_VARIABLE_TITLE,
                                     window.R.string.TRIGGERS_CONFIRM_DELETE_VARIABLE_MESSAGE.supplant({0: name}))
                .done($.proxy(this._variableDeleted, this, name));
    },

    onChangeVariable: function(name, value) {
        for (var i = 0; i < this.variables.length; i++) {
            if (this.variables[i].name == name) {
                this.variables[i].value = value;
                break;
            }
        }
    },

    _variableAdded: function(name, value) {
        var variable = {name: name, value: value};
        this.variables.push(variable);
        this.view.setVariables(this.variables);
        this.view.selectItem(variable);
    },

    _variableDeleted: function (name) {
        var index = -1;
        for (var i = 0; i < this.variables.length; i++) {
            if (this.variables[i].name === name) {
                index = i;
                break;
            }
        }
        if (index != -1) {
            this.variables.splice(index, 1);
        }
        this.view.setVariables(this.variables);

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

    _copyConfiguration: function() {
        return $.extend(new jscape.TriggerServiceConfiguration(), this.config);
    },

    _copyVariables: function(config) {
        return $.map(config && config.globalVariables || {}, function(value, key) { return {name: key, value: value}; });
    },

    _createConfiguration: function() {
        var variables = {};
        $.each(this.variables, function(_, entry) { variables[entry.name] = entry.value; });
        return new jscape.TriggerServiceConfiguration(this.view.getMaxThreadCount(),
                                                      this.view.getMaxEventQueueSize(),
                                                      this.view.getMaxTransferCount(),
                                                      this.view.getRecentTriggerExpirationPeriod(),
                                                      variables);
    },

    _setupView: function (config, variables) {
        if (config) {
            this.view.setMaxThreadCount(config.maxThreadCount);
            this.view.setMaxEventQueueSize(config.maxEventQueueSize);
            this.view.setMaxTransferCount(config.maxTransferCount);
            this.view.setRecentTriggerExpirationPeriod(config.recentTriggerExpirationPeriodMillis);
        }
        this.view.setVariables(variables);
    },

    _fireApplySuccess: function () {
        $(document).triggerHandler("broadcast", [window.R.string.APPLICATION_SUCCESS_APPLY]);
    },

    _initValidators: function() {
        var v = new jscape.Validator();
        v.nonEmptyRule("triggers_variable_nonempty", window.R.string.TRIGGERS_ERROR_BAD_VARIABLE_NAME);
        v.rule("triggers_variable_unique", $.proxy(this._variableNotExists, this), window.R.string.TRIGGERS_ERROR_VARIABLE_EXISTS);
    },

    _variableNotExists: function(value) {
        return $.grep(this.variables, function(entry) { return entry.name == value; }).length == 0;
    }
});

/** @class */
jscape.domain.TriggerSettingsView = Class.extend(
    /** @lends {jscape.domain.TriggerSettingsView.prototype} */
{
    TIME_FACTOR: 24 * 60 * 60 * 1000,

    /** @constructor */
    initialize: function() {
        this.fieldset = $("fieldset[name=triggerssettingsfields]");

        this.limitTriggers = $("input[name=limittriggers]:checkbox", this.fieldset);
        this.limitTriggers.change($.proxy(function() {
            var enabled = this.limitTriggers.is(":checked");
            $.each([this.maxThreads, this.maxEventQueueSize], function(_, input) {
                input.numberspinner(enabled ? "enable" : "disable").numberspinner(enabled ? "enableValidation" : "disableValidation");
            })
        }, this));
        this.maxThreads = $("input[name=maxthreads]", this.fieldset);
        this.maxThreads.numberspinner({required: true, disabled: true, min: 1, max: 1000, value: 50, width: 70});
        this.maxEventQueueSize = $("input[name=triggersqueuesize]", this.fieldset);
        this.maxEventQueueSize.numberspinner({required: true, disabled: true, min: 1, max: 1000, value: 100, width: 70});

        this.limitTransfers = $("input[name=limittransfers]:checkbox", this.fieldset);
        this.limitTransfers.change($.proxy(function() {
            var enabled = this.limitTransfers.is(":checked");
            this.maxTransfers.numberspinner(enabled ? "enable" : "disable").numberspinner(enabled ? "enableValidation" : "disableValidation");
        }, this));
        this.maxTransfers = $("input[name=maxtransfers]", this.fieldset);
        this.maxTransfers.numberspinner({required: true, disabled: true, min: 1, max: 1000, value: 10, width: 70});

        this.recentTriggerExpires = $("input[name=recenttriggerexpires]:checkbox", this.fieldset).change($.proxy(function() {
            var enabled = this.recentTriggerExpires.is(":checked");
            this.recentTriggerExpirationPeriod.numberspinner(enabled ? "enable" : "disable").numberspinner(enabled ? "enableValidation" : "disableValidation");
        }, this));
        this.recentTriggerExpirationPeriod = $("input[name=recenttriggerexpiresperiod]", this.fieldset).numberspinner({required: true, min: 0, max: 100, value: 0, width: 80});

        this.variables = $("table[role=grid][aria-label=variables]", this.fieldset);
        this.variables.propertygrid({
            fit: true,
            fitColumns: true,
            onSelect: $.proxy(this._adjustButtonsState, this),
            onUnselect: $.proxy(this._adjustButtonsState, this),
            onLoadSuccess: $.proxy(this._adjustButtonsState, this),
            onEndEdit: $.proxy(this._onAfterEdit, this),
            columns: [[
                {field: "name", title: $("thead th:nth-child(1)", this.variables).text(), sortable: true, width: 100},
                {field: "value", title: $("thead th:nth-child(2)", this.variables).text(), width: 100, resizable: false}
            ]]
        });

        this.addButton = $("a[role=button][name=add]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$AddButton, this)});
        this.deleteButton = $("a[role=button][name=delete]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$DeleteButton, this)});
        this.applyButton = $("a[role=button][name=apply]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ApplyButton, this)});
        this.discardButton = $("a[role=button][name=discard]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$DiscardButton, this)});
    },

    getMaxThreadCount: function() {
        return this.limitTriggers.is(":checked") ? parseInt(this.maxThreads.numberspinner("getValue")) : null;
    },

    setMaxThreadCount: function(value) {
        this.limitTriggers.prop("checked", value != null).change();
        if (value != null) {
            this.maxThreads.numberspinner("setValue", parseInt(value));
        }
    },

    getMaxEventQueueSize: function() {
        return this.limitTriggers.is(":checked") ? parseInt(this.maxEventQueueSize.numberspinner("getValue")) : null;
    },

    setMaxEventQueueSize: function(value) {
        if (value) {
            this.maxEventQueueSize.numberspinner("setValue", parseInt(value));
        }
    },

    getMaxTransferCount: function() {
        return this.limitTransfers.is(":checked") ? parseInt(this.maxTransfers.numberspinner("getValue")) : null;
    },

    setMaxTransferCount: function(value) {
        this.limitTransfers.prop("checked", value != null).change();
        if (value != null) {
            this.maxTransfers.numberspinner("setValue", parseInt(value));
        }
    },

    getRecentTriggerExpirationPeriod: function() {
        return this.recentTriggerExpires.is(":checked") ? parseInt(this.recentTriggerExpirationPeriod.numberspinner("getValue")) * this.TIME_FACTOR : null;
    },

    setRecentTriggerExpirationPeriod: function(value) {
        if (value != null) {
            this.recentTriggerExpirationPeriod.numberspinner("setValue", parseInt(value) / this.TIME_FACTOR).numberspinner("validate");
        }
        this.recentTriggerExpires.prop("checked", value != null).change();
    },

    setVariables: function(values) {
        var data = $.map(values || [], function(entry) { return $.extend({editor: "text"}, entry); });
        this.variables.propertygrid("loading").propertygrid("loadData", data).propertygrid("loaded");
    },

    selectItem: function(value) {
        this.variables.propertygrid("selectRow", this.variables.propertygrid("getRowIndex", value));
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.TriggerSettingsView.LISTENER;
        this._adjustButtonsState();
    },

    _onClick$AddButton: function() {
        this.listener.onAddVariable();
    },

    _onClick$DeleteButton: function() {
        var name = this.variables.propertygrid("getSelected").name;
        this.listener.onDeleteVariable(name);
    },

    _onAfterEdit: function(index, data, changes) {
        this.listener.onChangeVariable(data.name, changes.value);
    },

    _onClick$ApplyButton: function() {
        this.variables.propertygrid("acceptChanges");

        if (this.fieldset.form("validate")) {
            this.applyButton.linkbutton("disable");
            this.listener.onApply(this)
                    .always($.proxy(function() { this.applyButton.linkbutton("enable"); }, this));
        }
    },

    _onClick$DiscardButton: function() {
        this.variables.propertygrid("rejectChanges");
        this.listener.onDiscard();
    },

    _adjustButtonsState: function() {
        var selections = this.variables.propertygrid("getSelections");
        this.deleteButton.linkbutton(selections.length != 0 ? "enable" : "disable");
    }
});
jscape.domain.TriggerSettingsView.LISTENER = {
    onAddVariable: $.noop,
    onDeleteVariable: $.noop,
    onChangeVariable: $.noop,
    onApply: $.noop,
    onDiscard: $.noop
};

/** @class */
jscape.domain.AddGlobalVariableDialog = Class.extend(
    /** @lends {jscape.domain.AddGlobalVariableDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-triggers-addglobalvar").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", disabled: this.dialog.find("fieldset:first").is(":disabled"), 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=globalvariablesfields]", this.dialog);

        this.name = $("input[name=name]", this.fieldset).validatebox({required: true, validType: ["triggers_variable_nonempty", "triggers_variable_unique"], missingMessage: window.R.string.TRIGGERS_ERROR_BAD_VARIABLE_NAME, width: 170});
        this.value = $("input[name=value]", this.fieldset);
    },

    getName: function() {
        return this.name.val();
    },

    getValue: function() {
        return this.value.val();
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.name.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.domain.RemoteTriggerSettingsController = Class.extend(
    /** @lends {jscape.domain.RemoteTriggerSettingsController.prototype} */
{
    DEFAULT_CONFIG: new jscape.RemoteTriggerServiceConfiguration(false, null, null, null, null, null, null, null, 10000),

    view: null,
    config: null,
    copy: null,
    clientKeyProvider: null,

    /** @constructor */
    initialize: function() {
        this._initValidators();
    },

    onCreate: function (managementService, domainName, triggerContext) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.triggerContext = triggerContext;

        this.clientKeyController = new jscape.domain.ClientKeyController();
        this.testController = new jscape.domain.TestRemoteTriggerServiceController();
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.RemoteTriggerSettingsView();
        }
        this.view.show(this);
    },

    hasChanged: function () {
        return this.copy && !Object._equals(this.copy, this._createConfiguration());
    },

    onResume: function () {
        this.managementService.getRemoteTriggerServiceConfiguration(this.domainName)
                .done($.proxy(function (config) { this.config = config; }, this))
                .fail($.proxy(function () { this.config = this.DEFAULT_CONFIG; }, this))
                .always($.proxy(function () {
                            this.clientKeyProvider = this.config.keyProvider;
                            this.copy = this._copyConfiguration();
                            this._setupView(this.config);
                        }, this));
    },

    onApply: function () {
        var config = this._createConfiguration();
        return this.managementService.setRemoteTriggerServiceConfiguration(this.domainName, config)
                .done($.proxy(function () {
                          this.config = config;
                          this.copy = this._copyConfiguration();
                          this._setupView(this.config);
                      }, this))
                .done($.proxy(this._fireApplySuccess, this));
    },

    onDiscard: function () {
        this.config = this.copy;
        this.copy = this._copyConfiguration();
        this._setupView(this.config);
    },

    onTestServer: function() {
        return this.testController.start(this.managementService, this.domainName, this._createConfiguration());
    },

    onSelectClientKey: function () {
        this.clientKeyController.start(this.managementService, this.domainName, this.clientKeyProvider)
                .done($.proxy(function (provider) { this.clientKeyProvider = provider; }, this));
    },

    _copyConfiguration: function () {
        return $.extend(new jscape.RemoteTriggerServiceConfiguration(), this.config);
    },

    _createConfiguration: function() {
        return new jscape.RemoteTriggerServiceConfiguration(this.view.isEnabled(),
                                                            this.view.getBrokerUrl(),
                                                            this.view.getQueueName(),
                                                            this.view.getUsername(),
                                                            this.view.getPassword(),
                                                            this.clientKeyProvider,
                                                            this.view.getHostKey() != null ? new jscape.ServerKeyProvider(this.view.getHostKey()) : null,
                                                            this.view.getSubscribedEvents(),
                                                            this.view.getMaxQueueSize());
    },

    _setupView: function (config) {
        if (config && this.view) {
            this.view.setEnabled(config.enabled);
            this.view.setBrokerUrl(config.brokerUrl);
            this.view.setQueueName(config.queueName);
            this.view.setUsername(config.username);
            this.view.setPassword(config.password);
            this.view.setMaxQueueSize(config.maxQueueSize);
            this.view.setEvents(this.triggerContext.events);
            this.view.setSubscribedEvents(config.events);
        }

        var hostKeyProvider = config ? config.hostKeyProvider : null;
        this.managementService.getHostCertificates()
                .then(function (certificates) { return $.map(certificates, function (certificate) { return certificate.name; }); })
                .done($.proxy(function (keys) {
                          this.view.setHostKeys(keys);
                          this.view.setHostKey(hostKeyProvider ? hostKeyProvider.keyAlias : null);
                      }, this));
    },

    _fireApplySuccess: function () {
        $(document).triggerHandler("broadcast", [window.R.string.APPLICATION_SUCCESS_APPLY]);
    },

    _initValidators: function() {
        var v = new jscape.Validator();
        v.nonEmptyRule("triggers_broker_url_nonempty", window.R.string.TRIGGERS_ERROR_BAD_BROKER_URL);
        v.nonEmptyRule("triggers_queue_name_nonempty", window.R.string.TRIGGERS_ERROR_BAD_QUEUE_NAME);
    }
});

/** @class */
jscape.domain.RemoteTriggerSettingsView = Class.extend(
    /** @lends {jscape.domain.RemoteTriggerSettingsView.prototype} */
{
    DEFAULT_MAX_QUEUE_SIZE: 10000,

    /** @constructor */
    initialize: function() {
        this.fieldset = $("fieldset[name=triggersremotesettingsfields]");

        this.enabled = $("input[name=enabled]:checkbox", this.fieldset).change($.proxy(this._adjustFieldsState, this));

        this.brokerUrl = $("input[name=brokerurl]", this.fieldset).textbox({required: true, validType: "triggers_broker_url_nonempty", missingMessage: window.R.string.TRIGGERS_ERROR_BAD_BROKER_URL, invalidMessage: window.R.string.TRIGGERS_ERROR_BAD_BROKER_URL, width: "60%"});
        this.brokerUrl.textbox("textbox").on("input", $.proxy(this._adjustUseHostKeyFields, this));

        this.queueName = $("input[name=queuename]", this.fieldset).textbox({required: true, validType: "triggers_queue_name_nonempty", missingMessage: window.R.string.TRIGGERS_ERROR_BAD_QUEUE_NAME, invalidMessage: window.R.string.TRIGGERS_ERROR_BAD_QUEUE_NAME, width: "60%"});
        this.username = $("input[name=username]", this.fieldset).textbox({width: "40%"});
        this.password = $("input[name=password]", this.fieldset).textbox({type: "password", width: "40%"});
        this.maxQueueSize = $("input[name=queuesize]", this.fieldset).numberspinner({required: true, disabled: true, min: 0, max: 10000, value: this.DEFAULT_MAX_QUEUE_SIZE, width: 70});

        this.useHostKey = $("input[name=usehostkey]:checkbox", this.fieldset)
                .change($.proxy(function () { this.hostKey.combobox(this.useHostKey.is(":checked:not(:disabled)") ? "enable" : "disable"); }, this))
                .prop("disabled", true);
        this.hostKey = $("select[name=hostkey]", this.fieldset)
                .combobox({editable: false, disabled: true, width: 170, panelHeight: "auto", onLoadSuccess: $.proxy(function (data) {
                              this._adjustUseHostKeyFields();
                              $.fn.combobox.defaults.onLoadSuccess.apply(this.hostKey, arguments);
                          }, this)});
        this.clientKeyButton = $("a[role=button][name=selectclientkey]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ClientKeyButton, this)});

        this.events = $("table[role=grid][aria-label=events]", this.fieldset);
        this.events.datagrid({
            fit: true,
            fitColumns: true,
            singleSelect: false,
            checkOnSelect: true,
            selectOnCheck: true,
            remoteSort: false,
            sortOrder: "asc",
            idField: "type",
            frozenColumns: [[{field: "_ck", checkbox: true}]],
            columns: [[
                    {field: "name", title: $("thead th:nth-child(1)", this.events).text(), sortable: true, width: 100},
                    {field: "description", title: $("thead th:nth-child(2)", this.events).text(), sortable: true, width: 200}
                    ]],
            onBeforeSelect: $.proxy(this.isEnabled, this),
            onBeforeCheck: $.proxy(this.isEnabled, this),
            onLoadSuccess: $.proxy(this._adjustRowSelectionState, this)
        });

        this.testButton = $("a[role=button][name=testserver]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$TestButton, this)});
        this.applyButton = $("a[role=button][name=apply]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$ApplyButton, this)});
        this.discardButton = $("a[role=button][name=discard]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$DiscardButton, this)});
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.RemoteTriggerSettingsView.LISTENER;
        this.enabled.change();
    },

    isEnabled: function() {
        return this.enabled.is(":checked");
    },

    setEnabled: function(value) {
        this.enabled.prop("checked", !!value).change();
    },

    getBrokerUrl: function() {
        return this.brokerUrl.textbox("getValue");
    },

    setBrokerUrl: function(value) {
        this.brokerUrl.textbox("setValue", value);
    },

    getQueueName: function() {
        return this.queueName.textbox("getValue");
    },

    setQueueName: function(value) {
        this.queueName.textbox("setValue", value);
    },

    getUsername: function() {
        return this.username.textbox("getValue");
    },

    setUsername: function(value) {
        this.username.textbox("setValue", value);
    },

    getPassword: function() {
        return this.password.textbox("getValue");
    },

    setPassword: function(value) {
        this.password.textbox("setValue", value);
    },

    getMaxQueueSize: function() {
        return parseInt(this.maxQueueSize.numberspinner("getValue"));
    },

    setMaxQueueSize: function(value) {
        this.maxQueueSize.numberspinner("setValue", parseInt(value) || this.DEFAULT_MAX_QUEUE_SIZE);
    },

    setHostKeys: function (values) {
        this.hostKey.combobox("loadData", values);
    },

    getHostKey: function () {
        return this.useHostKey.is(":checked:not(:disabled)") ? this.hostKey.combobox("getValue") : null;
    },

    setHostKey: function (value) {
        this.useHostKey.prop("checked", value != null).change();
        if (value != null) {
            this.hostKey.combobox("select", value);
        }
    },

    setEvents: function(values) {
        this.events.datagrid("loadData", values);
    },

    getSubscribedEvents: function() {
        return this.isEnabled() ? $.map(this.events.datagrid("getSelections"), function(event) { return event.type || event; }) : [];
    },

    setSubscribedEvents: function(values) {
        this.events.datagrid("clearSelections");
        for (var i = 0; i < values.length; i++) {
            this.events.datagrid("selectRecord", values[i]);
        }
    },

    _onClick$ClientKeyButton: function () {
        this.listener.onSelectClientKey(this);
    },

    _onClick$TestButton: function () {
        if (this.fieldset.form("validate")) {
            this.testButton.linkbutton("disable");
            this.listener.onTestServer(this)
                    .always($.proxy(function() { this.testButton.linkbutton("enable"); }, this));
        }
    },

    _onClick$ApplyButton: function() {
        if (this.fieldset.form("validate")) {
            this.applyButton.linkbutton("disable");
            this.listener.onApply(this)
                    .always($.proxy(function() { this.applyButton.linkbutton("enable"); }, this));
        }
    },

    _onClick$DiscardButton: function() {
        this.listener.onDiscard(this);
    },

    _adjustUseHostKeyFields: function() {
        var secureBroker = /^ssl:\/\/.+$/i.test(this.brokerUrl.textbox("textbox").val());
        this.useHostKey.prop("disabled", !(this.isEnabled() && secureBroker && this.hostKey.combobox("getData").length != 0)).change();
    },

    _adjustRowSelectionState: function() {
        if (!this.isEnabled()) {
            this.events.datagrid("clearSelections");
        }
        this.events.datagrid("getPanel").find("div.datagrid-header-check input:checkbox")
                .prop("disabled", !this.isEnabled() || this.events.datagrid("getRows").length == 0);
    },

    _adjustFieldsState: function() {
        var enabled = this.isEnabled();
        this.brokerUrl.textbox(enabled ? "enable" : "disable").textbox(enabled ? "enableValidation" : "disableValidation");
        $.each([this.queueName, this.username, this.password], function(_, input) { input.textbox(enabled ? "enable" : "disable"); });
        $.each([this.clientKeyButton, this.testButton], function(_, button) { button.linkbutton(enabled ? "enable" : "disable"); });
        this.maxQueueSize.numberspinner(enabled ? "enable" : "disable").numberspinner(enabled ? "enableValidation" : "disableValidation");
        this._adjustUseHostKeyFields();
        this._adjustRowSelectionState();

        this.fieldset.form("validate");
    }
});
jscape.domain.RemoteTriggerSettingsView.LISTENER = {
    onSelectClientKey: $.noop,
    onTestServer: $.noop,
    onApply: $.noop,
    onDiscard: $.noop
};

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

    start: function(managementService, domainName, config) {
        this.deferred = $.Deferred();
        managementService.testRemoteTriggerServiceConfiguration(domainName, config)
                .done($.proxy(this._testPassed, this))
                .fail($.proxy(this._testFailed, this));
        return this.deferred.promise();
    },

    _testPassed: function () {
        $.messager.alert(window.R.string.TRIGGERS_TEST_REMOTE_SERVICE_TITLE,
                         window.R.string.TRIGGERS_TEST_REMOTE_SERVICE_SUCCESS,
                         "info");
        this.deferred.resolve();
        this.deferred = null;
    },

    _testFailed: function () {
        this.deferred.reject();
        this.deferred = null;
    }
});

/** @class */
jscape.domain.ActionsController = Class.extend(
    /** @lends {jscape.domain.ActionsController.prototype} */
{
    view: null,

    onCreate: function(_a, _b, triggerContext) {
        this.actions = triggerContext.actions || [];
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.ActionsView();
        }
        this.view.setActions(this.actions);
        this.view.show(this);
    },

    hasChanged: function() {
        return false;
    },

    onResume: function() {
    }
});

/** @class */
jscape.domain.ActionsView = Class.extend(
    /** @lends {jscape.domain.ActionsView.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.fieldset = $("fieldset[name=triggeractioncontentsfields]");

        this.data = $("table[role=grid][aria-label=triggeractioncontents]", this.fieldset);
        this.data.datagrid({
            height: "auto",
            fitColumns: true,
            remoteSort: false,
            singleSelect: true,
            sortName: "name",
            columns: [[
                {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: 100},
                {field: "description", title: $("thead th:nth-child(2)", this.data).text(), sortable: false, width: 250, formatter: $.proxy(this._formatDescription, this)}
            ]],
            onLoadSuccess: $.proxy(this._createTooltips, this)
        });
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.ActionsView.LISTENER;
    },

    setActions: function(values) {
        this.data.datagrid("loading").datagrid("loadData", values).datagrid("loaded");
    },

    _formatDescription: function(value, rowData) {
        return '<span class="easyui-tooltip" data-tooltip-title="' + String(rowData.name).escapeXml() + '" title="">' + String(value).escapeXml() + "</span>";
    },

    _createTooltips: function() {
        this.data.datagrid("getPanel")
                .find(".easyui-tooltip")
                .on("mouseenter", function () {
                              $(this).tooltip({
                                  showEvent: "none",
                                  position: "bottom",
                                  content: "<div class=\"tooltip-title\"><code>" + $(this).data("tooltip-title") + "</code></div>" + $(this).text(),
                                  onShow: function () { $(this).tooltip("tip").addClass("tooltip-tip-black"); },
                                  onHide: function () { $(this).tooltip("destroy"); }
                              }).tooltip("show");
                          });
    }
});
jscape.domain.ActionsView.LISTENER = {};

/** @class */
jscape.domain.FunctionsController = Class.extend(
    /** @lends {jscape.domain.FunctionsController.prototype} */
{
    view: null,

    onCreate: function (_a, _b, triggerContext) {
        this.functions = triggerContext.functions || [];
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.FunctionsView();
        }
        this.view.setFunctions(this.functions);
        this.view.show(this);
    },

    hasChanged: function() {
        return false;
    },

    onResume: function() {
    }
});

/** @class */
jscape.domain.FunctionsView = Class.extend(
    /** @lends {jscape.domain.FunctionsView.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.fieldset = $("fieldset[name=triggerfunctioncontentsfields]");
        this.data = $("table[role=grid][aria-label=triggerfunctioncontents]", this.fieldset);
        this.data.datagrid({
            height: "auto",
            fitColumns: true,
            remoteSort: false,
            singleSelect: true,
            sortName: "name",
            columns: [[
                {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: 100, formatter: $.proxy(this._formatName, this)},
                {field: "syntax", title: $("thead th:nth-child(2)", this.data).text(), width: 150, formatter: $.proxy(this._formatSyntax, this)},
                {field: "description", title: $("thead th:nth-child(3)", this.data).text(), width: 250, formatter: $.proxy(this._formatDescription, this)}
            ]],
            onLoadSuccess: $.proxy(this._createTooltips, this)
        });
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.FunctionsView.LISTENER;
    },

    setFunctions: function(values) {
        this.data.datagrid("loading").datagrid("loadData", values).datagrid("loaded");
    },

    _formatName: function(name) {
        return name.replace(/([a-z])([A-Z])/g, "$1 $2").escapeXml();
    },

    _formatSyntax: function(value, rowData) {
        return $.map(rowData.templates, function(value, key) { return "<code>" + String(key).escapeXml() + "</code>"; }).join("<br/>")
    },

    _formatDescription: function(value, rowData) {
        return $.map(rowData.templates, function(value, key) {
            return '<span class="easyui-tooltip" data-tooltip-title="' + String(key).escapeXml() + '" title="">' + String(value).escapeXml() + "</span>";
        }).join("<br/>");
    },

    _createTooltips: function() {
        this.data.datagrid("getPanel")
                .find(".easyui-tooltip")
                .off(".triggerfunctiontip")
                .on("mouseenter.triggerfunctiontip", function () {
                              $(this).tooltip({
                                  showEvent: "none",
                                  position: "bottom",
                                  content: "<div class=\"tooltip-title\"><code>" + $(this).data("tooltip-title") + "</code></div>" + $(this).text(),
                                  onShow: function () { $(this).tooltip("tip").addClass("tooltip-tip-black"); },
                                  onHide: function () { $(this).tooltip("destroy"); }
                              }).tooltip("show");
                          });
    }
});
jscape.domain.FunctionsView.LISTENER = {};