// ==UserScript==
// @name        AceInstead
// @namespace   https://github.com/Som1Lse/AceInstead
// @description Changes the text editor to be Ace (https://ace.c9.io) instead of CodeMirror
// @include     http://d.godbolt.org/*
// @include     http://go.godbolt.org/*
// @include     http://gcc.godbolt.org/*
// @include     http://rust.godbolt.org/*
// @include     https://d.godbolt.org/*
// @include     https://go.godbolt.org/*
// @include     https://gcc.godbolt.org/*
// @include     https://rust.godbolt.org/*
// @resource    require_js          http://requirejs.org/docs/release/2.3.2/comments/require.js
// @resource    ace_js              https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/ace.js
// @resource    ext_searchbox_js    https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/ext-searchbox.js
// @resource    theme_monokai_js    https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/theme-monokai.js
// @resource    mode_d_js           https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/mode-d.js
// @resource    mode_rust_js        https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/mode-rust.js
// @resource    mode_c_cpp_js       https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/mode-c_cpp.js
// @resource    mode_golang_js      https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-noconflict/mode-golang.js
// @version     2.0.1
// @grant       unsafeWindow
// @grant       GM_getResourceText
// @grant       GM_addStyle
// @run-at      document-start
// ==/UserScript==

(function(){
    'use strict';

    function RemoteExecute(code){
        if(typeof(code) === 'function') code = '('+code.toString()+')()';

        new unsafeWindow.Function(code).call(unsafeWindow);
    }

    GM_addStyle(
        '.ace_editor {'+
            'margin-bottom: 0px;'+
        '}'+
        '.ace_gutter-layer .ace_gutter-cell>div {'+
            'display: inline-block;'+
        '}'+
        '.ace_search {'+
            'color: #000000;'+
            'font-family: sans-serif;'+
        '}'+
        '.address, .opcodes {'+
            'vertical-align: top !important;'+
            'font-size: 12px !important;'+
            'display: inline-block;'+
            'width: initial !important;'+
        '}'+

        //fix the markers for the monokai-theme so they are properly transparent
        '.ace-monokai .ace_marker-layer .ace_active-line {'+
            'background: rgba(0,0,0,0.2) !important;'+
        '}'+
        '.ace-monokai .ace_marker-layer .ace_selection {'+
            'background: rgba(209, 200, 174, 0.2) !important;'+
        '}'+

        '.rainbow-0, .rainbow-1, .rainbow-2, .rainbow-3, .rainbow-4, .rainbow-5,'+
        '.rainbow-6, .rainbow-7, .rainbow-8, .rainbow-9, .rainbow-10,.rainbow-11 {'+
            'position: absolute;'+
            'opacity: 0.3;'+
        '}'
    );

    RemoteExecute(GM_getResourceText('require_js')+
                  'window.define = define;'+
                  'window.require = require;'+
                  'window.requirejs = requirejs;');

    var scripts = [
        'ace_js',
        'ext_searchbox_js',
        'theme_monokai_js',
        'mode_d_js',
        'mode_rust_js',
        'mode_c_cpp_js',
        'mode_golang_js',
    ];

    for(var i = 0;i<scripts.length;++i){
        try {
            RemoteExecute(GM_getResourceText(scripts[i]));
        }catch(e){
            console.log(scripts[i]+' FAILED: ',e);
        }
    }

    RemoteExecute(function(){
        function assert(b,msg){
            if(msg === undefined) msg = 'ASSERTION FAILED!';

            if(!b){
                alert(msg);
                throw msg;
            }
        }
        
        window.addEventListener('DOMContentLoaded',function(e){
            var main_str = document.head.querySelector('script[data-main]').dataset.main;
            var main_index = main_str.lastIndexOf('/');
            
            var conf = {
                paths: {
                    bootstrap: 'ext/bootstrap/dist/js/bootstrap.min',
                    jquery: 'ext/jquery/dist/jquery.min',
                    underscore: 'ext/underscore/underscore-min',
                    goldenlayout: 'ext/golden-layout/dist/goldenlayout',
                    selectize: 'ext/selectize/dist/js/selectize.min',
                    sifter: 'ext/sifter/sifter.min',
                    microplugin: 'ext/microplugin/src/microplugin',
                    events: 'ext/eventEmitter/EventEmitter',
                    lzstring: 'ext/lz-string/libs/lz-string',
                    clipboard: 'ext/clipboard/dist/clipboard',
                    'raven-js': 'ext/raven-js/dist/raven'
                },
                shim: {
                    underscore: {exports: '_'},
                    bootstrap: ['jquery']
                }
            };
            
            if(main_index !== -1){
                conf.baseUrl = main_str.substr(0,main_index+1);
            }

            //gcc.godbolt.org has a compiled main.js file that contains many of the modules
            if(location.hostname.substr(-11) === 'godbolt.org') conf.paths.editor = 'main';

            require.config(conf);

            require.config = function(){};

            var Range = ace.require("ace/range").Range;

            define('codemirror',[],function(){
                return {
                    'defineMode': function(){
                        //do nothing
                    },
                    'defineMIME': function(){
                        //do nothing
                    },
                    'registerHelper': function(){
                        //do nothing
                    },
                    'fromTextArea': function(el,options){
                        var r = {
                            'on': function(name,callback){
                                this._editor.on(name,callback);
                            },
                            'refresh': function(){},//noop
                            'setSize': function(width,height){
                                if(width  !== null) this._editor.container.style.width  = width+'px';
                                if(height !== null) this._editor.container.style.height = height+'px';

                                this._editor.resize();
                            },
                            'setValue': function(value){
                                this._editor.setValue(value,-1);
                            },
                            'getValue': function(){
                                return this._editor.getValue();
                            },
                            'lineCount': function(){
                                return this._editor.getSession().getLength();
                            },
                            'operation': function(f){
                                f();
                            },
                            'addLineClass': function(line,where,className){
                                if(where !== 'background'){
                                    console.log('Unexpected arguments ',arguments,' provided to `cm.addLineClass()`');
                                    return;
                                }

                                this._editor.getSession().addMarker(new Range(line,0,line,1),className,'fullLine');
                            },
                            'removeLineClass': function(line,where,className){
                                if(where !== 'background'){
                                    console.log('Unexpected arguments ',arguments,' provided to `cm.removeLineClass()`');
                                    return;
                                }

                                var session = this._editor.getSession();
                                var markers = session.getMarkers();

                                for(var i in markers){
                                    var marker = markers[i];

                                    if((className === null || marker.clazz === className) && marker.type === 'fullLine' &&
                                       marker.range.start.column === 0 && marker.range.end.column === 1 &&
                                       marker.range.start.row === line && marker.range.end.row === line){
                                        session.removeMarker(i);
                                    }
                                }
                            },
                            'setGutterMarker': function(){},//noop
                            'setOption': function(){},//noop
                            'markText': function(){},//noop
                            'setSelection': function(begin,end){
                                if(begin.line !== end.line-1 || begin.ch !== 0 || end.ch !== 0){
                                    console.log('Unexpected arguments ',arguments,' provided to `cm.setSelection()`');
                                    return;
                                }

                                this._editor.getSelection().setRange(
                                    new Range(begin.line,0,begin.line,this._editor.getSession().getLine(begin.line).length));
                            },

                            '_editor': ace.edit(el),
                        };

                        if(window.r1 === undefined) window.r1 = r;
                        else if(window.r2 === undefined) window.r2 = r;

                        r._editor.setSelectionStyle('text');
                        r._editor.setOption('scrollPastEnd',true);
                        r._editor.setOption('vScrollBarAlwaysVisible',true);
                        r._editor.renderer.setShowPrintMargin(true);

                        r._editor.container.classList.add('CodeMirror');//simulate CodeMirror
                        r._editor.$blockScrolling = Infinity;

                        r._editor.setTheme('ace/theme/monokai');
                        switch(options.mode){
                            case 'text/x-d':        r._editor.getSession().setMode('ace/mode/d'); break;
                            case 'text/x-go':       r._editor.getSession().setMode('ace/mode/golang'); break;
                            case 'text/x-asm':      r._editor.getSession().setMode('ace/mode/asm'); break;
                            case 'text/x-c++src':   r._editor.getSession().setMode('ace/mode/c_cpp'); break;
                            case 'text/x-rustsrc':  r._editor.getSession().setMode('ace/mode/rust'); break;
                            default: {
                                console.log('Invalid `options.mode` ',options.mode,' passed to `CodeMirror.fromTextArea()`');
                                break;
                            }
                        }

                        if(options.readOnly) r._editor.setReadOnly(true);

                        return r;
                    },
                };
            });

            define('codemirror/mode/clike/clike',[],function(){ return {}; });
            define('codemirror/mode/d/d',[],function(){ return {}; });
            define('codemirror/mode/go/go',[],function(){ return {}; });
            define('codemirror/mode/rust/rust',[],function(){ return {}; });

            ace.define('ace/mode/asm',['require','exports','module','ace/mode/text','ace/oop'],
                       function(require,exports,module){
                var oop = require('../lib/oop');

                var Mode = exports.Mode = function(){
                    this._tokenizer = {
                        'getLineTokens': function(str,prev,line){
                            var tokens = [];

                            function eat(n){
                                var r;
                                if(typeof(n) === 'undefined'){
                                    r = str;
                                    str = '';
                                    return r;
                                }else if(typeof(n) === 'string'){
                                    r = n;
                                    n = n.length;
                                }else{
                                    r = str.substr(0,n);
                                }
                                str = str.substr(n);
                                return r;
                            }

                            if(line === 0 &&
                               (str === '[Processing...]' || str === 'Awaiting' || str === '<Compilation failed>')){
                                tokens = [{'type': 'keyword','value': eat()}];
                            }


                            var match = /^\S[^#]*:/i.exec(str);
                            if(match === null){
                                match = /^\s+[a-z_]\w*/i.exec(str);
                                if(match !== null){
                                    tokens.push({'type': 'keyword','value': eat(match[0])});
                                }else{
                                    match = /^\s+\.\w+/i.exec(str);
                                    if(match !== null){
                                        tokens.push({'type': 'storage.type','value': eat(match[0])});
                                    }
                                }
                            }else{
                                tokens.push({'type': 'variable.parameter','value': eat(match[0])});
                            }


                            while(str.length>0){
                                match = /^\s+/i.exec(str);
                                if(match !== null){
                                    tokens.push({'type': 'spacing','value': eat(match[0])});
                                    continue;
                                }

                                match = /^\$?-?\d\w*/i.exec(str);
                                if(match !== null){
                                    tokens.push({'type': 'constant.numeric','value': eat(match[0])});
                                    continue;
                                }

                                match = /^%?\w+/i.exec(str);
                                if(match !== null &&
                                   (match[0].search(/^%?[xy]mm\d+$/i) === 0 ||
                                    match[0].search(/^%?[re]?([a-d]x|di|si|bp|sp)$/i) === 0 ||
                                    match[0].search(/^%?[re]ip$/i) === 0 ||
                                    match[0].search(/^%?(di|si|bp|sp)l$/i) === 0 ||
                                    match[0].search(/^%?[a-d][hl]$/i) === 0 ||
                                    match[0].search(/^%?[c-fs]s$/i) === 0)){
                                    console.log(match);
                                    tokens.push({'type': 'variable','value': eat(match[0])});
                                    continue;
                                }

                                match = /^(ptr|offset|flat|byte|(d|w|xmm|ymm)?word)/i.exec(str);
                                if(match !== null){
                                    tokens.push({'type': 'storage.type','value': eat(match[0])});
                                    continue;
                                }

                                match = /^[a-z_.\-$@][\w.\-$@]*/i.exec(str);
                                if(match !== null){
                                    tokens.push({'type': 'identifier','value': eat(match[0])});
                                    continue;
                                }

                                if(str[0] === '<' || str[0] === '(' || str[0] === '[' || str[0] === '{'){
                                    tokens.push({'type': 'paren.lparen','value': eat(1)});
                                }else if(str[0] === '>' || str[0] === ')' || str[0] === ']' || str[0] === '}'){
                                    tokens.push({'type': 'paren.lparen','value': eat(1)});
                                }else if(str[0] === ',' || str[0] === '$' || str[0] === '%' || str[0] === '+' ||
                                         str[0] === '*' || str[0] === ':' || str[0] === '!' || str[0] === '-' ||
                                         str[0] === '&'){
                                    tokens.push({'type': 'punctuation.operator','value': eat(1)});
                                }else if(str[0] === '\"'){
                                    var i;
                                    for(i = 1;i<str.length && str[i] !== '\"';++i){
                                        if(str[i] === '\\') ++i;
                                    }

                                    if(str[i] === '\"'){
                                        tokens.push({'type': 'string','value': eat(i+1)});
                                    }else{
                                        if(str.length>0) tokens.push({'type': 'invalid','value': eat(i)});
                                    }
                                }else if(str[0] === '#'){
                                    tokens.push({'type': 'comment','value': eat()});
                                }else{
                                    tokens.push({'type': 'invalid','value': eat(1)});
                                }
                            }

                            return {'state': 'start','tokens': tokens};
                        },
                    };
                    this.getTokenizer = function(){ return this._tokenizer; };
                    this.createWorker = function(){ return null; };
                };

                oop.inherits(Mode,require('./text').Mode);
            });

            require(['editor'],function(editor){
                function DiagTypeAsInt(type){
                    switch(type){
                        case 'error': return 2;
                        case 'warning': return 1;
                        default: return 0;
                    }
                }
                function MaxDiagType(type1,type2){
                    if(DiagTypeAsInt(type2) >= DiagTypeAsInt(type1)){
                        return type2;
                    }else{
                        return type1;
                    }
                }

                function UpdateAnnotations(editor){
                    var temp_annotations = {};

                    var a,x;
                    for(a in editor.widgetsByCompiler){
                        var compiler = editor.widgetsByCompiler[a].compiler;
                        x = editor.widgetsByCompiler[a].widgets;
                        for(var i = 0;i<x.length;++i){
                            var line = x[i].line;
                            if(!(line in temp_annotations)){
                                temp_annotations[line] = {
                                    'type': 'info',
                                    'output': {},
                                };
                            }

                            var y = temp_annotations[line];
                            if(!(compiler.name in y.output)){
                                y.output[compiler.name] = '';
                            }

                            y.type = MaxDiagType(y.type,x[i].type);
                            y.output[compiler.name] += '\n  '+x[i].text;
                        }
                    }

                    var annotations = [];

                    for(a in temp_annotations){
                        x = temp_annotations[a].output;

                        var text = '';
                        for(var b in x){
                            if(text !== '') text += '\n';
                            text += b+':'+x[b];
                        }

                        annotations.push({
                            'row': a,
                            'column': 0,
                            'type': temp_annotations[a].type,
                            'text': text,
                            'raw': text,//@todo: what should `raw` be?
                        });
                    }

                    editor.editor._editor.getSession().setAnnotations(annotations);
                }

                console.log(editor.Editor.prototype.onCompileResponse.toString());
                editor.Editor.prototype.onCompileResponse = function(id,compiler,result){
                    console.log(this,'.onCompileResponse(',id,', ',compiler,', ',result,')');

                    var widgets = {
                        'compiler': compiler,
                        'widgets': [],
                    };

                    var lines = (result.stdout+result.stderr).split('\n');

                    var regex = /^(\/tmp\/.*?):(\d+):((\d+:)*) (.*)$/;
                    for(var i = 0;i<lines.length;++i){
                        var match = lines[i].match(regex);
                        if(!match) continue;

                        var type = '';
                        var text = match[5];

                        var index = text.indexOf(': ');
                        if(index !== -1) type = text.substr(0,index);

                        if(type !== 'error' && type !== 'warning'){
                            //continue;
                            type = 'info';
                        }

                        widgets.widgets.push({
                            'line': Number(match[2])-1,
                            'type': type,
                            'text': text.trim(),
                        });
                    }

                    this.widgetsByCompiler[id] = widgets;

                    UpdateAnnotations(this);
                    this.asmByCompiler[id] = result.asm;
                    this.numberUsedLines();
                };
                editor.Editor.prototype.removeWidgets = function(widgets){
                    widgets.widgets = [];
                    UpdateAnnotations(this);
                };

                require([main_str.substr(main_index+1)]);
            });
        },false);
    });
})();