<commit_before>/**
 * @namespace
 */
var collie = collie || {};

(function () {
    /**
     * 콜리 버전
     * 
     * @name collie.version
     * @description 자동 치환되므로 직접 수정하지 않는다.
     */
    collie.version = "{{version}}";

    /**
     * 클래스 만들기
     * 
     * @method collie#Class
     * @param {Object} o 클래스 멤버, $init을 이용해 생성자를 정의할 수 있다.
     * @param {collie.Class} oParent 상속받을 부모 클래스
     * @return {collie.Class}
     * @example
     * ```
     * var Person = collie.Class({
     *  gender : false,
     *  walk : function () { return "walking!"; }
     * });
     * 
     * var Male = collie.Class({
     *  name : "",
     *  gender : "male"
     * }, Person);
     * 
     * var oDavid = new Male();
     * oDavid.name = "david";
     * 
     * alert(oDavid.name); // david
     * alert(oDavid.gender); // male
     * alert(oDavid.walk()); // walking!
     * ```
     * @example
     * override
     * ```
     * var Person = collie.Class({
     *  testMethod : function () {
     *  
     *  }
     * });
     * 
     * var Male = collie.Class({
     *  testMethod : function () {
     *      // blah
     *      this.constructor.$super.testMethod.apply(this, arguments);      
     *  }
     * }, Person);
     * ```
     * @example
     * You can also use create a instance without 'new' keyword
     * ```
     * var Person = collie.Class({
     *  $init : function () {
     *  },
     *  test : function () {
     *  }
     * });
     * 
     * var a = new Person();
     * var b = Person(); // It works fine!
     * ```
     */
    collie.Class = function (o, oParent) {      
        var $init = null;
        var checkDirectCall = function () { return true; };
        var F;
        
        if ("$init" in o) {
            $init = o.$init;
            delete o.$init;
        }
        
        if (typeof oParent === "undefined") {
            F = function () {
                var args = arguments;
                
                if (!(this instanceof F)) {
                    return new F(checkDirectCall, args);
                }
                
                if (args.length && args[0] === checkDirectCall) {
                    args = args[1];
                }
                
                if ($init !== null) {
                    $init.apply(this, args);
                }
            };
        } else {
            F = function () {
                var args = arguments;
    
                if (!(this instanceof F)) {
                    return new F(checkDirectCall, args);
                }
                
                if (args.length && args[0] === checkDirectCall) {
                    args = args[1];
                }
                
                // 부모의 생성자 실행
                oParent.apply(this, args);
                
                // 자식의 생성자 실행
                if ($init !== null) {
                    $init.apply(this, args);
                }
            };
            
            var Parent = function () {};
            Parent.prototype = oParent.prototype;
            F.$super = oParent.prototype;
            F.prototype = new Parent();
            F.prototype.constructor = F;
        }
        
        for (var i in o) {
            if (o.hasOwnProperty(i) && i !== "prototype") {
                F.prototype[i] = o[i];
            }
        }
        
        return F;
    };
    
    /**
     * 자주 쓰이는 유틸 모음
     * @namespace
     */
    collie.util = new (collie.Class(/** @lends collie.util */{
        $init : function () {
            this._sCSSPrefix = null;
            this._htDeviceInfo = null;
            this._bSupport3d = null;
            this._bSupportCSS3 = null;
            this._htBoundary = {
                left : 0,
                right : 0,
                top : 0,
                bottom : 0
            };
        },
        
        /**
         * 아이디로 표시 객체 인스턴스를 가져온다
         * 주로 DOM 방식일 때 사용 된다
         * 
         * @param {Number} nId
         * @return {collie.DisplayObject}
         */
        getDisplayObjectById : function (nId) {
            return collie.DisplayObject.htFactory[nId];
        },
        
        /**
         * name으로 표시 객체 인스턴스를 가져온다
         * 
         * @param {String} sName
         * @return {collie.DisplayObject}
         */
        getDisplayObjectByName : function (sName) {
            for (var i in collie.DisplayObject.htFactory) {
                if (collie.DisplayObject.htFactory[i].get("name") === sName) {
                    return collie.DisplayObject.htFactory[i];
                }
            }
            
            return false;
        },
        
        /**
         * userAgent 값으로 현재 단말 정보를 반환 한다
         * 값을 한번 얻어오면 다음부터는 캐시된 값을 사용 한다
         * 
         * @return {Object} htInfo
         * @return {Boolean} htInfo.desktop 데스크탑 여부
         * @return {Boolean} htInfo.supportCanvas 캔버스 지원 여부
         * @return {Boolean|Number} htInfo.android 안드로이드라면 두번째까지의 버젼, 안드로이드가 아니라면 false
         * @return {Boolean|Number} htInfo.ios iOS라면 두번째까지의 버젼, iOS가 아니라면 false
         * @return {Boolean|Number} htInfo.ie IE 브라우저라면 첫번째까지의 버전, IE 브라우저가 아니라면 false
         * @return {Boolean|Number} htInfo.chrome Agent에 Chrome이 포함돼 있는지 여부
         */
        getDeviceInfo : function (sAgent) {
            if (this._htDeviceInfo !== null && typeof sAgent === "undefined") {
                return this._htDeviceInfo;
            }
            
            var aMat = null;
            var bIsDesktop = false;
            var bSupportCanvas = typeof CanvasRenderingContext2D !== "undefined";
            var bIsAndroid = false;
            var bIsIOS = false;
            var bIsIE = false;
            var bHasChrome = (/chrome/i.test(sAgent)) ? true : false;
            var bHiggs = (/Higgs/i.test(sAgent))? true: false; // The Higgs is a browser engine made by Naver Corporation
            var sAgent = sAgent || navigator.userAgent;
            var nVersion = 0;
            
            if (/android/i.test(sAgent)) { // android
                bIsAndroid = true;
                aMat = sAgent.toString().match(/android ([0-9]\.[0-9])\.?([0-9]?)/i);
                
                if (aMat && aMat[1]) {
                    nVersion = (parseFloat(aMat[1]) + (aMat[2] ? aMat[2] * 0.01 : 0)).toFixed(2);
                }
            } else if (/(iphone|ipad|ipod)/i.test(sAgent)) { // iOS
                bIsIOS = true;
                aMat = sAgent.toString().match(/([0-9]_[0-9])/i);
                
                if (aMat && aMat[1]) {
                    nVersion = parseFloat(aMat[1].replace(/_/, '.'));
                }
            } else { // PC
                bIsDesktop = true;
                
                if (/(MSIE)/i.test(sAgent)) { // IE
                    bIsIE = true;
                    aMat = sAgent.toString().match(/MSIE ([0-9])/i);
                    
                    if (aMat && aMat[1]) {
                        nVersion = parseInt(aMat[1], 10);
                    }
                }
            }
            
            this._htDeviceInfo = {
                supportCanvas : bSupportCanvas,
                desktop : bIsDesktop,
                android : bIsAndroid ? nVersion : false,
                ios : bIsIOS ? nVersion : false,
                ie : bIsIE ? nVersion : false,
                chrome : bHasChrome,
                higgs : bHiggs
            };
            
            return this._htDeviceInfo;
        },
        
        /**
         * 브라우저에 따른 CSS Prefix를 반환
         * 
         * @param {String} sName 대상 CSS 속성 명 (- 포함), 값이 없으면 prefix만 반환
         * @param {Boolean} bJavascript 자바스크립트 속성 타입으로 반환
         * @example
         * collie.util.getCSSPrefix("transform"); // -webkit-transform
         * collie.util.getCSSPrefix("transform", true); // webkitTransform
         * 
         * // prefix가 없을 때
         * collie.util.getCSSPrefix("transform"); // transform
         * collie.util.getCSSPrefix("transform", true); // transform
         * @return {String} 조합된 CSS Prefix, 혹은 속성 명
         */
        getCSSPrefix : function (sName, bJavascript) {
            var sResult = '';
            
            if (this._sCSSPrefix === null) {
                this._sCSSPrefix = '';
                
                // webkit이 가장 먼저 쓰일 것 같아서 webkit을 최상단으로 옮김
                if (typeof document.body.style.webkitTransform !== "undefined") {
                    this._sCSSPrefix = "-webkit-";
                } else if (typeof document.body.style.MozTransform !== "undefined") {
                    this._sCSSPrefix = "-moz-";
                } else if (typeof document.body.style.OTransform !== "undefined") {
                    this._sCSSPrefix = "-o-";
                } else if (typeof document.body.style.msTransform !== "undefined") {
                    this._sCSSPrefix = "-ms-";
                }
            }
            
            sResult = this._sCSSPrefix + (sName ? sName : '');
            
            // - 빼기
            if (bJavascript) {
                var aTmp = sResult.split("-");
                sResult = '';
                
                for (var i = 0, len = aTmp.length; i < len; i++) {
                    if (aTmp[i]) {
                        sResult += sResult ? aTmp[i].substr(0, 1).toUpperCase() + aTmp[i].substr(1) : aTmp[i];
                    }
                }
                
                if (this._sCSSPrefix === "-moz-" || this._sCSSPrefix === "-o-") {
                    sResult = sResult.substr(0, 1).toUpperCase() + sResult.substr(1);
                }
            }
            
            return sResult;
        },
        
        /**
         * CSS3를 지원하는지 여부
         * 
         * @return {Boolean}
         */
        getSupportCSS3 : function () {
            if (this._bSupportCSS3 === null) {
                this._bSupportCSS3 = typeof document.body.style[collie.util.getCSSPrefix("transform", true)] !== "undefined" || typeof document.body.style.transform != "undefined";
            }
            
            return this._bSupportCSS3;
        },
        
        /**
         * CSS3d를 지원하는지 여부
         * 
         * @return {Boolean}
         */
        getSupportCSS3d : function () {
            if (this._bSupport3d === null) {
                this._bSupport3d = (typeof document.body.style[collie.util.getCSSPrefix("perspective", true)] !== "undefined" || typeof document.body.style.perspective != "undefined") && (!collie.util.getDeviceInfo().android || collie.util.getDeviceInfo().android >= 4);
            }
            
            return this._bSupport3d;
        },
        
        /**
         * 각도를 라디안으로 변환
         * 
         * @param {Number} nDeg
         * @return {Number}
         */
        toRad : function (nDeg) {
            return nDeg * Math.PI / 180;
        },
        
        /**
         * 라디안을 각도로 변환
         * 
         * @param {Number} nRad
         * @return {Number}
         */
        toDeg : function (nRad) {
            return nRad * 180 / Math.PI;
        },
        
        /**
         * 근사값 구함(소수 7자리 미만은 버림)
         * - javascript 소숫점 연산 오류로 인한 근사값 연산임
         * 
         * @param {Number} nValue 값
         * @return {Number}
         */
        approximateValue : function (nValue) {
            return Math.round(nValue * 10000000) / 10000000;
        },
        
        /**
         * 각도를 0~360 값 사이로 맞춤
         * 
         * @param {Number} nAngleRad 라디안 값
         * @return {Number}
         */
        fixAngle : function (nAngleRad) {
            var nAngleDeg = collie.util.toDeg(nAngleRad);
            nAngleDeg -= Math.floor(nAngleDeg / 360) * 360;
            return collie.util.toRad(nAngleDeg);
        },
        
        /**
         * 거리를 반환
         * 
         * @param {Number} x1
         * @param {Number} y1
         * @param {Number} x2
         * @param {Number} y2
         * @return {Number} 거리
         */
        getDistance : function (x1, y1, x2, y2) {
            return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
        },
        
        /**
         * 점 배열에서 최소 사각형 영역을 구한다
         * 
         * @param {Array} aPoints 대상 배열 [[x1, y1], [x2, y2], ... ]
         * @return {Object} htResult
         * @return {Number} htResult.left
         * @return {Number} htResult.right
         * @return {Number} htResult.bottom
         * @return {Number} htResult.top
         */
        getBoundary : function (aPoints) {
            var nMinX = aPoints[0][0];
            var nMaxX = aPoints[0][0];
            var nMinY = aPoints[0][1];
            var nMaxY = aPoints[0][1];
            
            for (var i = 1, len = aPoints.length; i < len; i++) {
                nMinX = Math.min(nMinX, aPoints[i][0]);
                nMaxX = Math.max(nMaxX, aPoints[i][0]);
                nMinY = Math.min(nMinY, aPoints[i][1]);
                nMaxY = Math.max(nMaxY, aPoints[i][1]);
            }
            
            return {
                left : nMinX,
                right : nMaxX,
                top : nMinY,
                bottom : nMaxY
            };
        },
        
        /**
         * boundary를 points로 변환한다
         * 
         * @param {Object} htBoundary
         * @param {Number} htBoundary.left
         * @param {Number} htBoundary.right
         * @param {Number} htBoundary.top
         * @param {Number} htBoundary.bottom
         * @return {Array} points [[left, top], [right, top], [right, bottom], [left, bottom]]
         */
        getBoundaryToPoints : function (htBoundary) {
            return [[htBoundary.left, htBoundary.top], [htBoundary.right, htBoundary.top], [htBoundary.right, htBoundary.bottom], [htBoundary.left, htBoundary.bottom]];
        },
        
        /**
         * 주소의 queryString을 객체화 한다
         * @return {Object}
         */
        queryString : function () {
            var htResult = {};
            
            if (location.search) {
                var aParam = location.search.substr(1).split("&");
                
                for (var i = 0, len = aParam.length; i < len; i++) {
                    var aKeyValue = aParam[i].split("=");
                    htResult[aKeyValue.shift()] = aKeyValue.join("=");
                }
            }
            
            return htResult;
        },
        
        /**
         * 객체를 복사
         * 
         * @param {Object} oSource 원본 객체
         * @return {Object}
         */
        cloneObject : function (oSource) {
            var oReturn = {};
            
            for (var i in oSource) {
                oReturn[i] = oSource[i];
            }
            
            return oReturn;
        },
        
        /**
         * zIndex에 따라 오름차순 정렬된 순서로 배열에 넣는다
         * 
         * @private
         * @param {Array} aTarget
         * @param {collie.DisplayObject} oDisplayObject
         */
        pushWithSort : function (aTarget, oDisplayObject) {
            var bAdded = false;
            
            for (var i = 0, len = aTarget.length; i < len; i++) {
                if (aTarget[i]._htOption.zIndex > oDisplayObject._htOption.zIndex) {
                    aTarget.splice(i, 0, oDisplayObject);
                    bAdded = true;
                    break;
                }
            }
            
            if (!bAdded) {
                aTarget.push(oDisplayObject);
            }
        },
        
        /**
         * DOM의 addEventListener
         * 
         * @param {HTMLElement|String} el
         * @param {String} sName 이벤트 이름, on을 제외한 이름
         * @param {Function} fHandler 바인딩할 함수
         * @param {Boolean} bUseCapture 캡쳐 사용 여부
         */
        addEventListener : function (el, sName, fHandler, bUseCapture) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            if ("addEventListener" in el) {
                el.addEventListener(sName, fHandler, bUseCapture);
            } else {
                el.attachEvent("on" + sName, fHandler, bUseCapture);
            }
        },
        
        /**
         * DOM의 removeEventListener
         * 
         * @param {HTMLElement|String} el
         * @param {String} sName 이벤트 이름, on을 제외한 이름
         * @param {Function} fHandler 바인딩할 함수
         * @param {Boolean} bUseCapture 캡쳐 사용 여부
         */
        removeEventListener : function (el, sName, fHandler, bUseCapture) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            if ("removeEventListener" in el) {
                el.removeEventListener(sName, fHandler, bUseCapture);
            } else {
                el.detachEvent("on" + sName, fHandler, bUseCapture);
            }
        },
        
        /**
         * 이벤트의 기본 동작을 멈춘다
         * 
         * @param {HTMLEvent} e
         */
        stopEventDefault : function (e) {
            e = e || window.event;
            
            if ("preventDefault" in e) {
                e.preventDefault();
            }
            
            e.returnValue = false;
        },
        
        /**
         * 엘리먼트의 위치를 구한다
         * 
         * @param {HTMLElement|String}
         * @return {Object} htResult
         * @return {Number} htResult.x 
         * @return {Number} htResult.y
         * @return {Number} htResult.width
         * @return {Number} htResult.height
         */
        getPosition : function (el) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            var elDocument = el.ownerDocument || el.document || document;
            var elHtml = elDocument.documentElement;
            var elBody = elDocument.body;
            var htPosition = {};
            
            if ("getBoundingClientRect" in el) {
                var htBox = el.getBoundingClientRect();
                htPosition.x = htBox.left;
                htPosition.x += elHtml.scrollLeft || elBody.scrollLeft;
                htPosition.y = htBox.top;
                htPosition.y += elHtml.scrollTop || elBody.scrollTop;
                htPosition.width = htBox.width;
                htPosition.height = htBox.height;
            } else {
                htPosition.x = 0;
                htPosition.y = 0;
                htPosition.width = el.offsetWidth;
                htPosition.height = el.offsetHeight;
                
                for (var o = el; o; o = o.offsetParent) {
                    htPosition.x += o.offsetLeft;
                    htPosition.y += o.offsetTop;
                }
    
                for (var o = el.parentNode; o; o = o.parentNode) {
                    if (o.tagName === 'BODY') {
                        break;
                    }
                    
                    if (o.tagName === 'TR') {
                        htPosition.y += 2;
                    }
                                        
                    htPosition.x -= o.scrollLeft;
                    htPosition.y -= o.scrollTop;
                }
            }
            
            return htPosition;
        }
    }))();
    
    // iOS에서 상단바 숨기기
    if (collie.util.getDeviceInfo().ios) {
        window.addEventListener("load", function () {
            setTimeout(function () {
                document.body.scrollTop = 0;
            }, 300);
        });
    }
    
    // bind polyfill, https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
    if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
            if (typeof this !== "function") {
                // closest thing possible to the ECMAScript 5 internal IsCallable function
                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
            }
    
            var aArgs = Array.prototype.slice.call(arguments, 1), 
                fToBind = this, 
                fNOP = function () {},
                fBound = function () {
                    return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
                };
    
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        };
    }
    
    // Implementation the dashedLine method in Canvas
    // I had fix some difference with Raphael.js
    // special thanks to Phrogz and <NAME>
    // http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
    var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;<commit_msg>#5 Translate to english at `Common.js` file. - deprecated scrollTop `0` on iOS device.<commit_after>/**
 * @namespace
 */
var collie = collie || {};

(function () {
    /**
     * It'll be replaced automatically. so you don't need edit this value.
     * @name collie.version
     */
    collie.version = "{{version}}";

    /**
     * Make a Class
     * @method collie#Class
     * @param {Object} o An Object contains members of this class. You can define the constructor with a `$init` property.
     * @param {collie.Class} [oParent] You can insert a superclass if you need to inherit.
     * @return {collie.Class}
     * @example
     * ```
     * var Person = collie.Class({
     *  gender : false,
     *  walk : function () { return "walking!"; }
     * });
     * 
     * var Male = collie.Class({
     *  name : "",
     *  gender : "male"
     * }, Person);
     * 
     * var oDavid = new Male();
     * oDavid.name = "david";
     * 
     * alert(oDavid.name); // david
     * alert(oDavid.gender); // male
     * alert(oDavid.walk()); // walking!
     * ```
     * @example
     * override
     * ```
     * var Person = collie.Class({
     *  testMethod : function () {
     *  
     *  }
     * });
     * 
     * var Male = collie.Class({
     *  testMethod : function () {
     *      // blah
     *      this.constructor.$super.testMethod.apply(this, arguments);      
     *  }
     * }, Person);
     * ```
     * @example
     * You can also create an instance without 'new' keyword
     * ```
     * var Person = collie.Class({
     *  $init : function () {
     *  },
     *  test : function () {
     *  }
     * });
     * 
     * var a = new Person();
     * var b = Person(); // It works fine!
     * ```
     */
    collie.Class = function (o, oParent) {
        var $init = null;
        var checkDirectCall = function () { return true; };
        var F;
        
        if ("$init" in o) {
            $init = o.$init;
            delete o.$init;
        }
        
        if (typeof oParent === "undefined") {
            F = function () {
                var args = arguments;
                
                if (!(this instanceof F)) {
                    return new F(checkDirectCall, args);
                }
                
                if (args.length && args[0] === checkDirectCall) {
                    args = args[1];
                }
                
                if ($init !== null) {
                    $init.apply(this, args);
                }
            };
        } else {
            F = function () {
                var args = arguments;
    
                if (!(this instanceof F)) {
                    return new F(checkDirectCall, args);
                }
                
                if (args.length && args[0] === checkDirectCall) {
                    args = args[1];
                }
                
                // runs constructor of a superclass
                oParent.apply(this, args);
                
                // runs constructor of current class
                if ($init !== null) {
                    $init.apply(this, args);
                }
            };
            
            var Parent = function () {};
            Parent.prototype = oParent.prototype;
            F.$super = oParent.prototype;
            F.prototype = new Parent();
            F.prototype.constructor = F;
        }
        
        for (var i in o) {
            if (o.hasOwnProperty(i) && i !== "prototype") {
                F.prototype[i] = o[i];
            }
        }
        
        return F;
    };
    
    /**
     * utilities
     * @namespace
     */
    collie.util = new (collie.Class(/** @lends collie.util */{
        $init : function () {
            this._sCSSPrefix = null;
            this._htDeviceInfo = null;
            this._bSupport3d = null;
            this._bSupportCSS3 = null;
            this._htBoundary = {
                left : 0,
                right : 0,
                top : 0,
                bottom : 0
            };
        },
        
        /**
         * Get an instance of DisplayObject by Id
         * It mainly be used with DOM rendering.
         * 
         * @param {Number} nId
         * @return {collie.DisplayObject}
         */
        getDisplayObjectById : function (nId) {
            return collie.DisplayObject.htFactory[nId];
        },
        
        /**
         * Get an instance of DisplayObject by name
         * 
         * @param {String} sName
         * @return {collie.DisplayObject}
         */
        getDisplayObjectByName : function (sName) {
            for (var i in collie.DisplayObject.htFactory) {
                if (collie.DisplayObject.htFactory[i].get("name") === sName) {
                    return collie.DisplayObject.htFactory[i];
                }
            }
            
            return false;
        },
        
        /**
         * Returns an information of current device by an user-agent value.
         * 
         * @return {Object} htInfo
         * @return {Boolean} htInfo.desktop whether it's a desktop or not
         * @return {Boolean} htInfo.supportCanvas whether it supports CANVAS rendering or not
         * @return {Boolean|Number} htInfo.android An Android version to two demical place. If It's not an android, It'll be false.
         * @return {Boolean|Number} htInfo.ios iOS version. same as `htInfo.android`
         * @return {Boolean|Number} htInfo.ie IE version. same as `htInfo.android`.
         * @return {Boolean|Number} htInfo.chrome whether an user-agent value contains `chrome` or not.
         */
        getDeviceInfo : function (sAgent) {
            if (this._htDeviceInfo !== null && typeof sAgent === "undefined") {
                return this._htDeviceInfo;
            }
            
            var aMat = null;
            var bIsDesktop = false;
            var bSupportCanvas = typeof CanvasRenderingContext2D !== "undefined";
            var bIsAndroid = false;
            var bIsIOS = false;
            var bIsIE = false;
            var bHasChrome = (/chrome/i.test(sAgent)) ? true : false;
            var bHiggs = (/Higgs/i.test(sAgent))? true: false; // The Higgs is a browser engine made by Naver Corporation
            var sAgent = sAgent || navigator.userAgent;
            var nVersion = 0;
            
            if (/android/i.test(sAgent)) { // android
                bIsAndroid = true;
                aMat = sAgent.toString().match(/android ([0-9]\.[0-9])\.?([0-9]?)/i);
                
                if (aMat && aMat[1]) {
                    nVersion = (parseFloat(aMat[1]) + (aMat[2] ? aMat[2] * 0.01 : 0)).toFixed(2);
                }
            } else if (/(iphone|ipad|ipod)/i.test(sAgent)) { // iOS
                bIsIOS = true;
                aMat = sAgent.toString().match(/([0-9]_[0-9])/i);
                
                if (aMat && aMat[1]) {
                    nVersion = parseFloat(aMat[1].replace(/_/, '.'));
                }
            } else { // PC
                bIsDesktop = true;
                
                if (/(MSIE)/i.test(sAgent)) { // IE
                    bIsIE = true;
                    aMat = sAgent.toString().match(/MSIE ([0-9])/i);
                    
                    if (aMat && aMat[1]) {
                        nVersion = parseInt(aMat[1], 10);
                    }
                }
            }
            
            this._htDeviceInfo = {
                supportCanvas : bSupportCanvas,
                desktop : bIsDesktop,
                android : bIsAndroid ? nVersion : false,
                ios : bIsIOS ? nVersion : false,
                ie : bIsIE ? nVersion : false,
                chrome : bHasChrome,
                higgs : bHiggs
            };
            
            return this._htDeviceInfo;
        },
        
        /**
         * Returns CSS vendor prefix
         * 
         * @param {String} [sName] target CSS property name(included `-`). If It's an empty value, this method'll return only prefix.
         * @param {Boolean} bJavascript you'll set this value as true if you want to use at javascript.
         * @example
         * ```
         * collie.util.getCSSPrefix("transform"); // -webkit-transform
         * collie.util.getCSSPrefix("transform", true); // webkitTransform
         * ```
         * @return {String} CSS Vendor prefix
         */
        getCSSPrefix : function (sName, bJavascript) {
            var sResult = '';
            
            if (this._sCSSPrefix === null) {
                this._sCSSPrefix = '';
                
                if (typeof document.body.style.webkitTransform !== "undefined") {
                    this._sCSSPrefix = "-webkit-";
                } else if (typeof document.body.style.MozTransform !== "undefined") {
                    this._sCSSPrefix = "-moz-";
                } else if (typeof document.body.style.OTransform !== "undefined") {
                    this._sCSSPrefix = "-o-";
                } else if (typeof document.body.style.msTransform !== "undefined") {
                    this._sCSSPrefix = "-ms-";
                }
            }
            
            sResult = this._sCSSPrefix + (sName ? sName : '');
            
            if (bJavascript) {
                var aTmp = sResult.split("-");
                sResult = '';
                
                for (var i = 0, len = aTmp.length; i < len; i++) {
                    if (aTmp[i]) {
                        sResult += sResult ? aTmp[i].substr(0, 1).toUpperCase() + aTmp[i].substr(1) : aTmp[i];
                    }
                }
                
                if (this._sCSSPrefix === "-moz-" || this._sCSSPrefix === "-o-") {
                    sResult = sResult.substr(0, 1).toUpperCase() + sResult.substr(1);
                }
            }
            
            return sResult;
        },
        
        /**
         * Whether current device supports CSS3 or not
         * @return {Boolean}
         */
        getSupportCSS3 : function () {
            if (this._bSupportCSS3 === null) {
                this._bSupportCSS3 = typeof document.body.style[collie.util.getCSSPrefix("transform", true)] !== "undefined" || typeof document.body.style.transform != "undefined";
            }
            
            return this._bSupportCSS3;
        },
        
        /**
         * Whether current device supports CSS3D or not
         * @return {Boolean}
         */
        getSupportCSS3d : function () {
            if (this._bSupport3d === null) {
                this._bSupport3d = (typeof document.body.style[collie.util.getCSSPrefix("perspective", true)] !== "undefined" || typeof document.body.style.perspective != "undefined") && (!collie.util.getDeviceInfo().android || collie.util.getDeviceInfo().android >= 4);
            }
            
            return this._bSupport3d;
        },
        
        /**
         * Converts Degree to Radian
         * @param {Number} nDeg
         * @return {Number}
         */
        toRad : function (nDeg) {
            return nDeg * Math.PI / 180;
        },
        
        /**
         * Converts Radian to Degree
         * @param {Number} nRad
         * @return {Number}
         */
        toDeg : function (nRad) {
            return nRad * 180 / Math.PI;
        },
        
        /**
         * Get an approximate value to avoid a floating value bug.
         * @param {Number} nValue
         * @return {Number}
         */
        approximateValue : function (nValue) {
            return Math.round(nValue * 10000000) / 10000000;
        },
        
        /**
         * Fixed degree value between 0 and 360
         * @param {Number} nAngleRad radian angle
         * @return {Number}
         */
        fixAngle : function (nAngleRad) {
            var nAngleDeg = collie.util.toDeg(nAngleRad);
            nAngleDeg -= Math.floor(nAngleDeg / 360) * 360;
            return collie.util.toRad(nAngleDeg);
        },
        
        /**
         * Returns distance between two points
         * @param {Number} x1
         * @param {Number} y1
         * @param {Number} x2
         * @param {Number} y2
         * @return {Number}
         */
        getDistance : function (x1, y1, x2, y2) {
            return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
        },
        
        /**
         * Returns a minimum boundary
         * 
         * @param {Array} aPoints target array [[x1, y1], [x2, y2], ... ]
         * @return {Object} htResult
         * @return {Number} htResult.left
         * @return {Number} htResult.right
         * @return {Number} htResult.bottom
         * @return {Number} htResult.top
         */
        getBoundary : function (aPoints) {
            var nMinX = aPoints[0][0];
            var nMaxX = aPoints[0][0];
            var nMinY = aPoints[0][1];
            var nMaxY = aPoints[0][1];
            
            for (var i = 1, len = aPoints.length; i < len; i++) {
                nMinX = Math.min(nMinX, aPoints[i][0]);
                nMaxX = Math.max(nMaxX, aPoints[i][0]);
                nMinY = Math.min(nMinY, aPoints[i][1]);
                nMaxY = Math.max(nMaxY, aPoints[i][1]);
            }
            
            return {
                left : nMinX,
                right : nMaxX,
                top : nMinY,
                bottom : nMaxY
            };
        },
        
        /**
         * Converts boundary to points
         * @param {Object} htBoundary
         * @param {Number} htBoundary.left
         * @param {Number} htBoundary.right
         * @param {Number} htBoundary.top
         * @param {Number} htBoundary.bottom
         * @return {Array} points [[left, top], [right, top], [right, bottom], [left, bottom]]
         */
        getBoundaryToPoints : function (htBoundary) {
            return [[htBoundary.left, htBoundary.top], [htBoundary.right, htBoundary.top], [htBoundary.right, htBoundary.bottom], [htBoundary.left, htBoundary.bottom]];
        },
        
        /**
         * 주소의 queryString을 객체화 한다
         * @return {Object}
         */
        queryString : function () {
            var htResult = {};
            
            if (location.search) {
                var aParam = location.search.substr(1).split("&");
                
                for (var i = 0, len = aParam.length; i < len; i++) {
                    var aKeyValue = aParam[i].split("=");
                    htResult[aKeyValue.shift()] = aKeyValue.join("=");
                }
            }
            
            return htResult;
        },
        
        /**
         * Clone an object
         * @param {Object} oSource source object
         * @return {Object}
         */
        cloneObject : function (oSource) {
            var oReturn = {};
            
            for (var i in oSource) {
                oReturn[i] = oSource[i];
            }
            
            return oReturn;
        },
        
        /**
         * Insert to target array sorted `zIndex` value on DisplayObject
         * @private
         * @param {Array} aTarget Target array
         * @param {collie.DisplayObject} oDisplayObject
         */
        pushWithSort : function (aTarget, oDisplayObject) {
            var bAdded = false;
            
            for (var i = 0, len = aTarget.length; i < len; i++) {
                if (aTarget[i]._htOption.zIndex > oDisplayObject._htOption.zIndex) {
                    aTarget.splice(i, 0, oDisplayObject);
                    bAdded = true;
                    break;
                }
            }
            
            if (!bAdded) {
                aTarget.push(oDisplayObject);
            }
        },
        
        /**
         * element.addEventListener for cross-browsing
         * @param {HTMLElement|String} el
         * @param {String} sName event name excluded `on` keyword.
         * @param {Function} fHandler Event handler
         * @param {Boolean} bUseCapture
         */
        addEventListener : function (el, sName, fHandler, bUseCapture) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            if ("addEventListener" in el) {
                el.addEventListener(sName, fHandler, bUseCapture);
            } else {
                el.attachEvent("on" + sName, fHandler, bUseCapture);
            }
        },
        
        /**
         * element.removeEventListener for cross-browsing
         * @param {HTMLElement|String} el
         * @param {String} sName event name excluded `on` keyword.
         * @param {Function} fHandler Event handler
         * @param {Boolean} bUseCapture
         */
        removeEventListener : function (el, sName, fHandler, bUseCapture) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            if ("removeEventListener" in el) {
                el.removeEventListener(sName, fHandler, bUseCapture);
            } else {
                el.detachEvent("on" + sName, fHandler, bUseCapture);
            }
        },
        
        /**
         * `event.preventDefault` method for cross-browsing
         * @param {HTMLEvent} e
         */
        stopEventDefault : function (e) {
            e = e || window.event;
            
            if ("preventDefault" in e) {
                e.preventDefault();
            }
            
            e.returnValue = false;
        },
        
        /**
         * Get an position of a target element
         * @param {HTMLElement|String} target element
         * @return {Object} htResult
         * @return {Number} htResult.x 
         * @return {Number} htResult.y
         * @return {Number} htResult.width
         * @return {Number} htResult.height
         */
        getPosition : function (el) {
            if (typeof el === "string") {
                el = document.getElementById(el);
            }
            
            var elDocument = el.ownerDocument || el.document || document;
            var elHtml = elDocument.documentElement;
            var elBody = elDocument.body;
            var htPosition = {};
            
            if ("getBoundingClientRect" in el) {
                var htBox = el.getBoundingClientRect();
                htPosition.x = htBox.left;
                htPosition.x += elHtml.scrollLeft || elBody.scrollLeft;
                htPosition.y = htBox.top;
                htPosition.y += elHtml.scrollTop || elBody.scrollTop;
                htPosition.width = htBox.width;
                htPosition.height = htBox.height;
            } else {
                htPosition.x = 0;
                htPosition.y = 0;
                htPosition.width = el.offsetWidth;
                htPosition.height = el.offsetHeight;
                
                for (var o = el; o; o = o.offsetParent) {
                    htPosition.x += o.offsetLeft;
                    htPosition.y += o.offsetTop;
                }
    
                for (var o = el.parentNode; o; o = o.parentNode) {
                    if (o.tagName === 'BODY') {
                        break;
                    }
                    
                    if (o.tagName === 'TR') {
                        htPosition.y += 2;
                    }
                                        
                    htPosition.x -= o.scrollLeft;
                    htPosition.y -= o.scrollTop;
                }
            }
            
            return htPosition;
        }
    }))();
    
    // bind polyfill, https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
    if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
            if (typeof this !== "function") {
                // closest thing possible to the ECMAScript 5 internal IsCallable function
                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
            }
    
            var aArgs = Array.prototype.slice.call(arguments, 1), 
                fToBind = this, 
                fNOP = function () {},
                fBound = function () {
                    return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
                };
    
            fNOP.prototype = this.prototype;
            fBound.prototype = new fNOP();
            return fBound;
        };
    }
    
    // Implementation the dashedLine method in Canvas
    // I had fix some difference with Raphael.js
    // special thanks to Phrogz and <NAME>
    // http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
    var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
{'fraction_non_alphanumeric': 0.11784560489898259, 'fraction_numerical': 0.006822894064323255, 'mean_word_length': 1.2142208936102066, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '565378', 'n_tokens_mistral': 12361, 'n_tokens_neox': 12211, 'n_words': 2646}