jscape.ui = jscape.ui || {};

/** @class */
jscape.ui.DatagridController = Class.extend(
    /** @lends {jscape.ui.DatagridController.prototype} */
{
    _recordAdded: function (record) {
        this._fireApplySuccess();
        this._reloadAndSelect(record);
    },

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

    _recordDeleted: function (records) {
        var copy = [].concat(this.view.getRecords());
        var index = -1;
        while (records.length) {
            index = $.inArray(records.shift(), copy);
            copy.splice(index, 1);
        }
        if (copy.length) {
            index = Math.min(Math.max(0, index), copy.length - 1);
            this._reloadAndSelect(copy[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 () {
        $(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");
    },

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

/** @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;
        suggestions = this._formatSuggestions(suggestions);
        this._create(suggestions).combobox("showPanel").combobox("clear").combobox("textbox").focus();
    },

    _formatSuggestions: 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: function(row) { var text = ("" + row.text).escapeXml(); return row.title ? '<span title="{tip}">{text}</span>'.supplant({tip: row.title.escapeXml(), text: text}) : text},
            onHidePanel: $.proxy(this._onHide, 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: function () {
        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;
    },

    _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.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.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, value) {
        var rule = $.fn.validatebox.defaults.rules[key];
        if (rule && $.isFunction(rule.validator)) {
            var params = arguments.length > 1 ? Array.prototype.slice.call(arguments, 2) : [];
            return rule.validator.call(rule.validator, value, params);
        }
        return 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));
    }
});

/** @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.Page = Class.extend(
    /** @lends {jscape.Page.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c) {
        this.records = $.isArray(a) ? a : [];
        this.recordIndex = parseInt(b) || 0;
        this.totalRecords = parseInt(c) || this.records.length;
    }
});
jscape.Page.fromJSON = function(data, recordsProvider) {
    return $.isPlainObject(data)
            ? new jscape.Page($.map(data.records || [], recordsProvider), data.recordIndex, data.totalRecords)
            : new jscape.Page($.map($.isArray(data) ? data : [], recordsProvider));
};
jscape.Page.requestParameters = function(params) {
    function parametrize(value) {
        return value ? value.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase() : value;
    }
    var startIndex, count, query;
    if ($.isPlainObject(params)) {
        startIndex = params.page ? (params.page - 1) * params.rows : null;
        count = params.rows || null;
        query = $.extend({}, params);
        delete query.page;
        delete query.rows;
        query.sort = parametrize(query.sort);
        query.order = parametrize(query.order);
        query.field = parametrize(query.field);
    }
    return {startIndex: startIndex, count: count, query: query};
};
jscape.Page.datagridFormatter = function () {
    return function (page) {
        return $.isArray(page) ? {total: page.length, rows: page} : {total: page.totalRecords, rows: page.records};
    }
};