
var TextAreaEditor = makeClass();
TextAreaEditor.prototype = {
    
    init : function(ta_dom) {
        this.ta = ta_dom;
        this.registerMeasure();
    },

    registerMeasure : function() {
        this.measure = $("#measure")[0];
        if (!this.measure) {
            var self = this;
            setTimeout(function() {
                self.registerMeasure();
            }, 1000);
        }
    },
    
    getCaretY : function() {
        $(this.measure).height(10).width($(this.ta).width()).val(this.ta.value.substring(0, this.ta.selectionStart));
        return this.measure.scrollHeight;
    },
    
    updateScrollTop : function() {
        // キャレットが見える位置に無かったら見えるようにスクロールバーを調整する
        // scrollHeight = height + scrollTopのMax
        // 見えてる範囲は scrollTop から scrollTop + height
        var caretY = this.getCaretY();
        var lineheight = this.getLineHeight();

        if (caretY < this.ta.scrollTop + lineheight) {
            this.ta.scrollTop = caretY - lineheight;
        }
        else if (caretY > this.ta.scrollTop + $(this.ta).height()) {
            this.ta.scrollTop = caretY - $(this.ta).height();
        }
    }, 
    
    selected : function() {
        return this.ta.selectionEnd - this.ta.selectionStart !== 0;
    },
    
    forward : function() {
        if (this.ta_selectionEnd == this.ta.value.length) return;
        var pos = this.ta.selectionEnd + (this.ta.value.substr(this.ta.selectionEnd, 2) === "\r\n" ? 2 : 1);
        this.ta.selectionEnd = pos;
        this.ta.selectionStart = pos;
    },
    
    back : function() {
        var pos = this.ta.selectionStart - 1;
        this.ta.selectionStart = pos;
        this.ta.selectionEnd = pos;
    },
    
    deleteSelection : function() {
        var start = this.ta.selectionStart;
        var end = this.ta.selectionEnd;
        var v = this.ta.value;
        this.ta.value = v.substr(0, start) + v.substr(end);
        this.ta.selectionEnd = start;
    },
    
    insert : function(str) {
        if (this.selected(this.ta)) this.deleteSelection(this.ta);
        
        var v = this.ta.value;
        var start = this.ta.selectionStart;
        this.ta.value = v.substr(0, start) + str + v.substr(start);
        var i = this.ta.value.substr(start, 2) === "\r\n" ? 2 : str.length;
        this.ta.selectionStart = start + i;
        this.ta.selectionEnd = start + i;
    },
    
    backspace : function() {
        if (this.selected(this.ta)) {
            this.deleteSelection(this.ta);
        }
        else if (this.ta.selectionStart > 0) {
            var v = this.ta.value;
            var start = this.ta.selectionStart;
            var i = v.substr(start - 2, 2) === "\r\n" ? 2 : 1;
            this.ta.value = v.substr(0, start - i) + v.substr(start);
            this.ta.selectionEnd = this.ta.selectionStart = start - i;
        }
    },
    
    backFor : function(callback) {
        if (this.selected(this.ta)) {
            this.ta.selectionEnd = this.ta.selectionStart;
        }
        
        for (var pos = this.ta.selectionEnd; pos > 0; pos--) {
            if (callback(this.ta.value[pos - 1])) break;
        }
        
        this.ta.selectionStart = pos;
        this.ta.selectionEnd = pos;
    },
    
    forwardFor : function(callback) {
        if (this.selected(this.ta)) {
            this.ta.selectionStart = this.ta.selectionEnd;
        }
        
        for (var pos = this.ta.selectionEnd, len = this.ta.value.length; pos < len; pos++) {
            if (callback(this.ta.value[pos])) break;
        }
        
        this.ta.selectionEnd = pos;
        this.ta.selectionStart = pos;
    },
    
    searchLineStart : function(start) {
        for (;start > 0; start--) {
            if (this.ta.value[start - 1] === "\n") break;
        }
        return start; 
    },
    
    searchLineEnd : function(start) {
        start++;
        for (var len = this.ta.value.length; start < len; start++) {
            if (this.ta.value[start - 1] === "\n" ) break;
        }
        return start;
    },
    
    killLine : function() {
        var pos = this.ta.selectionStart;
        var start = this.searchLineStart(pos);
        this.ta.value = this.ta.value.substr(0, start) + this.ta.value.substr(this.searchLineEnd(pos));
        this.ta.selectionStart = start;
        this.ta.selectionEnd = start;
    },
    
    nextLine : function() {
        var i = this.searchLineEnd(this.ta.selectionEnd);
        this.ta.selectionEnd = i;
        this.ta.selectionStart = i;
    },
    
    previousLine : function() {
        var i = this.searchLineStart(this.ta.selectionStart) - 1;
        this.ta.selectionStart = i;
        this.ta.selectionEnd = i;
        return false;
    },
    
    getLineHeight : function() {
        var result = /^(\d+(\.\d*)?)px$/.exec($(this.ta).css("line-height"));
        if (!result) return 0;
        return parseFloat(result[1]);
    },
    
    "C-a" : function() {
        this.backFor(function(c) {
            return c === "\n";
        });
    },
    "C-b" : function() {
        this.back();
        if (/[\r\n]/.test(this.ta.value[this.ta.selectionStart])) { 
            this.updateScrollTop();
        }
    },
    "C-e" : function() {
        this.forwardFor(function(c) {
            return c === "\n";
        });
    },
    "C-f" : function() {
        this.forward();
        if (/[\r\n]/.test(this.ta.value[this.ta.selectionStart - 1])) {
            this.updateScrollTop();
        }
    },
    "C-h" : function() {
        var top = this.ta.scrollTop;
        this.backspace();
        this.updateScrollTop();
    },
    "C-k" : function() {
        var top = this.ta.scrollTop;
        this.killLine();
        this.updateScrollTop();
    },
    "C-m" : function() {
        var top = this.ta.scrollTop;
        this.insert("\n");
        this.updateScrollTop();
    },
    "C-n" : function() {
        this.nextLine();
        this.updateScrollTop();
    },
    "C-p" : function() {
        this.previousLine();
        this.updateScrollTop();
    },
    
    dispatchKeyEvent : function(e) {
        if (!e.ctrlKey || e.altKey) return;
        var key = "C-" + String.fromCharCode(e.keyCode).toLowerCase();

        if (key in this) {
            e.preventDefault();
            e.stopPropagation();
            
            var o = e.originalEvent;

            this[key]();
            return false;
        }
    }
}




