/** JSCAPE namespaces. */
var jscape = jscape || {};
jscape.ui = jscape.ui || {};
jscape.view = jscape.view || {};

/**Class inheritance */
(function() {
    var fnTest = /xyz/.test(function() {xyz;}) ? /\b_super\b/ : /.*/;
    var initializing = false;
    this.Class = function() {};
    Class.extend = function(prop) {
        var _super = this.prototype;
        initializing = true;
        var prototype = new this();
        initializing = false;

        for (var name in prop) {
            var overwriting = typeof prop[name] === "function"
                              && typeof _super[name] === "function"
                              && fnTest.test(prop[name]);
            prototype[name] = overwriting
                              ? (function(name, fn) {
                                    return function() {
                                        var ref = this._super;
                                        this._super = _super[name];
                                        var result = fn.apply(this, arguments);
                                        this._super = ref;
                                        return result;
                                    }
                                })(name, prop[name])
                              : prop[name];
        }
        function Class() {
            if (!initializing && this.initialize) {
                this.initialize.apply(this, arguments);
            }
        }
        Class.prototype = prototype;
        Class.prototype.constructor = Class;
        Class.extend = arguments.callee;
        return Class;
    };
})();

String.prototype.trim = function() {
    return this.replace(/(^\s+)|(\s+$)/g, "");
};
String.prototype.escapeXml = function() {
    var entities = {"<": "&lt;", ">": "&gt;", "&": "&amp;", '"': "&quot;"};
    return this.replace(/[<>&"]/g, function(entry) { return entities[entry]});
};
String.prototype.supplant = function(substitution) {
    return this.replace(/\{([^{}]*)\}/g, function (value, variable) {
                            var replacement = substitution[variable];
                            return typeof replacement === "string" || typeof replacement === "number" || typeof replacement === "boolean" ? replacement : value;
                        }
            );
};

(function() {
    function support() { try { var n = 0; n.toLocaleString("i"); } catch (e) { return "RangeError" === e.name; } return false; }
    if (!support()) {
        Number.prototype.toLocaleString = function(_, opts) {
            var n = (opts && opts.maximumFractionDigits ? this.toFixed(opts.maximumFractionDigits) : this.toString()).split(".");
            var s = parseInt(this, 10).toString().replace(/\d/g, function(d, i, s) { return (i && (s.length - i) % 3 == 0 ? "," : "") + d; });
            return n.length > 1 ? s + "." + n[1] : s;
        };
    }
})();
Object._equals = function (o1, o2) {
    if (o1 === o2) {
        return true;
    }
    if (o1 === null || $.type(o1) !== $.type(o2)) {
        return false;
    }
    if (o1 !== o1 && o2 !== o2) { // NaN comparison
        return true;
    }

    var type = $.type(o1);
    if (type === "date") {
        return o1.getTime() == o2.getTime();
    } else if (type === "regexp") {
        return o1.toString() == o2.toString();
    } else if (type === "array") {
        if (o1.length != o2.length) {
            return false;
        }
        for (var i = 0; i < o1.length; i++) {
            if (!Object._equals(o1[i], o2[i])) {
                return false;
            }
        }
        return true;
    } else if (type === "object") {
        var prop;
        for (prop in o1) {
            if (o1.hasOwnProperty(prop) && (o2[prop] === undefined || !Object._equals(o1[prop], o2[prop]))) {
                return false;
            }
        }
        for (prop in o2) {
            if (o2.hasOwnProperty(prop) && o1[prop] === undefined) {
                return false;
            }
        }
        return true;
    }
    return false;
};


/** @class */
jscape.ConfirmDialog = {};
jscape.ConfirmDialog.confirm = function(title, message, escape) {
    var deferred = $.Deferred();
    $.messager.confirm({
        title: title,
        msg: message,
        fn: function (confirmed) {
            if (confirmed) {
                deferred.resolve(confirmed);
            } else {
                deferred.reject();
            }
            deferred = null;
        },
        escapeXml: escape !== false
    });
    return deferred.promise();
};

/** @class */
jscape.view.WizardDialog = Class.extend(
    /** @lends {jscape.view.WizardDialog.prototype} */
{
    index: -1,
    items: null,

    /** @constructor */
    initialize: function(dialog, options) {
        this.items = [];
        this.dialog = dialog.clone().attr("id", "");
        this.dialog.dialog($.extend({
            width: 700, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {id: "wizard-btn-back", text: window.R.string.DIALOG_BUTTON_BACK || "< Back", handler: $.proxy(this._onBack, this), disabled: true},
                {id: "wizard-btn-next", text: window.R.string.DIALOG_BUTTON_NEXT || "Next >", handler: $.proxy(this._onNext, this)},
                {id: "wizard-btn-submit", 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)
        }, options || {})).show();

        this.title = this.dialog.dialog("header").text() || "";

        var container = this.dialog.dialog("dialog");
        this.backButton = container.find("#wizard-btn-back");
        this.nextButton = container.find("#wizard-btn-next");
        this.okButton = container.find("#wizard-btn-submit");
    },

    container: function() {
        return this.dialog;
    },

    addItem: function(item) {
        this.items.push(item);
    },

    getItem: function(index) {
        return index >= 0 && index < this.items.length ? this.items[index] : null;
    },

    getCurrentItem: function() {
        return this.index;
    },

    setCurrentItem: function (index) {
        this.index = Math.max(0, Math.min(index, this.items.length - 1));

        for (var i = 0; i < this.items.length; i++) {
            var item = this.items[i];
            if (i == this.index) {
                item.resume();
                this.listener.onPageSelected(this);
                setTimeout($.proxy(function() { this.dialog.dialog("resize"); }, this), 50);
            } else {
                item.pause();
            }
        }
        this._adjustDialogTitle();
        this._adjustButtonsState();
    },

    show: function (listener) {
        this.listener = listener || jscape.view.WizardDialog.LISTENER;
        this.setCurrentItem(0);
        this.dialog.dialog("center").dialog("open").find("input[type=text]:visible:not(:disabled)").first().focus();
    },

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

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

    _onBack: function() {
        this.setCurrentItem(this.index - 1);
    },

    _onNext: function() {
        if (this.items[this.index].valid()) {
            this.setCurrentItem(this.index + 1);
        }
    },

    _onSubmit: function () {
        var valid = true;
        for (var i = 0; i < this.items.length; i++) {
            valid &= this.items[i].valid();
        }
        if (valid) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

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

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

    _adjustDialogTitle: function() {
        this.dialog.dialog("setTitle", this.title.supplant({0: this.index + 1, 1: this.items.length}));
    },

    _adjustButtonsState: function() {
        this.backButton.linkbutton(this.index > 0 ? "enable" : "disable");
        this.nextButton.linkbutton(this.index < this.items.length - 1 ? "enable" : "disable");
        this.okButton.linkbutton(this.index == this.items.length - 1 ? "enable" : "disable");

        if (this.index == this.items.length - 1) {
            this.nextButton.hide();
            this.okButton.show();
        } else {
            this.nextButton.show();
            this.okButton.hide();
        }
    }
});
jscape.view.WizardDialog.LISTENER = {
    onPageSelected: $.noop,
    onSubmit: $.noop,
    onCancel: $.noop
};

/** @class */
jscape.view.WizardDialog.Page = Class.extend(
    /** @lends {jscape.view.WizardDialog.Page.prototype} */
{
    pause: function() {
    },

    resume: function() {
    },

    valid: function() {
        return true;
    }
});

/** @class */
jscape.view.ListView = Class.extend(
    /** @lends {jscape.view.ListView.prototype} */
{
    items: null,
    currentKey: null,

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

    addItem: function(key, value) {
        this.items[key] = value;
    },

    getItem: function(key) {
        return this.items.hasOwnProperty(key) ? this.items[key] : null;
    },

    getCurrentItem: function() {
        return this.currentKey;
    },

    setCurrentItem: function (key) {
        this.currentKey = null;
        $.each(this.items, $.proxy(function (prop, item) {
            var t = $.type(item);
            if (prop === key) {
                if (t === "function") {
                    this.items[prop] = this._createItem(item);
                } else if (t === "object") {
                    item.resume();
                }
                this.currentKey = key;
            } else {
                if (t === "object") {
                    item.pause();
                }
            }
        }, this));
    },

    _createItem: function(item) {
        return item;
    }
});

/** @class */
jscape.AutoCompleteController = Class.extend(
    /** @lends {jscape.AutoCompleteController.prototype} */
{
    ID: "autocomplete-list",

    list: null,

    /** @constructor */
    initialize: function() {
        this.list = $("#" + this.ID);
    },

    start: function (input, suggestions) {
        this.input = input;
        this._create(this._formatData(suggestions))
                .combobox("showPanel")
                .combobox("clear")
                .combobox("textbox").focus();
    },

    _formatData: function(items) {
        return $.map(items || [], function(item) {
            return typeof item == "object"
                   ? item.hasOwnProperty("description")
                      ? {value: item.value || item.text, text: item.text, title: item.description}
                      : {value: item.value || item.text, text: item.text}
                   : {value: item, text: item};
        });
    },

    _create: function (items) {
        if (this.list == null || this.list.length == 0) {
            this.list = $("<select id='" + this.ID + "'></select>");
            this.list.appendTo(document.body);
        }
        this.list.combobox({
            data: items,
            editable: true,
            hasDownArrow: false,
            width: 250,
            delay: 0,
            filter: function(q, row) { return row[$(this).combobox("options").valueField].toLocaleLowerCase().indexOf(q.toLocaleLowerCase()) >= 0; },
            formatter: $.proxy(this._formatRow, this),
            onLoadSuccess: $.proxy(this._createTooltips, this),
            onHidePanel: $.proxy(this._onHide$Panel, this)
        });
        this._align();
        return this.list;
    },

    _align: function () {
        var offset = this.input.offset();
        var left = Math.max(0, Math.min(offset.left, $(window).outerWidth() + $(document).scrollLeft() - this.list.outerWidth() - 5));
        var top = Math.max(0, Math.min(offset.top + this.input.outerHeight(), $(window).outerHeight() + $(document).scrollTop() - this.list.outerHeight() - 5));
        this.list.combobox("textbox").parent().css({position: "absolute", left: left, top: top, zIndex: $.fn.window.defaults.zIndex++});
    },

    _onHide$Panel: function (e) {
        var value = this.list.combobox("getValue");
        if (value) {
            this._insertText(this.input, value);
        }
        this.input.focus();
        this.list.combobox("destroy");
        this.input = null;
        this.list = null;
    },

    _formatRow: function (row) {
        var text = ("" + row.text).escapeXml();
        return row.title ? '<span aria-label="{tip}">{text}</span>'.supplant({tip: row.title.escapeXml(), text: text}) : text;
    },

    _createTooltips: function () {
        this.list.combobox("panel")
                .find(".combobox-item")
                .each(function () {
                          var tip = $(this).find("span[aria-label]").attr("aria-label") || "";
                          if (tip) {
                              $(this).off(".tip")
                                      .on("mouseenter.tip", function () {
                                              var t = $(this);
                                              t.tooltip({
                                                  content: "<div class=\"tooltip-title\"><code>" + t.text() + "</code></div>" + tip,
                                                  position: "right",
                                                  deltaX: t.parent().width() - t.width(),
                                                  showEvent: "none",
                                                  onShow: function () {
                                                      $(this).tooltip("tip").addClass("tooltip-tip-black");
                                                      $(this).tooltip("reposition");
                                                  },
                                                  onHide: function () { $(this).tooltip("destroy"); }
                                              }).tooltip("show");
                                          })
                                      .on("mouseleave.tip", function () { $(this).tooltip("hide"); });
                          }
                      });
    },

    _insertText: function (input, text) {
        var value = input.val();
        var caret = this._caret(input);
        text = value.substring(0, caret.start) + text + value.substring(caret.end);
        input.focus(); //focus to setup value for textbox component
        input.val(text);
        input.blur(); // fire blur event to invoke $.numberbox("fix") method;
        // assign to .selectionStart property fire focus event in Chrome
        input[0].selectionStart = input[0].selectionEnd = input[0].selectionStart + value.length;
        input.change();
    },

    _caret: function (input) {
        var caret = {start: 0, end: 0};
        if (document.selection && document.selection.createRange) {
            var r1, r2, selection = document.selection;
            if ("textarea" == input[0].tagName.toLowerCase()) {
                r1 = selection.createRange();
                r2 = r1.duplicate();
                r2.moveToElementText(input[0]);
                r2.setEndPoint("EndToEnd", r1);
                caret.start = r2.text.length - r1.text.length;
                caret.end = caret.start + r1.text.length;
            } else {
                var value = input.val();
                r1 = selection.createRange().duplicate();
                r2 = selection.createRange().duplicate();
                r1.moveEnd("character", value.length);
                r2.moveStart("character", -value.length);
                caret.start = r1.text.length ? value.lastIndexOf(r1.text) : value.length;
                caret.end = r2.text.length;
            }
        } else {
            caret.start = input[0].selectionStart;
            caret.end = input[0].selectionEnd;
        }
        return caret;
    }
});

/** @class */
jscape.VariableController = jscape.AutoCompleteController.extend(
    /** @lends {jscape.VariableController.prototype} */
{
    /** @override */
    _insertText: function(input, value) {
        if (this._nestedContext(input)) {
            this._super(input, value);
        } else {
            this._super(input, "%" + value + "%");
        }
    },

    _nestedContext: function(input) {
        var text = input.val().substring(0, this._caret(input).start);
        var count = 0;
        for (var i = 0; i < text.length; i++) {
            if (text.charAt(i) === "%") {
                count++;
            }
        }
        return count % 2 != 0;
    }
});

/** @class */
jscape.FreemarkerVariableController = jscape.AutoCompleteController.extend(
    /** @lends {jscape.FreemarkerVariableController.prototype} */
{
    /** @override */
    _insertText: function (input, value) {
        this._super(input, "${" + value + "}");
    }
});

/** @class */
jscape.Hours = Class.extend({
    /** @const */
    SECONDS_PER_MINUTE: 60,
    MINUTES_PER_HOUR: 60,
    MILLISECONDS_PER_SECOND: 1000,
    MILLISECONDS_PER_MINUTE: 1000 * 60,
    MILLISECONDS_PER_HOUR: 1000 * 60 * 60,

    /** @lends {jscape.Hours.prototype} */
    initialize: function(value) {
        this.hours = value / this.MILLISECONDS_PER_HOUR | 0;
        value %= this.MILLISECONDS_PER_HOUR;
        this.minutes = value / this.MILLISECONDS_PER_MINUTE | 0;
        value %= this.MILLISECONDS_PER_MINUTE;
        this.seconds = value / this.MILLISECONDS_PER_SECOND | 0;
        this.millis = value % this.MILLISECONDS_PER_SECOND | 0;
    }
});

/** @class */
jscape.SizeFormat = Class.extend(
    /** @lends {jscape.SizeFormat.prototype} */
{
    /** @const */
    SIZE: {
        BYTE: {factor: 1, unit: "byte(s)"},
        KB: {factor: 1024, unit: "KiB"},
        MB: {factor: 1024 * 1024, unit: "MiB"},
        GB: {factor: 1024 * 1024 * 1024, unit: "GiB"},
        TB: {factor: 1024 * 1024 * 1024 * 1024, unit: "TiB"}
    },

    format: function(value) {
        var size;
        if (value < this.SIZE.KB.factor) {
            size = this.SIZE.BYTE;
        } else if (this.SIZE.KB.factor <= value && value < this.SIZE.MB.factor) {
            size = this.SIZE.KB;
        } else if (this.SIZE.MB.factor <= value && value < this.SIZE.GB.factor) {
            size = this.SIZE.MB;
        } else if (this.SIZE.GB.factor <= value && value < this.SIZE.TB.factor) {
            size = this.SIZE.GB;
        } else {
            size = this.SIZE.TB;
        }
        var digits = size == this.SIZE.BYTE ? 0 : 2;
        return (value / size.factor).toFixed(digits) + " " + size.unit;
    }
});
jscape.SizeFormat.format = function(value) {
    return new jscape.SizeFormat().format(value);
};

/** @class */
jscape.DateFormat = Class.extend(
    /** @lends {jscape.DateFormat.prototype} */
{
    /** @constructor */
    initialize: function(pattern) {
        this.pattern = pattern || "{hh}:{mm}:{ss} {MM}-{dd}-{yy}";
    },

    format: function(date) {
        var year = date.getFullYear();
        var month = date.getMonth() + 1;
        var day = date.getDate();
        var hours = date.getHours();
        var minutes = date.getMinutes();
        var seconds = date.getSeconds();
        var millis = date.getMilliseconds();

        return this.pattern.supplant({
            d: day, dd: this._leadingZero(day),
            M: month, MM: this._leadingZero(month),
            y: year, yy: this._leadingZero(year),
            h: hours, hh: this._leadingZero(hours),
            m: minutes, mm: this._leadingZero(minutes),
            s: seconds, ss: this._leadingZero(seconds),
            S: millis, SS: this._leadingZero(millis)
        });
    },
    _leadingZero: function(value) {
        return (value < 10 ? "0" : "") + value;
    }
});
jscape.DateFormat.format = function(value, pattern) {
    return value ? new jscape.DateFormat(pattern).format(typeof value === "number" ? new Date(value) : value) : "";
};

/** @class */
jscape.Validator = Class.extend(
    /** @lends {jscape.Validator.prototype} */
{
    rule: function(key, validator, message) {
        var rules = {};
        rules[key] = {validator: validator, message: message || "Please provide valid data."};
        $.extend($.fn.validatebox.defaults.rules, rules);
    },

    ruleMessage: function (key, message) {
        var rule = $.fn.validatebox.defaults.rules[key];
        if (rule) {
            if (arguments.length == 1) {
                return rule.message;
            }
            rule.message = message;
        }
    },

    nonEmptyRule: function(key, message) {
        this.rule(key, function(value) { return value.trim().length != 0; }, message);
    },

    isSatisfied: function(key, args) {
        var rule = $.fn.validatebox.defaults.rules[key];
        return rule && rule.validator
               ? rule.validator.apply(rule.validator, Array.prototype.slice.call(arguments, 1))
               : true;
    },

    validateTabs: function(tabs) {
        var fields = tabs.find(".validatebox-text:not(:disabled)");
        for (var i = 0; i < fields.length; i++) {
            var field = $(fields[i]);
            if (!field.validatebox("isValid")) {
                this._navigateFrom(field);
                field.focus();
                return false;
            }
        }
        return true;
    },

    _navigateFrom: function (source) {
        source.parents(".tabs-container").each($.proxy(function (_, elem) {
            var tabs = $(elem);
            var tabIndex = -1;
            $.each(tabs.tabs("tabs"), function (i, target) {
                if (target.has(source).length != 0) {
                    tabIndex = i;
                    return false;
                }
                return true;
            });
            if (tabIndex != -1) {
                tabs.tabs("select", tabIndex);
            }
            if (this.tabs != tabs) {
                this._navigateFrom(tabs);
            }
        }, this));
    }
});

$.fn._unitValueFor = function (factorInput, value) {
    var unit = 1;
    if (factorInput && factorInput.length) {
        var opts = factorInput.combobox("options");
        var units = $.map(factorInput.combobox("getData"), function (entry) { return parseInt(entry[opts.valueField]); });
        units.sort(function (a, b) { return a - b; });
        while (units.length) {
            unit = units.pop();
            if (value % unit == 0) {
                break;
            }
        }
    }
    return unit;
};

/** @class */
jscape.ui.DatagridController = Class.extend(
    /** @lends {jscape.ui.DatagridController.prototype} */
{
    suppressMessages: false,

    initialize: function(suppressMessages) {
        this.suppressMessages = suppressMessages != undefined ? !!suppressMessages : false;
    },

    _recordAdded: function (record) {
        this._fireApplySuccess();
        this._reloadAndSelect(record);
    },

    _recordEdited: function (record) {
        this._fireApplySuccess();
        this._reloadAndSelect(record);
    },

    _recordDeleted: function (records) {
        var copy = [].concat(records);
        var index = -1;
        while (copy.length) {
            index = this.view.indexOf(copy.shift());
            this.view.deleteRecord(index);
        }
        var rows = this.view.getRecords();
        if (rows.length > 0) {
            index = Math.min(Math.max(0, index), rows.length - 1);
            this.view.selectRecord(rows[index]);
        } else {
            this._gotoPreviousPage();
        }
    },

    _reloadAndSelect: function (record) {
        this.view.refresh($.proxy(function () {
            record && this.view.selectRecord(record);
        }, this));
    },

    _gotoPreviousPage: function () {
        this.view.previousPage($.proxy(function () {
            var records = this.view.getRecords();
            records.length && this.view.selectRecord(records[records.length - 1]);
        }, this));
    },

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

/** @class */
jscape.ui.DatagridView = Class.extend(
    /** @lends {jscape.ui.DatagridView.prototype} */
{
    refresh: function (callback) {
        this.data.datagrid("reload", callback);
    },

    previousPage: function (callback) {
        var pageNumber = this.data.datagrid("options").pageNumber;
        if (pageNumber > 1 && this.data.datagrid("getData").total > 0) {
            this.data.datagrid("gotoPage", {page: --pageNumber, callback: callback});
        } else {
            this.data.datagrid("reload", callback);
        }
    },

    nextPage: function (callback) {
        var opts = this.data.datagrid("options");
        var total = this.data.datagrid("getData").total;
        if (opts.pageNumber > 0 && total > 0 && Math.ceil(total/opts.pageSize) > opts.pageNumber) {
            this.data.datagrid("gotoPage", {page: opts.pageNumber + 1, callback: callback});
        } else {
            this.data.datagrid("reload", callback);
        }
    },

    getRecords: function() {
        return this.data.datagrid("getRows");
    },

    deleteRecord: function(value) {
        var index = isNaN(parseInt(value)) ? this.indexOf(value) : value;
        this.data.datagrid("deleteRow", index);
    },

    indexOf: function (value) {
        var idField = this.data.datagrid("options").idField;
        value = value && typeof value == "object" && idField in value ? value[idField] : value;
        return this.data.datagrid("getRowIndex", value);
    },

    selectRecord: function (value) {
        var index = this.indexOf(value);
        if (index != -1) {
            if (!this.data.datagrid("options").singleSelect) {
                this.data.datagrid("clearSelections");
            }
            this.data.datagrid("selectRow", index);
        }
    }
});