// TODO: remove vestiges of vbscript

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("qbjs", function(conf, parserConf) {
    var ERRORCLASS = 'error';

    function wordRegexp(words) {
        return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
    }

    var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]");
    var doubleOperators = new RegExp("^((<>)|(<=)|(>=))");
    var singleDelimiters = new RegExp('^[\\.,]');
    var brakets = new RegExp('^[\\(\\)]');
    var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*");

    var openingKeywords = ['sub','select','while','if','function', 'property', 'with', 'for', 'type\\s'];
    var middleKeywords = ['else','elseif','case','then'];
    var endKeywords = ['next','loop','wend'];

    var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']);
    var commonkeywords = ['dim', 'as', 'redim', 'then', 'until', 'exit', 'in', 'to', 'let',
                          'const', 'integer', 'single', 'double', 'long', '_unsigned', 'string', '_byte', 'object',
                          'option explicit', 'call', 'step'];

    // TODO: adjust for QB
    var atomWords = ['true', 'false', 'nothing', 'empty', 'null'];

    var builtinFuncsWords = ['_acos', '_acosh', '_alpha', '_alpha32', '_asin', '_asinh', '_atan2', '_atanh', '_autodisplay',
                             '_backgroundcolor', '_blue', '_blue32', '_ceil', '_commandcount', '_continue', '_copyimage',
                             '_cosh', '_coth', '_csch', '_cwd', '_defaultcolor', '_d2g', '_d2r', '_desktopwidth', '_desktopheight',
                             '_delay', '_dest', '_dir', '_direxists', '_display', '_echo', '_fileexists', '_font', '_fontwidth', '_fontheight',
                             '_freeimage', '_fullscreen', '_g2d', '_g2r', '_green', '_green32', '_height', '_hypot',
                             '_instrrev', '_limit', '_keyclear', '_keydown', '_keyhit', '_loadfont', '_loadimage',
                             '_mousebutton', '_mousehide', '_mouseinput', '_mouseshow', '_mousewheel', '_mousex', '_mousey',
                             '_newimage', '_os', '_palettecolor', '_pi', '_printstring', '_printwidth', '_printmode', '_putimage',
                             '_r2d', '_r2g', '_readbit', '_red', '_red32', '_resetbit', '_resize', '_resizewidth',
                             '_resizeheight', '_rgb', '_rgba', '_rgb32', '_rgba32', '_round',
                             '_screenexists', '_screenmove', '_screenx', '_screeny', '_sech', '_setbit', '_shl', '_shr', '_sinh', '_source',
                             '_sndclose', '_sndopen', '_sndplay', '_sndloop', '_sndpause', '_sndstop', '_sndvol', '_startdir',
                             '_strcmp', '_stricmp', '_tanh', '_title', '_trim', '_togglebit', '_width',
                             'abs', 'asc', 'atn', 'beep',
                             'chr', 'cdbl', 'cint', 'clng', 'csng', 'circle', 'cls', 'color', 'command', 'cos', 'cvi', 'cvl',
                             'data', 'date', 'draw', 'environ', 'error', 'exp', 'fix', 'hex', 'input', 'inkey', 'instr', 'int',
                             'lbound', 'left', 'lcase', 'len', 'line', 'locate', 'log', 'ltrim', 'mid', 'mki', 'mkl',
                             'oct', 'paint', 'point', 'preset', 'print', 'pset',
                             'right', 'rtrim', 'randomize', 'read', 'restore', 'rnd',
                             'screen', 'shared', 'sgn', 'sin', 'sleep', 'sound', 'space', 'sqr',
                             'str', 'swap', 'tan', 'time', 'timer', 'ubound', 'ucase',
                             'val', 'varptr', 'window',
                             'mkdir', 'chdir', 'rmdir', 'kill', 'name', 'files', 'open', 'close', 'lof', 'eof', 'put', 'get', 'freefile',
                             'seek', 'write',
                             // QBJS-specific
                             'export', 'from', 'import']

    var builtinConsts = ['append', 'binary', 'input', 'output', 'random',
                         '_off', '_smooth', '_stretch', '_squarepixels', '_keepbackground', '_onlybackground', '_fillbackground',
                         'gx_true', 'gx_false', 'gxevent_init', 'gxevent_update', 'gxevent_drawbg', 'gxevent_drawmap', 'gxevent_drawscreen',
                         'gxevent_mouseinput', 'gxevent_paintbefore', 'gxevent_paintafter', 'gxevent_collision_tile', 'gxevent_collision_entity',
                         'gxevent_player_action', 'gxevent_animate_complete', 'gxanimate_loop', 'gxanimate_single', 'gxbg_stretch',
                         'gxbg_scroll', 'gxbg_wrap', 'gxkey_esc', 'gxkey_1', 'gxkey_2', 'gxkey_3', 'gxkey_4', 'gxkey_5', 'gxkey_6', 'gxkey_7',
                         'gxkey_8', 'gxkey_9', 'gxkey_0', 'gxkey_dash', 'gxkey_equals', 'gxkey_backspace', 'gxkey_tab', 'gxkey_q', 'gxkey_w',
                         'gxkey_e', 'gxkey_r', 'gxkey_t', 'gxkey_y', 'gxkey_u', 'gxkey_i', 'gxkey_o', 'gxkey_p', 'gxkey_lbracket',
                         'gxkey_rbracket', 'gxkey_enter', 'gxkey_lctrl', 'gxkey_a', 'gxkey_s', 'gxkey_d', 'gxkey_f', 'gxkey_g', 'gxkey_h',
                         'gxkey_j', 'gxkey_k', 'gxkey_l', 'gxkey_semicolon', 'gxkey_quote', 'gxkey_backquote', 'gxkey_lshift', 'gxkey_backslash',
                         'gxkey_z', 'gxkey_x', 'gxkey_c', 'gxkey_v', 'gxkey_b', 'gxkey_n', 'gxkey_m', 'gxkey_comma', 'gxkey_period',
                         'gxkey_slash', 'gxkey_rshift', 'gxkey_numpad_multiply', 'gxkey_spacebar', 'gxkey_capslock', 'gxkey_f1', 'gxkey_f2',
                         'gxkey_f3', 'gxkey_f4', 'gxkey_f5', 'gxkey_f6', 'gxkey_f7', 'gxkey_f8', 'gxkey_f9', 'gxkey_f9', 'gxkey_pause',
                         'gxkey_scrlk', 'gxkey_numpad_7', 'gxkey_numpad_8', 'gxkey_numpad_9', 'gxkey_numpad_minus', 'gxkey_numpad_4',
                         'gxkey_numpad_5', 'gxkey_numpad_6', 'gxkey_numpad_plus', 'gxkey_numpad_1', 'gxkey_numpad_2', 'gxkey_numpad_3',
                         'gxkey_numpad_0', 'gxkey_numpad_period', 'gxkey_f11', 'gxkey_f12', 'gxkey_numpad_enter', 'gxkey_rctrl',
                         'gxkey_numpad_divide', 'gxkey_numlock', 'gxkey_home', 'gxkey_up', 'gxkey_pageup', 'gxkey_left', 'gxkey_right',
                         'gxkey_end', 'gxkey_down', 'gxkey_pagedown', 'gxkey_insert', 'gxkey_delete', 'gxkey_lwin', 'gxkey_rwin', 'gxkey_menu',
                         'gxaction_move_left', 'gxaction_move_right', 'gxaction_move_up', 'gxaction_move_down', 'gxaction_jump',
                         'gxaction_jump_right', 'gxaction_jump_left', 'gxscene_follow_none', 'gxscene_follow_entity_center',
                         'gxscene_follow_entity_center_x', 'gxscene_follow_entity_center_y', 'gxscene_follow_entity_center_x_pos',
                         'gxscene_follow_entity_center_x_neg', 'gxscene_constrain_none', 'gxscene_constrain_to_map', 'gxfont_default',
                         'gxfont_default_black', 'gxdevice_keyboard', 'gxdevice_mouse', 'gxdevice_controller', 'gxdevice_button',
                         'gxdevice_axis', 'gxdevice_wheel', 'gxtype_entity', 'gxtype_font', 'gx_cr', 'gx_lf', 'gx_crlf',
                         'local', 'session'];

    var builtinObjsWords = ['\\$if', '\\$end if', '\\$elseif', '\\$else', '\\$touchmouse'];

    // TODO: adjust for QB
    var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'value', 'count'];

    var knownMethods = ['gxongameevent', 'gxmousex', 'gxmousey', 'gxsoundload', 'gxsoundplay', 'gxsoundrepeat', 'gxsoundvolume', 'gxsoundpause', 'gxsoundstop',
                        'gxsoundmuted', 'gxsoundmuted', 'gxentityanimate', 'gxentityanimatestop', 'gxentityanimatemode', 'gxentityanimatemode',
                        'gxscreenentitycreate', 'gxentitycreate', 'gxentityvisible', 'gxentitymove', 'gxentitypos', 'gxentityvx', 'gxentityvy',
                        'gxentityx', 'gxentityy', 'gxentitywidth', 'gxentityheight', 'gxentityframenext', 'gxentityframeset', 'gxentitytype',
                        'gxentitytype', 'gxentityuid', 'gxfontuid', 'gxentityapplygravity', 'gxentitycollisionoffset',
                        'gxentitycollisionoffsetleft', 'gxentitycollisionoffsettop', 'gxentitycollisionoffsetright',
                        'gxentitycollisionoffsetbottom', 'gxfullscreen', 'gxbackgroundadd', 'gxbackgroundwrapfactor',
                        'gxbackgroundclear', 'gxsceneembedded', 'gxscenecreate', 'gxscenewindowsize', 'gxscenescale', 'gxsceneresize',
                        'gxscenedestroy', 'gxcustomdraw', 'gxframerate', 'gxframe', 'gxscenedraw', 'gxscenemove', 'gxscenepos', 'gxscenex',
                        'gxsceney', 'gxscenewidth', 'gxsceneheight', 'gxscenecolumns', 'gxscenerows', 'gxscenestart', 'gxsceneupdate',
                        'gxscenefollowentity', 'gxsceneconstrain', 'gxscenestop', 'gxmapcreate', 'gxmapcolumns', 'gxmaprows', 'gxmaplayers',
                        'gxmaplayervisible', 'gxmaplayeradd', 'gxmaplayerinsert', 'gxmaplayerremove', 'gxmapresize', 'gxmapdraw',
                        'gxmaptileposat', 'gxmaptile', 'gxmaptile', 'gxmaptiledepth', 'gxmaptileadd', 'gxmaptileremove', 'gxmapversion',
                        'gxmapsave', 'gxmapload', 'gxmapisometric', 'gxspritedraw', 'gxspritedrawscaled', 'gxtilesetcreate',
                        'gxtilesetreplaceimage', 'gxtilesetload', 'gxtilesetsave', 'gxtilesetpos', 'gxtilesetwidth', 'gxtilesetheight',
                        'gxtilesetcolumns', 'gxtilesetrows', 'gxtilesetfilename', 'gxtilesetimage', 'gxtilesetanimationcreate',
                        'gxtilesetanimationadd', 'gxtilesetanimationremove', 'gxtilesetanimationframes', 'gxtilesetanimationspeed',
                        'gxfontcreate', 'gxfontwidth', 'gxfontheight', 'gxfontcharspacing', 'gxfontlinespacing',
                        'gxdrawtext', 'gxdebug', 'gxdebug', 'gxdebugscreenentities', 'gxdebugfont', 'gxdebugtilebordercolor',
                        'gxdebugentitybordercolor', 'gxdebugentitycollisioncolor', 'gxkeyinput', 'gxkeydown', 'gxdeviceinputdetect',
                        'gxdeviceinputtest', 'gxdevicename', 'gxdevicetypename', 'gxinputtypename', 'gxkeybuttonname'];

    var knownWords = knownMethods.concat(knownProperties);

    builtinObjsWords = builtinObjsWords.concat(builtinConsts);

    var keywords = wordRegexp(commonkeywords);
    var atoms = wordRegexp(atomWords);
    var builtinFuncs = wordRegexp(builtinFuncsWords);
    var builtinObjs = wordRegexp(builtinObjsWords);
    var known = wordRegexp(knownWords);
    var stringPrefixes = '"';

    var opening = wordRegexp(openingKeywords);
    var middle = wordRegexp(middleKeywords);
    var closing = wordRegexp(endKeywords);
    var doubleClosing = wordRegexp(['end']);
    var doOpening = wordRegexp(['do']);
    var noIndentWords = wordRegexp(['on error resume next', 'exit']);
    var comment = wordRegexp(['rem']);


    function indent(_stream, state) {
      state.currentIndent++;
    }

    function dedent(_stream, state) {
      state.currentIndent--;
    }
    // tokenizers
    function tokenBase(stream, state) {
        if (stream.eatSpace()) {
            return 'space';
            //return null;
        }

        var ch = stream.peek();

        // Handle Comments
        if (ch === "'") {
            stream.skipToEnd();
            return 'comment';
        }
        if (stream.match(comment)){
            stream.skipToEnd();
            return 'comment';
        }


        // Handle Number Literals
        if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) {
            var floatLiteral = false;
            // Floats
            if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; }
            else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
            else if (stream.match(/^\.\d+/)) { floatLiteral = true; }

            if (floatLiteral) {
                // Float literals may be "imaginary"
                stream.eat(/J/i);
                return 'number';
            }
            // Integers
            var intLiteral = false;
            // Hex
            if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; }
            // Octal
            else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; }
            // Decimal
            else if (stream.match(/^[1-9]\d*F?/)) {
                // Decimal literals may be "imaginary"
                stream.eat(/J/i);
                // TODO - Can you have imaginary longs?
                intLiteral = true;
            }
            // Zero by itself with no other piece of number.
            else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
            if (intLiteral) {
                // Integer literals may be "long"
                stream.eat(/L/i);
                return 'number';
            }
        }

        // Handle Strings
        if (stream.match(stringPrefixes)) {
            state.tokenize = tokenStringFactory(stream.current());
            return state.tokenize(stream, state);
        }

        // Handle operators and Delimiters
        if (stream.match(doubleOperators)
            || stream.match(singleOperators)
            || stream.match(wordOperators)) {
            return 'operator';
        }
        if (stream.match(singleDelimiters)) {
            return null;
        }

        if (stream.match(brakets)) {
            return "bracket";
        }

        if (stream.match(noIndentWords)) {
            state.doInCurrentLine = true;

            return 'keyword';
        }

        if (stream.match(doOpening)) {
            indent(stream,state);
            state.doInCurrentLine = true;

            return 'keyword';
        }
        if (stream.match(opening)) {
            if (! state.doInCurrentLine)
              indent(stream,state);
            else
              state.doInCurrentLine = false;

            return 'keyword';
        }
        if (stream.match(middle)) {
            return 'keyword';
        }


        if (stream.match(doubleClosing)) {
            dedent(stream,state);
            dedent(stream,state);

            return 'keyword';
        }
        if (stream.match(closing)) {
            if (! state.doInCurrentLine)
              dedent(stream,state);
            else
              state.doInCurrentLine = false;

            return 'keyword';
        }

        if (stream.match(keywords)) {
            return 'keyword';
        }

        if (stream.match(atoms)) {
            return 'atom';
        }

        if (stream.match(known)) {
            return 'variable-2';
        }

        if (stream.match(builtinFuncs)) {
            return 'builtin';
        }

        if (stream.match(builtinObjs)){
            return 'variable-2';
        }

        if (stream.match(identifiers)) {
            return 'variable';
        }

        // Handle non-detected items
        stream.next();
        return ERRORCLASS;
    }

    function tokenStringFactory(delimiter) {
        var singleline = delimiter.length == 1;
        var OUTCLASS = 'string';

        return function(stream, state) {
            while (!stream.eol()) {
                stream.eatWhile(/[^'"]/);
                if (stream.match(delimiter)) {
                    state.tokenize = tokenBase;
                    return OUTCLASS;
                } else {
                    stream.eat(/['"]/);
                }
            }
            if (singleline) {
                if (parserConf.singleLineStringErrors) {
                    return ERRORCLASS;
                } else {
                    state.tokenize = tokenBase;
                }
            }
            return OUTCLASS;
        };
    }


    function tokenLexer(stream, state) {
        var style = state.tokenize(stream, state);
        var current = stream.current();

        // Handle '.' connected identifiers
        if (current === '.') {
            style = state.tokenize(stream, state);

            current = stream.current();
            if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) {
                if (style === 'builtin' || style === 'keyword') style='variable';
                if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2';

                return style;
            } else {
                return ERRORCLASS;
            }
        }

        return style;
    }

    var external = {
        electricChars:"dDpPtTfFeE ",
        startState: function() {
            return {
              tokenize: tokenBase,
              lastToken: null,
              currentIndent: 0,
              nextLineIndent: 0,
              doInCurrentLine: false,
              ignoreKeyword: false


          };
        },

        token: function(stream, state) {
            if (stream.sol()) {
              state.currentIndent += state.nextLineIndent;
              state.nextLineIndent = 0;
              state.doInCurrentLine = 0;
            }
            var style = tokenLexer(stream, state);

            state.lastToken = {style:style, content: stream.current()};

            if (style==='space') style=null;

            return style;
        },

        indent: function(state, textAfter) {
            var trueText = textAfter.replace(/^\s+|\s+$/g, '') ;
            if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1);
            if(state.currentIndent < 0) return 0;
            return state.currentIndent * conf.indentUnit;
        }

    };
    return external;
});

CodeMirror.defineMIME("text/qbjs", "qbjs");

});
