define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "ui", "tabManager", "ace", "language",
        "menus", "commands", "c9", "tabManager",
        "settings", "language.jumptodef"
    ];
    main.provides = ["language.quickfix"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var tabs = imports.tabManager;
        var language = imports.language;
        var commands = imports.commands;
        var jumptodef = imports["language.jumptodef"];
        var plugin = new Plugin("Ajax.org", main.consumes);
        
        var CRASHED_JOB_TIMEOUT = 30000;
        
        var worker;
        
        var loaded;
        function load() {
            if (loaded) return;
            loaded = true;
            
            commands.on("update", function() {
                var key = commands.getPrettyHotkey("quickfix");
                
                language.getWorker(function(err, result) {
                    if (err) return console.error(err);
                    worker = result;
                    
                    worker.emit("quickfix_key", { data: key });
                });
            });
            
            commands.addCommand({
                name: "quickfix",
                hint: "quickfix",
                bindKey: { mac: "Command-F3", win: "Ctrl-F3" },
                exec: function(editor) {
                    invoke();
                }
            }, plugin);
            
            language.getWorker(function(err, result) {
                if (err) console.error(err);
                worker = result;
                
                worker.on("quickfixes_result", onResult);
            });
            
            /*
            menus.addItemByPath("Tools/Quickfix", new apf.item({
                caption: "Quickfix",
                command: "quickfix"
            }), 20001, plugin)
            */
        }
    
        function invoke() {
            if (!tabs.focussedTab || !tabs.focussedTab.editor || !tabs.focussedTab.editor.ace)
                return;
            
            var tab = tabs.focussedTab;
            var ace = tab.editor.ace;
            var sel = ace.getSelection();
            var pos = sel.getCursor();
    
            activateSpinner(tabs.focussedTab);
    
            worker.emit("quickfixes", {
                data: pos
            });
        }
        
        function onResult(e) {
            var tab = tabs.findTab(e.data.path);
            if (!tab || tabs.focussedTab !== tab)
                return;
            
            clearSpinners(tab);
            var results = e.data.results;
            
            if (!results.length)
                return;
            
            // HACK: don't show UI for now, assume there's only 1 result
            if (results[0].deltas.length > 1 && results[0].delta.some(function(d) { return d.path; }))
                throw new Error("Multiple deltas with paths not allowed");
                
            applyQuickfix(e.data.path, results[0]);
        }
    
        function activateSpinner(tab) {
            tab.classList.add("loading");
            clearTimeout(tab.$quickfixReset);
            tab.$quickfixReset = setTimeout(function() {
                clearSpinners(tab);
            }, CRASHED_JOB_TIMEOUT);
        }
    
        function clearSpinners(tab) {
            clearTimeout(tab.$quickfixReset);
            tab.classList.remove("loading");
        }
        
        function applyQuickfix(sourcePath, fix) {
            var tab = fix.deltas[0].path
                ? tabs.findTab(fix.deltas[0].path)
                : tabs.focussedTab;
            if (tab !== tabs.focussedTab) {
                var sourcePos = tabs.focussedTab.editor.ace.getCursorPosition();
                return jumptodef.jumpToPos(fix.deltas[0].path, fix.pos, sourcePath, sourcePos, function(err) {
                    if (err) return console.error(err);
                    applyQuickfix(sourcePath, fix);
                });
            }
            
            var ace = tab.editor.ace;
            var doc = ace.getSession().getDocument();
    
            doc.applyDeltas(fix.deltas);
        
            if (fix.pos) {
                var pos = fix.pos;
                var selection = ace.getSelection();
                selection.clearSelection();
                selection.moveCursorTo(pos.row, pos.column, false);
            }
        }
        
        /*
        var CLASS_SELECTED = "cc_complete_option selected";
        var CLASS_UNSELECTED = "cc_complete_option";
        var SHOW_DOC_DELAY = 1500;
        var SHOW_DOC_DELAY_MOUSE_OVER = 100;
        var HIDE_DOC_DELAY = 1000;
        var MENU_WIDTH = 400;
        var MENU_SHOWN_ITEMS = 9;
        var EXTRA_LINE_HEIGHT = 3;
        var QFBOX_MINTIME = 500;
        
        var ignoreMouseOnce = false;
        var isDocShown;
        var isDrawDocInvokeScheduled = false;
        var quickfixElement;
        var editor;
        var selectedIdx;
        var scrollIdx;
        var quickfixEls;
        var docElement;
        var cursorConfig;
        var lineHeight;
        var quickFixes;
        var popupTime;
        var oldCommandKey;
        var oldOnTextInput;
        
        function isPopupVisible() {
            return barQuickfixCont.$ext.style.display !== "none";
        }
        
        var drawDocInvoke = lang.deferredCall(function() {
            if (isPopupVisible() && (quickFixes[selectedIdx].preview
                || quickFixes[selectedIdx].previewHtml)) {
                isDocShown = true;
                txtQuickfixDoc.parentNode.show();
            }
            isDrawDocInvokeScheduled = false;
        });
        
        var undrawDocInvoke = lang.deferredCall(function() {
            if (!isPopupVisible()) {
                isDocShown = false;
                txtQuickfixDoc.parentNode.hide();
            }
        });
        
        function initEditor(editor) {
                   
            editor.on("guttermousedown", editor.$markerListener = function(e) {
                 editor = editor;
                if (!e.getButton())
                    return;
                apf.addListener(mnuCtxEditor, "prop.visible", hideContext);
                function hideContext(ev) {
                    // only fire when visibility is set to true
                    if (ev.value) {
                        apf.removeListener(mnuCtxEditor, "prop.visible", hideContext);
                        mnuCtxEditor.hide();
                    }
                }
                var gutterRegion = editor.renderer.$gutterLayer.getRegion(e);
                if (gutterRegion != "markers")
                    return;
                
                var row = e.getDocumentPosition().row;
                showQuickfixBox(row, 0);
                
            });
        }
            
        function getAnnos(row) {
            var editor = editors.currentEditor.amlEditor.$editor;
            var res = [];
            
            editor.getSession().languageAnnos.forEach(function(anno, idx) {
                if (anno.row == row) {
                    res.push(anno);
                    
                    // Select the annotation in the editor
                    anno.select = function() {
                        if (!(anno.pos.sl && anno.pos.sc && anno.pos.el && anno.pos.ec)) {
                            return;
                        }
                        var startPos = { row: anno.pos.sl, column: anno.pos.sc };
                        var endPos = { row: anno.pos.el, column: anno.pos.ec };
                        if (startPos.row < endPos.row || startPos.column < endPos.column) {
                            editor.getSelection().setSelectionRange(
                                {start: startPos, end: endPos});
                        }
                    };
                    
                    // Returns the screen coordinates of the start of the annotation
                    anno.getScreenCoordinates = function() {
                        return editor.renderer.textToScreenCoordinates(anno.pos.sl,
                                                                       anno.pos.sc);  
                    };
                }
            });
            
            res.sort(function(a,b) { return a.pos.sc - b.pos.sc; });
            
            return res;
        }
        
        function showQuickfixBox(row, column) {
            // Get the annotation on this line that is containing or left of the 
            // position (row,column)
            var annos = getAnnos(row);
            if (!annos.length) {
                return;
            }
            for (var i = 0; i < annos.length - 1; i++) {
                if (annos[i+1].pos.sc > column) { break; }
            }
            var anno = annos[i];
            if (!anno.resolutions.length) {
                // TODO If some other annotation on this line has resolutions, 
                // quickfix that one instead
                return;
            }
    
            editor = editors.currentEditor;
            var ace = editor.amlEditor.$editor;
            selectedIdx = 0;
            scrollIdx = 0;
            quickfixEls = [];
            // annos = annos;
            quickFixes = [];
            quickfixElement = txtQuickfix.$ext;
            docElement = txtQuickfixDoc.$ext;
            cursorConfig = ace.renderer.$cursorLayer.config;
            lineHeight = cursorConfig.lineHeight + EXTRA_LINE_HEIGHT;
            var style = getComputedStyle(editor.amlEditor.$ext);
            quickfixElement.style.fontSize = style.fontSize;
            
            barQuickfixCont.setAttribute('visible', true);
    
    
            // Monkey patch
            if (!oldCommandKey) {
                oldCommandKey = ace.keyBinding.onCommandKey;
                ace.keyBinding.onCommandKey = onKeyPress.bind(this);
                oldOnTextInput = ace.keyBinding.onTextInput;
                ace.keyBinding.onTextInput = onTextInput.bind(this);
            }
            
            // Collect all quickfixes for the given annotation
            quickFixes = anno.resolutions;
            
            // Select it in the editor
            anno.select();
            
            populateQuickfixBox(quickFixes);
    
            apf.popup.setContent("quickfixBox", barQuickfixCont.$ext);
            var boxLength = quickFixes.length || 1;
            var quickfixBoxHeight = 11 + Math.min(10 * lineHeight, boxLength * (lineHeight));
            
            var innerBoxLength = quickFixes.length || 1;
            var innerQuickfixBoxHeight = Math.min(10 * lineHeight, innerBoxLength * (lineHeight));
            txtQuickfixHolder.$ext.style.height = innerQuickfixBoxHeight + "px";
            
            ignoreMouseOnce = !isPopupVisible();
            
            var pos = anno.getScreenCoordinates();
            apf.popup.show("quickfixBox", {
                x: pos.pageX, 
                y: pos.pageY + cursorConfig.lineHeight, 
                height: quickfixBoxHeight,
                width: MENU_WIDTH,
                animate: false,
                callback: function() {
                    barQuickfixCont.setHeight(quickfixBoxHeight);
                    barQuickfixCont.$ext.style.height = quickfixBoxHeight + "px";
                    sbQuickfix.$resize();
                    // HACK: Need to set with non-falsy value first
                    quickfixElement.scrollTop = 1;
                    quickfixElement.scrollTop = 0;
                }
            });
            
            popupTime = new Date().getTime();
            document.addEventListener("click", closeQuickfixBox, false);
            ace.container.addEventListener("DOMMouseScroll", closeQuickfixBox, false);
            ace.container.addEventListener("mousewheel", closeQuickfixBox, false);
        }
    
        function closeQuickfixBox(event) {
            var qfBoxTime = new Date().getTime() - popupTime;
            if (!forceClose && qfBoxTime < QFBOX_MINTIME) {
                return;
            }
            
            forceClose = false;
        
            barQuickfixCont.$ext.style.display = "none";
            if (!editors.currentEditor.amlEditor) // no editor, try again later
                return;
            var ace = editors.currentEditor.amlEditor.$editor;
            
            // TODO these calls don't work.
            document.removeEventListener("click", closeQuickfixBox, false);
            ace.container.removeEventListener("DOMMouseScroll", closeQuickfixBox, false);
            ace.container.removeEventListener("mousewheel", closeQuickfixBox, false);
            
            if (oldCommandKey) {
                ace.keyBinding.onCommandKey = oldCommandKey;
                ace.keyBinding.onTextInput = oldOnTextInput;
            }
            oldCommandKey = oldOnTextInput = null;
            undrawDocInvoke.schedule(HIDE_DOC_DELAY);
        }
        
        function populateQuickfixBox(quickFixes) {
            
            quickfixElement.innerHTML = "";
            var cursorConfig = code.amlEditor.$editor.renderer.$cursorLayer.config;
    
            // For each quickfix, create a list entry
            quickFixes.forEach(function(qfix, qfidx) {
    
                var annoEl = dom.createElement("div");
                annoEl.className = qfidx === selectedIdx ? CLASS_SELECTED : CLASS_UNSELECTED;
                var html = "";
    
                if (qfix.image)
                    html = "<img src='" + ide.staticPrefix + qfix.image + "'/>";
    
                html += '<span class="main">' + (qfix.messageHtml || escapeHtml(qfix.message)) + '</span>';
    
                annoEl.innerHTML = html;     
                
                annoEl.addEventListener("mouseover", function() {
                    if (ignoreMouseOnce) {
                        ignoreMouseOnce = false;
                        return;
                    }
                    quickfixEls[selectedIdx].className = CLASS_UNSELECTED;
                    selectedIdx = qfidx;
                    quickfixEls[selectedIdx].className = CLASS_SELECTED;
                    updateDoc();
                    if (!isDrawDocInvokeScheduled)
                        drawDocInvoke.schedule(SHOW_DOC_DELAY_MOUSE_OVER);
                });
                
                
                annoEl.addEventListener("click", function() {
                    forceClose = true;
                    applyQuickfix(qfix);
                });
                
                
                annoEl.style.height = cursorConfig.lineHeight + EXTRA_LINE_HEIGHT +  "px";
                annoEl.style.width = (MENU_WIDTH - 10) + "px";
                quickfixElement.appendChild(annoEl);
                quickfixEls.push(annoEl);
            });
    
            updateDoc(true);
            
        }
        
        function updateDoc(delayPopup) {
            docElement.innerHTML = '<span class="code_complete_doc_body">';
            var selected = quickFixes[selectedIdx];
    
            if (selected && (selected.preview || selected.previewHtml)) {
                if (isDocShown) {
                    txtQuickfixDoc.parentNode.show();
                }
                else {
                    txtQuickfixDoc.parentNode.hide();
                    if (!isDrawDocInvokeScheduled || delayPopup)
                        drawDocInvoke.schedule(SHOW_DOC_DELAY);
                }
                docElement.innerHTML += 
                    selected.previewHtml
                    || escapeHtml(selected.preview).replace(/\n/g, '<br/>');
                docElement.innerHTML += '</span>';
            }
            else {
                txtQuickfixDoc.parentNode.hide();
            }
    
            docElement.innerHTML += '</span>';
        }
        
        function onTextInput(text, pasted) {
            closeQuickfixBox();
        }
    
        function onKeyPress(e, hashKey, keyCode) {
            
            if (e.metaKey || e.ctrlKey || e.altKey) {
                closeQuickfixBox();
                return;
            }
            
            var keyBinding = editors.currentEditor.amlEditor.$editor.keyBinding;
    
            switch (keyCode) {
                case 0: break;
                case 32: // Space
                    closeQuickfixBox();
                    break;
                case 27: // Esc
                    closeQuickfixBox();
                    e.preventDefault();
                    break;
                case 8: // Backspace
                    closeQuickfixBox();
                    e.preventDefault();
                    break;
                case 37:
                case 39:
                    oldCommandKey.apply(keyBinding, arguments);
                    closeQuickfixBox();
                    e.preventDefault();
                    break;
                case 13: // Enter
                case 9: // Tab
                    applyQuickfix(quickFixes[selectedIdx]);
                    forceClose = true;
                    closeQuickfixBox();
                    e.stopPropagation();
                    e.preventDefault();
                    break;
                case 40: // Down
                    if (quickfixEls.length === 1) {
                        closeQuickfixBox();
                        break;
                    }
                    e.stopPropagation();
                    e.preventDefault();
                    quickfixEls[selectedIdx].className = CLASS_UNSELECTED;
                    if (selectedIdx < quickFixes.length-1)
                        selectedIdx++;
                    quickfixEls[selectedIdx].className = CLASS_SELECTED;
                    if (selectedIdx - scrollIdx > MENU_SHOWN_ITEMS) {
                        scrollIdx++;
                        quickfixEls[scrollIdx].scrollIntoView(true);
                    }
                    updateDoc();
                    break;
                case 38: // Up
                    if (quickfixEls.length === 1) {
                    closeQuickfixBox();
                        break;
                    }
                    e.stopPropagation();
                    e.preventDefault();
                    if (selectedIdx <= 0)
                        return;
                    quickfixEls[selectedIdx].className = CLASS_UNSELECTED;
                    selectedIdx--;
                    quickfixEls[selectedIdx].className = CLASS_SELECTED;
                    if (selectedIdx < scrollIdx) {
                        scrollIdx--;
                        quickfixEls[scrollIdx].scrollIntoView(true);
                    }
                    updateDoc();
                    break;
            }
        }
        
        function invoke(forceBox) {
            var editor = editors.currentEditor.amlEditor.$editor;
            if (editor.inMultiSelectMode) {
                closeQuickfixBox();
                return;
            }
            forceBox = forceBox;
            
            var pos = editor.getCursorPosition();
            
            showQuickfixBox(pos.row, pos.column);
        }
        */
        
        plugin.on("load", load);
        
        register(null, {
            "language.quickfix": plugin.freezePublicAPI({
                
            })
        });
    }

});