// core.js가 로드되지 않았을 경우
if (typeof(window.jui) != "object") {
    (function (window, nodeGlobal) {
        var global = {
                jquery: (typeof(jQuery) != "undefined") ? jQuery : null
            },
            globalFunc = {},
            globalClass = {};

        /**
         * @class util.base
         *
         * jui 에서 공통적으로 사용하는 유틸리티 함수 모음
         *
         * ```
         * var _ = jui.include("util.base");
         *
         * console.log(_.browser.webkit);
         * ```
         *
         * @singleton
         */
        var utility = global["util.base"] = {

            /**
             * @property browser check browser agent
             * @property {Boolean} browser.webkit  Webkit 브라우저 체크
             * @property {Boolean} browser.mozilla  Mozilla 브라우저 체크
             * @property {Boolean} browser.msie  IE 브라우저 체크 */
            browser: {
                webkit: ('WebkitAppearance' in document.documentElement.style) ? true : false,
                mozilla: (typeof window.mozInnerScreenX != "undefined") ? true : false,
                msie: (window.navigator.userAgent.indexOf("Trident") != -1) ? true : false
            },

            /**
             * @property {Boolean} isTouch
             * check touch device
             */
            isTouch: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent),

            /**
             * @method inherit
             *
             * 프로토타입 기반의 상속 제공
             *
             * @param {Function} ctor base Class
             * @param {Function} superCtor super Class
             */
            inherit: function (ctor, superCtor) {
                if (!this.typeCheck("function", ctor) || !this.typeCheck("function", superCtor)) return;

                ctor.parent = superCtor;
                ctor.prototype = new superCtor;
                ctor.prototype.constructor = ctor;
                ctor.prototype.parent = ctor.prototype;

                /**
                 * @method super
                 * call parent method
                 * @param {String} method  parent method name
                 * @param {Array} args
                 * @returns {Mixed}
                 */
                ctor.prototype.super = function (method, args) {
                    return this.constructor.prototype[method].apply(this, args);
                }
            },

            /**
             * @method extend
             *
             * implements object extend
             *
             * @param {Object|Function} origin
             * @param {Object|Function} add
             * @param {Boolean} skip
             * @return {Object}
             */
            extend: function (origin, add, skip) {
                if (!this.typeCheck(["object", "function"], origin)) origin = {};
                if (!this.typeCheck(["object", "function"], add)) return origin;

                for (var key in add) {
                    if (skip === true) {
                        if (isRecursive(origin[key])) {
                            this.extend(origin[key], add[key], skip);
                        } else if (this.typeCheck("undefined", origin[key])) {
                            origin[key] = add[key];
                        }
                    } else {
                        if (isRecursive(origin[key])) {
                            this.extend(origin[key], add[key], skip);
                        } else {
                            origin[key] = add[key];
                        }
                    }
                }

                function isRecursive(value) {
                    return utility.typeCheck("object", value);
                }

                return origin;
            },

            /**
             * convert px to integer
             * @param {String or Number} px
             * @return {Number}
             */
            pxToInt: function (px) {
                if (this.typeCheck("string", px) && px.indexOf("px") != -1) {
                    return parseInt(px.split("px").join(""));
                }

                return px;
            },

            /**
             * @method clone
             * implements object clone
             * @param {Array/Object} obj 복사할 객체
             * @return {Array}
             */
            clone: function (obj) {
                var clone = (this.typeCheck("array", obj)) ? [] : {};

                for (var i in obj) {
                    if (this.typeCheck("object", obj[i]))
                        clone[i] = this.clone(obj[i]);
                    else
                        clone[i] = obj[i];
                }

                return clone;
            },

            /**
             * @method deepClone
             * implements object deep clone
             * @param obj
             * @param emit
             * @return {*}
             */
            deepClone: function (obj, emit) {
                var value = null;
                emit = emit || {};

                if (this.typeCheck("array", obj)) {
                    value = new Array(obj.length);

                    for (var i = 0, len = obj.length; i < len; i++) {
                        value[i] = this.deepClone(obj[i], emit);
                    }
                } else if (this.typeCheck("date", obj)) {
                    value = obj;
                } else if (this.typeCheck("object", obj)) {
                    value = {};

                    for (var key in obj) {
                        if (emit[key]) {
                            value[key] = obj[key];
                        } else {
                            value[key] = this.deepClone(obj[key], emit);
                        }
                    }
                } else {
                    value = obj;
                }

                return value;
            },

            /**
             * @method sort
             * use QuickSort
             * @param {Array} array
             * @return {QuickSort}
             */
            sort: function (array) {
                var QuickSort = jui.include("util.sort");
                return new QuickSort(array);
            },

            /**
             * @method runtime
             *
             * caculate callback runtime
             *
             * @param {String} name
             * @param {Function} callback
             */
            runtime: function (name, callback) {
                var nStart = new Date().getTime();
                callback();
                var nEnd = new Date().getTime();

                console.log(name + " : " + (nEnd - nStart) + "ms");
            },

            /**
             * @method resize
             * add event in window resize event
             * @param {Function} callback
             * @param {Number} ms delay time
             */
            resize: function (callback, ms) {
                var after_resize = (function () {
                    var timer = 0;

                    return function () {
                        clearTimeout(timer);
                        timer = setTimeout(callback, ms);
                    }
                })();

                if (window.addEventListener) {
                    window.addEventListener("resize", after_resize);
                } else if (object.attachEvent) {
                    window.attachEvent("onresize", after_resize);
                } else {
                    window["onresize"] = after_resize;
                }
            },

            /**
             * @method index
             *
             * IndexParser 객체 생성
             *
             * @return {IndexParser}
             */
            index: function () {
                var KeyParser = jui.include("util.keyparser");
                return new KeyParser();
            },

            /**
             * @method chunk
             * split array by length
             * @param {Array} arr
             * @param {Number} len
             * @return {Array}
             */
            chunk: function (arr, len) {
                var chunks = [],
                    i = 0,
                    n = arr.length;

                while (i < n) {
                    chunks.push(arr.slice(i, i += len));
                }

                return chunks;
            },

            /**
             * @method typeCheck
             * check data  type
             * @param {String} t  type string
             * @param {Object} v value object
             * @return {Boolean}
             */
            typeCheck: function (t, v) {
                function check(type, value) {
                    if (typeof(type) != "string") return false;

                    if (type == "string") {
                        return (typeof(value) == "string");
                    }
                    else if (type == "integer") {
                        return (typeof(value) == "number" && value % 1 == 0);
                    }
                    else if (type == "float") {
                        return (typeof(value) == "number" && value % 1 != 0);
                    }
                    else if (type == "number") {
                        return (typeof(value) == "number");
                    }
                    else if (type == "boolean") {
                        return (typeof(value) == "boolean");
                    }
                    else if (type == "undefined") {
                        return (typeof(value) == "undefined");
                    }
                    else if (type == "null") {
                        return (value === null);
                    }
                    else if (type == "array") {
                        return (value instanceof Array);
                    }
                    else if (type == "date") {
                        return (value instanceof Date);
                    }
                    else if (type == "function") {
                        return (typeof(value) == "function");
                    }
                    else if (type == "object") {
                        // typeCheck에 정의된 타입일 경우에는 object 체크시 false를 반환 (date, array, null)
                        return (
                            typeof(value) == "object" &&
                            value !== null && !(value instanceof Array) && !(value instanceof Date) && !(value instanceof RegExp)
                        );
                    }

                    return false;
                }

                if (typeof(t) == "object" && t.length) {
                    var typeList = t;

                    for (var i = 0; i < typeList.length; i++) {
                        if (check(typeList[i], v)) return true;
                    }

                    return false;
                } else {
                    return check(t, v);
                }
            },
            typeCheckObj: function (uiObj, list) {
                if (typeof(uiObj) != "object") return;
                var self = this;

                for (var key in uiObj) {
                    var func = uiObj[key];

                    if (typeof(func) == "function") {
                        (function (funcName, funcObj) {
                            uiObj[funcName] = function () {
                                var args = arguments,
                                    params = list[funcName];

                                for (var i = 0; i < args.length; i++) {
                                    if (!self.typeCheck(params[i], args[i])) {
                                        throw new Error("JUI_CRITICAL_ERR: the " + i + "th parameter is not a " + params[i] + " (" + name + ")");
                                    }
                                }

                                return funcObj.apply(this, args);
                            }
                        })(key, func);
                    }
                }
            },

            /**
             * @method svgToBase64
             *
             * xml 문자열로 svg datauri 생성
             *
             * @param {String} xml
             * @return {String} 변환된 data uri 링크
             */
            svgToBase64: function (xml) {
                var Base64 = jui.include("util.base64");
                return "data:image/svg+xml;base64," + Base64.encode(xml);
            },

            /**
             * @method dateFormat
             *
             * implements date format function
             *
             * yyyy : 4 digits year
             * yy : 2 digits year
             * y : 1 digit year
             *
             * @param {Date} date
             * @param {String} format   date format string
             * @param utc
             * @return {string}
             */
            dateFormat: function (date, format, utc) {
                var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
                var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
                var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

                function ii(i, len) {
                    var s = i + "";
                    len = len || 2;
                    while (s.length < len) s = "0" + s;
                    return s;
                }

                var y = utc ? date.getUTCFullYear() : date.getFullYear();
                format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y);
                format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2));
                format = format.replace(/(^|[^\\])y/g, "$1" + y);

                var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
                format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]);
                format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]);
                format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M));
                format = format.replace(/(^|[^\\])M/g, "$1" + M);

                var d = utc ? date.getUTCDate() : date.getDate();
                format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]);
                format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]);
                format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d));
                format = format.replace(/(^|[^\\])d/g, "$1" + d);

                var H = utc ? date.getUTCHours() : date.getHours();
                format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H));
                format = format.replace(/(^|[^\\])H/g, "$1" + H);

                var h = H > 12 ? H - 12 : H == 0 ? 12 : H;
                format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h));
                format = format.replace(/(^|[^\\])h/g, "$1" + h);

                var m = utc ? date.getUTCMinutes() : date.getMinutes();
                format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m));
                format = format.replace(/(^|[^\\])m/g, "$1" + m);

                var s = utc ? date.getUTCSeconds() : date.getSeconds();
                format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s));
                format = format.replace(/(^|[^\\])s/g, "$1" + s);

                var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds();
                format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3));
                f = Math.round(f / 10);
                format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f));
                f = Math.round(f / 10);
                format = format.replace(/(^|[^\\])f/g, "$1" + f);

                var T = H < 12 ? "AM" : "PM";
                format = format.replace(/(^|[^\\])TT+/g, "$1" + T);
                format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0));

                var t = T.toLowerCase();
                format = format.replace(/(^|[^\\])tt+/g, "$1" + t);
                format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0));

                var tz = -date.getTimezoneOffset();
                var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-";
                if (!utc) {
                    tz = Math.abs(tz);
                    var tzHrs = Math.floor(tz / 60);
                    var tzMin = tz % 60;
                    K += ii(tzHrs) + ":" + ii(tzMin);
                }
                format = format.replace(/(^|[^\\])K/g, "$1" + K);

                var day = (utc ? date.getUTCDay() : date.getDay()) + 1;
                format = format.replace(new RegExp(dddd[0], "g"), dddd[day]);
                format = format.replace(new RegExp(ddd[0], "g"), ddd[day]);

                format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]);
                format = format.replace(new RegExp(MMM[0], "g"), MMM[M]);

                format = format.replace(/\\(.)/g, "$1");

                return format;
            },
            /**
             * @method createId
             *
             * 유니크 아이디 생성
             *
             * @param {String} key  prefix string
             * @return {String} 생성된 아이디 문자열
             */
            createId: function (key) {
                return [key || "id", (+new Date), Math.round(Math.random() * 100) % 100].join("-");
            },
            /**
             * @method btoa
             *
             * Base64 인코딩
             *
             * @return {String}
             */
            btoa: function (input) {
                var Base64 = jui.include("util.base64");
                return Base64.encode(input);
            },
            /**
             * @method atob
             *
             * Base64 디코딩
             *
             * @return {String}
             */
            atob: function (input) {
                var Base64 = jui.include("util.base64");
                return Base64.decode(input);
            },

            /**
             * implement async loop without blocking ui
             *
             * @param total
             * @param context
             * @returns {Function}
             */
            timeLoop: function (total, context) {

                return function (callback, lastCallback) {
                    function TimeLoopCallback(i) {

                        if (i < 1) return;

                        if (i == 1) {
                            callback.call(context, i)
                            lastCallback.call(context);
                        } else {
                            setTimeout(function () {
                                if (i > -1) callback.call(context, i--);
                                if (i > -1) TimeLoopCallback(i);
                            }, 1);
                        }
                    }

                    TimeLoopCallback(total);
                };
            },
            /**
             * @method loop
             *
             * 최적화된 루프 생성 (5단계로 나눔)
             *
             * @param {Number} total
             * @param {Object} [context=null]
             * @return {Function} 최적화된 루프 콜백 (index, groupIndex 2가지 파라미터를 받는다.)
             */
            loop: function (total, context) {
                var start = 0,
                    end = total,
                    unit = Math.ceil(total / 5);

                return function (callback) {
                    var first = start, second = unit * 1, third = unit * 2, fourth = unit * 3, fifth = unit * 4,
                        firstMax = second, secondMax = third, thirdMax = fourth, fourthMax = fifth, fifthMax = end;

                    while (first < firstMax && first < end) {
                        callback.call(context, first, 1);
                        first++;

                        if (second < secondMax && second < end) {
                            callback.call(context, second, 2);
                            second++;
                        }
                        if (third < thirdMax && third < end) {
                            callback.call(context, third, 3);
                            third++;
                        }
                        if (fourth < fourthMax && fourth < end) {
                            callback.call(context, fourth, 4);
                            fourth++;
                        }
                        if (fifth < fifthMax && fifth < end) {
                            callback.call(context, fifth, 5);
                            fifth++;
                        }
                    }
                };
            },

            /**
             * @method loopArray
             *
             * 배열을 사용해서 최적화된 루프로 생성한다.
             *
             *
             * @param {Array} data 루프로 생성될 배열
             * @param {Object} [context=null]
             * @return {Function} 최적화된 루프 콜백 (data, index, groupIndex 3가지 파라미터를 받는다.)
             */
            loopArray: function (data, context) {
                var total = data.length,
                    start = 0,
                    end = total,
                    unit = Math.ceil(total / 5);

                return function (callback) {
                    var first = start, second = unit * 1, third = unit * 2, fourth = unit * 3, fifth = unit * 4,
                        firstMax = second, secondMax = third, thirdMax = fourth, fourthMax = fifth, fifthMax = end;

                    while (first < firstMax && first < end) {
                        callback.call(context, data[first], first, 1);
                        first++;
                        if (second < secondMax && second < end) {
                            callback.call(context, data[second], second, 2);
                            second++;
                        }
                        if (third < thirdMax && third < end) {
                            callback.call(context, data[third], third, 3);
                            third++;
                        }
                        if (fourth < fourthMax && fourth < end) {
                            callback.call(context, data[fourth], fourth, 4);
                            fourth++;
                        }
                        if (fifth < fifthMax && fifth < end) {
                            callback.call(context, data[fifth], fifth, 5);
                            fifth++;
                        }
                    }
                };

            },

            /**
             * @method makeIndex
             *
             * 배열의 키 기반 인덱스를 생성한다.
             *
             * 개별 값 별로 멀티 인덱스를 생성한다.
             *
             * @param {Array} data
             * @param {String} keyField
             * @return {Object} 생성된 인덱스
             */
            makeIndex: function (data, keyField) {
                var list = {},
                    func = this.loopArray(data);

                func(function (d, i) {
                    var value = d[keyField];

                    if (typeof list[value] == 'undefined') {
                        list[value] = [];
                    }

                    list[value].push(i);
                });

                return list;
            },

            /**
             * @method startsWith
             * Check that it matches the starting string search string.
             *
             * @param {String} string
             * @param {String} searchString
             * @return {Integer} position
             */
            startsWith: function (string, searchString, position) {
                position = position || 0;

                return string.lastIndexOf(searchString, position) === position;
            },

            /**
             * @method endsWith
             * Check that it matches the end of a string search string.
             *
             * @param {String} string
             * @param {String} searchString
             * @return {Integer} position
             */
            endsWith: function (string, searchString, position) {
                var subjectString = string;

                if (position === undefined || position > subjectString.length) {
                    position = subjectString.length;
                }

                position -= searchString.length;
                var lastIndex = subjectString.indexOf(searchString, position);

                return lastIndex !== -1 && lastIndex === position;
            },

            inArray: function (target, list) {
                if (this.typeCheck(["undefined", "null"], target) || !this.typeCheck("array", list)) return -1;

                for (var i = 0, len = list.length; i < len; i++) {
                    if (list[i] == target)
                        return i;
                }

                return -1;
            },

            trim: function (text) {
                var whitespace = "[\\x20\\t\\r\\n\\f]",
                    rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g");

                return text == null ?
                    "" :
                    ( text + "" ).replace(rtrim, "");
            },

            ready: (function () {
                var readyList,
                    DOMContentLoaded,
                    class2type = {};

                class2type["[object Boolean]"] = "boolean";
                class2type["[object Number]"] = "number";
                class2type["[object String]"] = "string";
                class2type["[object Function]"] = "function";
                class2type["[object Array]"] = "array";
                class2type["[object Date]"] = "date";
                class2type["[object RegExp]"] = "regexp";
                class2type["[object Object]"] = "object";

                var ReadyObj = {
                    // Is the DOM ready to be used? Set to true once it occurs.
                    isReady: false,
                    // A counter to track how many items to wait for before
                    // the ready event fires. See #6781
                    readyWait: 1,
                    // Hold (or release) the ready event
                    holdReady: function (hold) {
                        if (hold) {
                            ReadyObj.readyWait++;
                        } else {
                            ReadyObj.ready(true);
                        }
                    },
                    // Handle when the DOM is ready
                    ready: function (wait) {
                        // Either a released hold or an DOMready/load event and not yet ready
                        if ((wait === true && !--ReadyObj.readyWait) || (wait !== true && !ReadyObj.isReady)) {
                            // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                            if (!document.body) {
                                return setTimeout(ReadyObj.ready, 1);
                            }

                            // Remember that the DOM is ready
                            ReadyObj.isReady = true;
                            // If a normal DOM Ready event fired, decrement, and wait if need be
                            if (wait !== true && --ReadyObj.readyWait > 0) {
                                return;
                            }
                            // If there are functions bound, to execute
                            readyList.resolveWith(document, [ReadyObj]);

                            // Trigger any bound ready events
                            //if ( ReadyObj.fn.trigger ) {
                            //  ReadyObj( document ).trigger( "ready" ).unbind( "ready" );
                            //}
                        }
                    },
                    bindReady: function () {
                        if (readyList) {
                            return;
                        }
                        readyList = ReadyObj._Deferred();

                        // Catch cases where $(document).ready() is called after the
                        // browser event has already occurred.
                        if (document.readyState === "complete") {
                            // Handle it asynchronously to allow scripts the opportunity to delay ready
                            return setTimeout(ReadyObj.ready, 1);
                        }

                        // Mozilla, Opera and webkit nightlies currently support this event
                        if (document.addEventListener) {
                            // Use the handy event callback
                            document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
                            // A fallback to window.onload, that will always work
                            window.addEventListener("load", ReadyObj.ready, false);

                            // If IE event model is used
                        } else if (document.attachEvent) {
                            // ensure firing before onload,
                            // maybe late but safe also for iframes
                            document.attachEvent("onreadystatechange", DOMContentLoaded);

                            // A fallback to window.onload, that will always work
                            window.attachEvent("onload", ReadyObj.ready);

                            // If IE and not a frame
                            // continually check to see if the document is ready
                            var toplevel = false;

                            try {
                                toplevel = window.frameElement == null;
                            } catch (e) {
                            }

                            if (document.documentElement.doScroll && toplevel) {
                                doScrollCheck();
                            }
                        }
                    },
                    _Deferred: function () {
                        var // callbacks list
                            callbacks = [],
                            // stored [ context , args ]
                            fired,
                            // to avoid firing when already doing so
                            firing,
                            // flag to know if the deferred has been cancelled
                            cancelled,
                            // the deferred itself
                            deferred = {

                                // done( f1, f2, ...)
                                done: function () {
                                    if (!cancelled) {
                                        var args = arguments,
                                            i,
                                            length,
                                            elem,
                                            type,
                                            _fired;
                                        if (fired) {
                                            _fired = fired;
                                            fired = 0;
                                        }
                                        for (i = 0, length = args.length; i < length; i++) {
                                            elem = args[i];
                                            type = ReadyObj.type(elem);
                                            if (type === "array") {
                                                deferred.done.apply(deferred, elem);
                                            } else if (type === "function") {
                                                callbacks.push(elem);
                                            }
                                        }
                                        if (_fired) {
                                            deferred.resolveWith(_fired[0], _fired[1]);
                                        }
                                    }
                                    return this;
                                },

                                // resolve with given context and args
                                resolveWith: function (context, args) {
                                    if (!cancelled && !fired && !firing) {
                                        // make sure args are available (#8421)
                                        args = args || [];
                                        firing = 1;
                                        try {
                                            while (callbacks[0]) {
                                                callbacks.shift().apply(context, args);//shifts a callback, and applies it to document
                                            }
                                        }
                                        finally {
                                            fired = [context, args];
                                            firing = 0;
                                        }
                                    }
                                    return this;
                                },

                                // resolve with this as context and given arguments
                                resolve: function () {
                                    deferred.resolveWith(this, arguments);
                                    return this;
                                },

                                // Has this deferred been resolved?
                                isResolved: function () {
                                    return !!( firing || fired );
                                },

                                // Cancel
                                cancel: function () {
                                    cancelled = 1;
                                    callbacks = [];
                                    return this;
                                }
                            };

                        return deferred;
                    },
                    type: function (obj) {
                        return obj == null ?
                            String(obj) :
                            class2type[Object.prototype.toString.call(obj)] || "object";
                    }
                }
                // The DOM ready check for Internet Explorer
                function doScrollCheck() {
                    if (ReadyObj.isReady) {
                        return;
                    }

                    try {
                        // If IE is used, use the trick by Diego Perini
                        // http://javascript.nwbox.com/IEContentLoaded/
                        document.documentElement.doScroll("left");
                    } catch (e) {
                        setTimeout(doScrollCheck, 1);
                        return;
                    }

                    // and execute any waiting functions
                    ReadyObj.ready();
                }

                // Cleanup functions for the document ready method
                if (document.addEventListener) {
                    DOMContentLoaded = function () {
                        document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
                        ReadyObj.ready();
                    };

                } else if (document.attachEvent) {
                    DOMContentLoaded = function () {
                        // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                        if (document.readyState === "complete") {
                            document.detachEvent("onreadystatechange", DOMContentLoaded);
                            ReadyObj.ready();
                        }
                    };
                }
                function ready(fn) {
                    // Attach the listeners
                    ReadyObj.bindReady();

                    var type = ReadyObj.type(fn);

                    // Add the callback
                    readyList.done(fn);//readyList is result of _Deferred()
                }

                return ready;
            })(),

            param: function (data) {
                var r20 = /%20/g,
                    s = [],
                    add = function (key, value) {
                        // If value is a function, invoke it and return its value
                        value = utility.typeCheck("function", value) ? value() : (value == null ? "" : value);
                        s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
                    };

                for (var key in data) {
                    add(key, data[key]);
                }

                return s.join("&").replace(r20, "+");
            },

            ajax: function (data) {
                var xhr = null, paramStr = "", callback = null;

                var opts = utility.extend({
                    url: null,
                    type: "GET",
                    data: null,
                    async: true,
                    success: null,
                    fail: null
                }, data);

                if (!this.typeCheck("string", opts.url) || !this.typeCheck("function", opts.success))
                    return;

                if (this.typeCheck("object", opts.data))
                    paramStr = this.param(opts.data);

                if (!this.typeCheck("undefined", XMLHttpRequest)) {
                    xhr = new XMLHttpRequest();
                } else {
                    var versions = [
                        "MSXML2.XmlHttp.5.0",
                        "MSXML2.XmlHttp.4.0",
                        "MSXML2.XmlHttp.3.0",
                        "MSXML2.XmlHttp.2.0",
                        "Microsoft.XmlHttp"
                    ];

                    for (var i = 0, len = versions.length; i < len; i++) {
                        try {
                            xhr = new ActiveXObject(versions[i]);
                            break;
                        }
                        catch (e) {
                        }
                    }
                }

                if (xhr != null) {
                    xhr.open(opts.type, opts.url, opts.async);
                    xhr.send(paramStr);

                    callback = function () {
                        if (xhr.readyState === 4 && xhr.status == 200) {
                            opts.success(xhr);
                        } else {
                            if (utility.typeCheck("function", opts.fail)) {
                                opts.fail(xhr);
                            }
                        }
                    }

                    if (!opts.async) {
                        callback();
                    } else {
                        xhr.onreadystatechange = callback;
                    }
                }
            },

            scrollWidth: function () {
                var inner = document.createElement("p");
                inner.style.width = "100%";
                inner.style.height = "200px";

                var outer = document.createElement("div");
                outer.style.position = "absolute";
                outer.style.top = "0px";
                outer.style.left = "0px";
                outer.style.visibility = "hidden";
                outer.style.width = "200px";
                outer.style.height = "150px";
                outer.style.overflow = "hidden";
                outer.appendChild(inner);

                document.body.appendChild(outer);
                var w1 = inner.offsetWidth;
                outer.style.overflow = "scroll";
                var w2 = inner.offsetWidth;
                if (w1 == w2) w2 = outer.clientWidth;
                document.body.removeChild(outer);

                return (w1 - w2);
            }
        }


		/*
		 * Module related functions
		 *
		 */
        var getDepends = function (depends) {
            var args = [];

            for (var i = 0; i < depends.length; i++) {
                var module = global[depends[i]];

                if (!utility.typeCheck(["function", "object"], module)) {
                    var modules = getModules(depends[i]);

                    if (modules == null) {
                        console.log("JUI_WARNING_MSG: '" + depends[i] + "' is not loaded");
                        args.push(null);
                    } else {
                        args.push(modules);
                    }

                } else {
                    args.push(module);
                }
            }

            return args;
        }

        var getModules = function (parent) {
            var modules = null,
                parent = parent + ".";

            for (var key in global) {
                if (key.indexOf(parent) != -1) {
                    if (utility.typeCheck(["function", "object"], global[key])) {
                        var child = key.split(parent).join("");

                        if (child.indexOf(".") == -1) {
                            if (modules == null) {
                                modules = {};
                            }

                            modules[child] = global[key];
                        }
                    }
                }
            }

            return modules;
        }

        /**
         * @class jui
         *
         * Global Object
         *
         * @singleton
         */
        window.jui = nodeGlobal.jui = {
            /**
             * @method ready
             *
             * ready 타임에 실행될 callback 정의
             *
             * @param {Function} callback
             */
            ready: function () {
                var args = [],
                    callback = (arguments.length == 2) ? arguments[1] : arguments[0],
                    depends = (arguments.length == 2) ? arguments[0] : null;

                if (!utility.typeCheck(["array", "null"], depends) || !utility.typeCheck("function", callback)) {
                    throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function");
                }

                utility.ready(function () {
                    if (depends) {
                        args = getDepends(depends);
                    } else {
                        // @Deprecated 기존의 레거시를 위한 코드
                        var ui = getModules("ui"),
                            uix = {};

                        utility.extend(uix, ui);
                        utility.extend(uix, getModules("grid"));

                        args = [ui, uix, utility];
                    }

                    callback.apply(null, args);
                });
            },

            /**
             * @method defineUI
             *
             * 사용자가 실제로 사용할 수 있는 UI 클래스를 정의
             *
             * @param {String} name 모듈 로드와 상속에 사용될 이름을 정한다.
             * @param {Array} depends 'define'이나 'defineUI'로 정의된 클래스나 객체를 인자로 받을 수 있다.
             * @param {Function} callback UI 클래스를 해당 콜백 함수 내에서 클래스 형태로 구현하고 리턴해야 한다.
             */
            defineUI: function (name, depends, callback, parent) {
                if (!utility.typeCheck("string", name) || !utility.typeCheck("array", depends) || !utility.typeCheck("function", callback) || !utility.typeCheck(["string", "undefined"], parent)) {
                    throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function");
                }

                if (utility.typeCheck("function", globalClass[name])) {
                    throw new Error("JUI_CRITICAL_ERR: '" + name + "' is already exist");
                }

                if (utility.typeCheck("undefined", parent)) { // 기본적으로 'event' 클래스를 상속함
                    parent = "event";
                }

                if (!utility.typeCheck("function", globalClass[parent])) {
                    throw new Error("JUI_CRITICAL_ERR: Parents are the only function");
                } else {
                    if (globalFunc[parent] !== true) {
                        throw new Error("JUI_CRITICAL_ERR: UI function can not be inherited");
                    }
                }

                var args = getDepends(depends),
                    uiFunc = callback.apply(null, args);

                // 상속
                utility.inherit(uiFunc, globalClass[parent]);

                // TODO: 차트 빌더를 제외하고, 무조건 event 클래스에 정의된 init 메소드를 호출함
                global[name] = globalClass[parent != "core" ? "event" : "core"].init({
                    type: name,
                    "class": uiFunc
                });

                globalClass[name] = uiFunc;
                globalFunc[name] = true;

                /**
                 * @deprecated
                 // support AMD module
                 if (typeof define == "function" && define.amd) {
			define(name, function () {
				return global[name]
			});
		}
                 */
            },

            createUIObject: function (UI, selector, index, elem, options, afterHook) {
                var mainObj = new UI["class"]();

                // Check Options
                var opts = jui.defineOptions(UI["class"], options || {});

                // Public Properties
                mainObj.init.prototype = mainObj;
                /** @property {String/HTMLElement} selector */
                mainObj.init.prototype.selector = selector;
                /** @property {HTMLElement} root */
                mainObj.init.prototype.root = elem;
                /** @property {Object} options */
                mainObj.init.prototype.options = opts;
                /** @property {Object} tpl Templates */
                mainObj.init.prototype.tpl = {};
                /** @property {Array} event Custom events */
                mainObj.init.prototype.event = new Array(); // Custom Event
                /** @property {Integer} timestamp UI Instance creation time*/
                mainObj.init.prototype.timestamp = new Date().getTime();
                /** @property {Integer} index Index of UI instance*/
                mainObj.init.prototype.index = index;
                /** @property {Class} module Module class */
                mainObj.init.prototype.module = UI;

                // UI 객체 프로퍼티를 외부에서 정의할 수 있음 (jQuery 의존성 제거를 위한 코드)
                if (utility.typeCheck("function", afterHook)) {
                    afterHook(mainObj, opts);
                }

                // Script-based Template Settings
                for (var name in opts.tpl) {
                    var tplHtml = opts.tpl[name];

                    if (utility.typeCheck("string", tplHtml) && tplHtml != "") {
                        mainObj.init.prototype.tpl[name] = utility.template(tplHtml);
                    }
                }

                var uiObj = new mainObj.init();

                // Custom Event Setting
                for (var key in opts.event) {
                    uiObj.on(key, opts.event[key]);
                }

                // 엘리먼트 객체에 jui 속성 추가
                elem.jui = uiObj;

                return uiObj;
            },

            /**
             * @method define
             *
             * UI 클래스에서 사용될 클래스를 정의하고, 자유롭게 상속할 수 있는 클래스를 정의
             *
             * @param {String} name 모듈 로드와 상속에 사용될 이름을 정한다.
             * @param {Array} depends 'define'이나 'defineUI'로 정의된 클래스나 객체를 인자로 받을 수 있다.
             * @param {Function} callback UI 클래스를 해당 콜백 함수 내에서 클래스 형태로 구현하고 리턴해야 한다.
             * @param {String} parent 상속받을 클래스
             */
            define: function (name, depends, callback, parent) {
                if (!utility.typeCheck("string", name) || !utility.typeCheck("array", depends) ||
                    !utility.typeCheck("function", callback) || !utility.typeCheck([ "string", "undefined", "null" ], parent)) {
                    throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function");
                }

                if (utility.typeCheck("function", globalClass[name])) {
                    throw new Error("JUI_CRITICAL_ERR: '" + name + "' is already exist");
                }

                var args = getDepends(depends),
                    uiFunc = callback.apply(null, args);

                if (utility.typeCheck("function", globalClass[parent])) {
                    if (globalFunc[parent] !== true) {
                        throw new Error("JUI_CRITICAL_ERR: UI function can not be inherited");
                    } else {
                        utility.inherit(uiFunc, globalClass[parent]);
                    }
                }

                // 함수 고유 설정
                global[name] = uiFunc;
                globalClass[name] = uiFunc; // original function
                globalFunc[name] = true;

                // support AMD module
                // @deprecated
				/*
				 if (typeof define == "function" && define.amd) {
				 define(name, function () {
				 return global[name]
				 });
				 }*/
            },

            /**
             * @method redefine
             *
             * UI 클래스에서 사용될 클래스를 정의하고, 자유롭게 상속할 수 있는 클래스를 정의
             *
             * @param {String} name 모듈 로드와 상속에 사용될 이름을 정한다.
             * @param {Array} depends 'define'이나 'defineUI'로 정의된 클래스나 객체를 인자로 받을 수 있다.
             * @param {Function} callback UI 클래스를 해당 콜백 함수 내에서 클래스 형태로 구현하고 리턴해야 한다.
             * @param {String} parent 상속받을 클래스
             * @param {Boolean} 이미 정의되어 있으면 무시하기
             */
            redefine: function (name, depends, callback, parent, skip) {
                if (!skip && globalFunc[name] === true) {
                    global[name] = null;
                    globalClass[name] = null;
                    globalFunc[name] = false;
                }

                if (!skip || (skip && globalFunc[name] !== true)) {
                    this.define(name, depends, callback, parent);
                }
            },

            /**
             * @method defineOptions
             *
             * 모듈 기본 옵션 정의
             *
             * @param {Object} Module
             * @param {Object} options
             * @param {Object} exceptOpts
             * @return {Object}
             */
            defineOptions: function (Module, options, exceptOpts) {
                var defOpts = getOptions(Module, {});
                var defOptKeys = Object.keys(defOpts),
                    optKeys = Object.keys(options);

                // 정의되지 않은 옵션 사용 유무 체크
                for (var i = 0; i < optKeys.length; i++) {
                    var name = optKeys[i];

                    if (utility.inArray(name, defOptKeys) == -1 && utility.inArray(name, exceptOpts) == -1) {
                        throw new Error("JUI_CRITICAL_ERR: '" + name + "' is not an option");
                    }
                }

                // 사용자 옵션 + 기본 옵션
                utility.extend(options, defOpts, true);

                // 상위 모듈의 옵션까지 모두 얻어오는 함수
                function getOptions(Module, options) {
                    if (utility.typeCheck("function", Module)) {
                        if (utility.typeCheck("function", Module.setup)) {
                            var opts = Module.setup();

                            for (var key in opts) {
                                if (utility.typeCheck("undefined", options[key])) {
                                    options[key] = opts[key];
                                }
                            }
                        }

                        getOptions(Module.parent, options);
                    }

                    return options;
                }

                return options;
            },

            /**
             * define과 defineUI로 정의된 클래스 또는 객체를 가져온다.
             *
             * @param name 가져온 클래스 또는 객체의 이름
             * @return {*}
             */
            include: function (name) {
                if (!utility.typeCheck("string", name)) {
                    throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function");
                }

                var module = global[name];

                if (utility.typeCheck(["function", "object"], module)) {
                    return module;
                } else {
                    var modules = getModules(name);

                    if (modules == null) {
                        console.log("JUI_WARNING_MSG: '" + name + "' is not loaded");
                        return null;
                    } else {
                        return modules;
                    }
                }
            },

            /**
             * define과 defineUI로 정의된 모든 클래스와 객체를 가져온다.
             *
             * @return {Array}
             */
            includeAll: function () {
                var result = [];

                for (var key in global) {
                    result.push(global[key]);
                }

                return result;
            }
        };


        if (typeof module == 'object' && module.exports) {
            module.exports = window.jui || global.jui;
        }

    })(window, (typeof(global) !== "undefined") ? global : window);
}