/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2010, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function (require, exports, module) {
    "use strict";

    var event = require("../lib/event");
    var useragent = require("../lib/useragent");
    var dom = require("../lib/dom");
    var lang = require("../lib/lang");
    var BROKEN_SETDATA = useragent.isChrome < 18;
    var USE_IE_MIME_TYPE = useragent.isIE;

    var TextInput = function (parentNode, host) {
        var text = dom.createElement("textarea");
        text.className = "ace_text-input";

        if (useragent.isTouchPad)
            text.setAttribute("x-palm-disable-auto-cap", true);

        text.setAttribute("wrap", "off");
        text.setAttribute("autocorrect", "off");
        text.setAttribute("autocapitalize", "off");
        text.setAttribute("spellcheck", false);

        text.style.opacity = "0";
        if (useragent.isOldIE) text.style.top = "-1000px";
        parentNode.insertBefore(text, parentNode.firstChild);

        var PLACEHOLDER = "\x01\x01";

        var copied = false;
        var pasted = false;
        var inComposition = false;
        var tempStyle = '';
        var isSelectionEmpty = true;

        // FOCUS
        // ie9 throws error if document.activeElement is accessed too soon
        try {
            var isFocused = document.activeElement === text;
        } catch (e) {
        }

        event.addListener(text, "blur", function (e) {
            host.onBlur(e);
            isFocused = false;
        });
        event.addListener(text, "focus", function (e) {
            isFocused = true;
            host.onFocus(e);
            resetSelection();
        });
        this.focus = function () {
            if (tempStyle) return text.focus();
            var top = text.style.top;
            text.style.position = "fixed";
            text.style.top = "0px";
            text.focus();
            setTimeout(function () {
                text.style.position = "";
                if (text.style.top == "0px")
                    text.style.top = top;
            }, 0);
        };
        this.blur = function () {
            text.blur();
        };
        this.isFocused = function () {
            return isFocused;
        };

        // modifying selection of blured textarea can focus it (chrome mac/linux)
        var syncSelection = lang.delayedCall(function () {
            isFocused && resetSelection(isSelectionEmpty);
        });
        var syncValue = lang.delayedCall(function () {
            if (!inComposition) {
                text.value = PLACEHOLDER;
                isFocused && resetSelection();
            }
        });

        function resetSelection(isEmpty) {
            if (inComposition)
                return;

            // this prevents infinite recursion on safari 8
            // see https://github.com/ajaxorg/ace/issues/2114
            inComposition = true;

            if (inputHandler) {
                selectionStart = 0;
                selectionEnd = isEmpty ? 0 : text.value.length - 1;
            } else {
                var selectionStart = isEmpty ? 2 : 1;
                var selectionEnd = 2;
            }
            // on firefox this throws if textarea is hidden
            try {
                text.setSelectionRange(selectionStart, selectionEnd);
            } catch (e) {
            }

            inComposition = false;
        }

        function resetValue() {
            if (inComposition)
                return;
            text.value = PLACEHOLDER;
            //http://code.google.com/p/chromium/issues/detail?id=76516
            if (useragent.isWebKit)
                syncValue.schedule();
        }

        useragent.isWebKit || host.addEventListener('changeSelection', function () {
            if (host.selection.isEmpty() != isSelectionEmpty) {
                isSelectionEmpty = !isSelectionEmpty;
                syncSelection.schedule();
            }
        });

        resetValue();
        if (isFocused)
            host.onFocus();


        var isAllSelected = function (text) {
            return text.selectionStart === 0 && text.selectionEnd === text.value.length;
        };
        // IE8 does not support setSelectionRange
        if (!text.setSelectionRange && text.createTextRange) {
            text.setSelectionRange = function (selectionStart, selectionEnd) {
                var range = this.createTextRange();
                range.collapse(true);
                range.moveStart('character', selectionStart);
                range.moveEnd('character', selectionEnd);
                range.select();
            };
            isAllSelected = function (text) {
                try {
                    var range = text.ownerDocument.selection.createRange();
                } catch (e) {
                }
                if (!range || range.parentElement() != text) return false;
                return range.text == text.value;
            }
        }
        if (useragent.isOldIE) {
            var inPropertyChange = false;
            var onPropertyChange = function (e) {
                if (inPropertyChange)
                    return;
                var data = text.value;
                if (inComposition || !data || data == PLACEHOLDER)
                    return;
                // can happen either after delete or during insert operation
                if (e && data == PLACEHOLDER[0])
                    return syncProperty.schedule();

                sendText(data);
                // ie8 calls propertychange handlers synchronously!
                inPropertyChange = true;
                resetValue();
                inPropertyChange = false;
            };
            var syncProperty = lang.delayedCall(onPropertyChange);
            event.addListener(text, "propertychange", onPropertyChange);

            var keytable = {13: 1, 27: 1};
            event.addListener(text, "keyup", function (e) {
                if (inComposition && (!text.value || keytable[e.keyCode]))
                    setTimeout(onCompositionEnd, 0);
                if ((text.value.charCodeAt(0) || 0) < 129) {
                    return syncProperty.call();
                }
                inComposition ? onCompositionUpdate() : onCompositionStart();
            });
            // when user presses backspace after focusing the editor
            // propertychange isn't called for the next character
            event.addListener(text, "keydown", function (e) {
                syncProperty.schedule(50);
            });
        }

        var onSelect = function (e) {
            if (copied) {
                copied = false;
            } else if (isAllSelected(text)) {
                host.selectAll();
                resetSelection();
            } else if (inputHandler) {
                resetSelection(host.selection.isEmpty());
            }
        };

        var inputHandler = null;
        this.setInputHandler = function (cb) {
            inputHandler = cb
        };
        this.getInputHandler = function () {
            return inputHandler
        };
        var afterContextMenu = false;

        var sendText = function (data) {
            if (inputHandler) {
                data = inputHandler(data);
                inputHandler = null;
            }
            if (pasted) {
                resetSelection();
                if (data)
                    host.onPaste(data);
                pasted = false;
            } else if (data == PLACEHOLDER.charAt(0)) {
                if (afterContextMenu)
                    host.execCommand("del", {source: "ace"});
                else // some versions of android do not fire keydown when pressing backspace
                    host.execCommand("backspace", {source: "ace"});
            } else {
                if (data.substring(0, 2) == PLACEHOLDER)
                    data = data.substr(2);
                else if (data.charAt(0) == PLACEHOLDER.charAt(0))
                    data = data.substr(1);
                else if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
                    data = data.slice(0, -1);
                // can happen if undo in textarea isn't stopped
                if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
                    data = data.slice(0, -1);

                if (data)
                    host.onTextInput(data);
            }
            if (afterContextMenu)
                afterContextMenu = false;
        };
        var onInput = function (e) {
            // console.log("onInput", inComposition)
            if (inComposition)
                return;
            var data = text.value;
            sendText(data);
            resetValue();
        };

        var handleClipboardData = function (e, data, forceIEMime) {
            var clipboardData = e.clipboardData || window.clipboardData;
            if (!clipboardData || BROKEN_SETDATA)
                return;
            // using "Text" doesn't work on old webkit but ie needs it
            var mime = USE_IE_MIME_TYPE || forceIEMime ? "Text" : "text/plain";
            try {
                if (data) {
                    // Safari 5 has clipboardData object, but does not handle setData()
                    return clipboardData.setData(mime, data) !== false;
                } else {
                    return clipboardData.getData(mime);
                }
            } catch (e) {
                if (!forceIEMime)
                    return handleClipboardData(e, data, true);
            }
        };

        var doCopy = function (e, isCut) {
            var data = host.getCopyText();
            if (!data)
                return event.preventDefault(e);

            if (handleClipboardData(e, data)) {
                isCut ? host.onCut() : host.onCopy();
                event.preventDefault(e);
            } else {
                copied = true;
                text.value = data;
                text.select();
                setTimeout(function () {
                    copied = false;
                    resetValue();
                    resetSelection();
                    isCut ? host.onCut() : host.onCopy();
                });
            }
        };

        var onCut = function (e) {
            doCopy(e, true);
        };

        var onCopy = function (e) {
            doCopy(e, false);
        };

        var onPaste = function (e) {
            var data = handleClipboardData(e);
            if (typeof data == "string") {
                if (data)
                    host.onPaste(data, e);
                if (useragent.isIE)
                    setTimeout(resetSelection);
                event.preventDefault(e);
            }
            else {
                text.value = "";
                pasted = true;
            }
        };

        event.addCommandKeyListener(text, host.onCommandKey.bind(host));

        event.addListener(text, "select", onSelect);

        event.addListener(text, "input", onInput);

        event.addListener(text, "cut", onCut);
        event.addListener(text, "copy", onCopy);
        event.addListener(text, "paste", onPaste);


        // Opera has no clipboard events
        if (!('oncut' in text) || !('oncopy' in text) || !('onpaste' in text)) {
            event.addListener(parentNode, "keydown", function (e) {
                if ((useragent.isMac && !e.metaKey) || !e.ctrlKey)
                    return;

                switch (e.keyCode) {
                    case 67:
                        onCopy(e);
                        break;
                    case 86:
                        onPaste(e);
                        break;
                    case 88:
                        onCut(e);
                        break;
                }
            });
        }


        // COMPOSITION
        var onCompositionStart = function (e) {
            if (inComposition || !host.onCompositionStart || host.$readOnly)
                return;
            // console.log("onCompositionStart", inComposition)
            inComposition = {};
            inComposition.canUndo = host.session.$undoManager;
            host.onCompositionStart();
            setTimeout(onCompositionUpdate, 0);
            host.on("mousedown", onCompositionEnd);
            if (inComposition.canUndo && !host.selection.isEmpty()) {
                host.insert("");
                host.session.markUndoGroup();
                host.selection.clearSelection();
            }
            host.session.markUndoGroup();
        };

        var onCompositionUpdate = function () {
            // console.log("onCompositionUpdate", inComposition && JSON.stringify(text.value))
            if (!inComposition || !host.onCompositionUpdate || host.$readOnly)
                return;
            var val = text.value.replace(/\x01/g, "");
            if (inComposition.lastValue === val) return;

            host.onCompositionUpdate(val);
            if (inComposition.lastValue)
                host.undo();
            if (inComposition.canUndo)
                inComposition.lastValue = val;
            if (inComposition.lastValue) {
                var r = host.selection.getRange();
                host.insert(inComposition.lastValue);
                host.session.markUndoGroup();
                inComposition.range = host.selection.getRange();
                host.selection.setRange(r);
                host.selection.clearSelection();
            }
        };

        var onCompositionEnd = function (e) {
            if (!host.onCompositionEnd || host.$readOnly) return;
            // console.log("onCompositionEnd", inComposition &&inComposition.lastValue)
            var c = inComposition;
            inComposition = false;
            var timer = setTimeout(function () {
                timer = null;
                var str = text.value.replace(/\x01/g, "");
                // console.log(str, c.lastValue)
                if (inComposition)
                    return;
                else if (str == c.lastValue)
                    resetValue();
                else if (!c.lastValue && str) {
                    resetValue();
                    sendText(str);
                }
            });
            inputHandler = function compositionInputHandler(str) {
                // console.log("onCompositionEnd", str, c.lastValue)
                if (timer)
                    clearTimeout(timer);
                str = str.replace(/\x01/g, "");
                if (str == c.lastValue)
                    return "";
                if (c.lastValue && timer)
                    host.undo();
                return str;
            };
            host.onCompositionEnd();
            host.removeListener("mousedown", onCompositionEnd);
            if (e.type == "compositionend" && c.range) {
                host.selection.setRange(c.range);
            }
            // Workaround for #3027, #3045, #3097, #3100
            if (useragent.isChrome && useragent.isChrome >= 53) {
                onInput();
            }
        };


        var syncComposition = lang.delayedCall(onCompositionUpdate, 50);

        event.addListener(text, "compositionstart", onCompositionStart);
        if (useragent.isGecko) {
            event.addListener(text, "text", function () {
                syncComposition.schedule()
            });
        } else {
            event.addListener(text, "keyup", function () {
                syncComposition.schedule()
            });
            event.addListener(text, "keydown", function () {
                syncComposition.schedule()
            });
        }
        event.addListener(text, "compositionend", onCompositionEnd);

        this.getElement = function () {
            return text;
        };

        this.setReadOnly = function (readOnly) {
            text.readOnly = readOnly;
        };

        this.onContextMenu = function (e) {
            afterContextMenu = true;
            resetSelection(host.selection.isEmpty());
            host._emit("nativecontextmenu", {target: host, domEvent: e});
            this.moveToMouse(e, true);
        };

        this.moveToMouse = function (e, bringToFront) {
            if (!bringToFront && useragent.isOldIE)
                return;
            if (!tempStyle)
                tempStyle = text.style.cssText;
            text.style.cssText = (bringToFront ? "z-index:100000;" : "")
                + "height:" + text.style.height + ";"
                + (useragent.isIE ? "opacity:0.1;" : "");

            var rect = host.container.getBoundingClientRect();
            var style = dom.computedStyle(host.container);
            var top = rect.top + (parseInt(style.borderTopWidth) || 0);
            var left = rect.left + (parseInt(rect.borderLeftWidth) || 0);
            var maxTop = rect.bottom - top - text.clientHeight - 2;
            var move = function (e) {
                text.style.left = e.clientX - left - 2 + "px";
                text.style.top = Math.min(e.clientY - top - 2, maxTop) + "px";
            };
            move(e);

            if (e.type != "mousedown")
                return;

            if (host.renderer.$keepTextAreaAtCursor)
                host.renderer.$keepTextAreaAtCursor = null;

            clearTimeout(closeTimeout);
            // on windows context menu is opened after mouseup
            if (useragent.isWin && !useragent.isOldIE)
                event.capture(host.container, move, onContextMenuClose);
        };

        this.onContextMenuClose = onContextMenuClose;
        var closeTimeout;

        function onContextMenuClose() {
            clearTimeout(closeTimeout);
            closeTimeout = setTimeout(function () {
                if (tempStyle) {
                    text.style.cssText = tempStyle;
                    tempStyle = '';
                }
                if (host.renderer.$keepTextAreaAtCursor == null) {
                    host.renderer.$keepTextAreaAtCursor = true;
                    host.renderer.$moveTextAreaToCursor();
                }
            }, useragent.isOldIE ? 200 : 0);
        }

        var onContextMenu = function (e) {
            host.textInput.onContextMenu(e);
            onContextMenuClose();
        };
        event.addListener(text, "mouseup", onContextMenu);
        event.addListener(text, "mousedown", function (e) {
            e.preventDefault();
            onContextMenuClose();
        });
        event.addListener(host.renderer.scroller, "contextmenu", onContextMenu);
        event.addListener(text, "contextmenu", onContextMenu);
    };

    exports.TextInput = TextInput;
});
