// Javascript object literal parser
// Splits an object literal string into a set of top-level key-value pairs
// (c) Michael Best (https://github.com/mbest)
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
// Version 2.1.0

// https://github.com/umdjs/umd
// Support AMD, Node.js, and globals
;(function (root, factory) {
    if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like enviroments that support module.exports,
        // like Node.
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(factory);
    } else {
        // Browser globals (root is window)
        root.parseObjectLiteral = factory();
  }
}(this, function(undefined) {
    // This parser is inspired by json-sans-eval by Mike Samuel (http://code.google.com/p/json-sans-eval/)

        // These two match strings, either with double quotes or single quotes
    const stringDouble = '"(?:[^"\\\\]|\\\\.)*"',
          stringSingle = "'(?:[^'\\\\]|\\\\.)*'",
          // Matches a regular expression (text enclosed by slashes), but will also match sets of divisions
          // as a regular expression (this is handled by the parsing loop below).
          stringRegexp = '/(?:[^/\\\\]|\\\\.)*/\w*',
          // These characters have special meaning to the parser and must not appear in the middle of a
          // token, except as part of a string.
          specials = ',"\'{}()/:[\\]',
          // Match text (at least two characters) that does not contain any of the above special characters,
          // although some of the special characters are allowed to start it (all but the colon and comma).
          // The text can contain spaces, but leading or trailing spaces are skipped.
          everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']',
          // Match any non-space character not matched already. This will match colons and commas, since they're
          // not matched by "everyThingElse", but will also match any other single character that wasn't already
          // matched (for example: in "a: 1, b: 2", each of the non-space characters will be matched by oneNotSpace).
          oneNotSpace = '[^\\s]',
          // Create the actual regular expression by or-ing the above strings. The order is important.
          token = RegExp(stringDouble + '|' + stringSingle + '|' + stringRegexp + '|' + everyThingElse + '|' + oneNotSpace, 'g'),
          // Match end of previous token to determine whether a slash is a division or regex.
          divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/,
          keywordRegexLookBehind = {'in':1,'return':1,'typeof':1};

    function trim(string) {
        return string == null ? '' :
            string.trim ?
                string.trim() :
                string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
    }

    return function(objectLiteralString) {
        // Trim leading and trailing spaces from the string
        let str = trim(objectLiteralString);

        // Trim braces '{' surrounding the whole object literal
        if (str.charCodeAt(0) === 123)
            str = str.slice(1, -1);

        // Split into tokens
        const result = [];

        let toks = str.match(token);
        let key;
        let values = [];
        let depth = 0;

        if (toks) {
            // Append a comma so that we don't need a separate code block to deal with the last item
            toks.push(',');

            for (let i = 0, tok; tok = toks[i]; ++i) {
                const c = tok.charCodeAt(0);
                // A comma signals the end of a key/value pair if depth is zero
                if (c === 44) { // ","
                    if (depth <= 0) {
                        if (!key && values.length === 1) {
                            key = values.pop();
                        }
                        result.push([key, values.length ? values.join('') : undefined]);
                        key = undefined;
                        values = [];
                        depth = 0;
                        continue;
                    }
                // Simply skip the colon that separates the name and value
                } else if (c === 58) { // ":"
                    if (!depth && !key && values.length === 1) {
                        key = values.pop();
                        continue;
                    }
                // A set of slashes is initially matched as a regular expression, but could be division
                } else if (c === 47 && i && tok.length > 1) {  // "/"
                    // Look at the end of the previous token to determine if the slash is actually division
                    const match = toks[i-1].match(divisionLookBehind);
                    if (match && !keywordRegexLookBehind[match[0]]) {
                        // The slash is actually a division punctuator; re-parse the remainder of the string (not including the slash)
                        str = str.substr(str.indexOf(tok) + 1);
                        toks = str.match(token);
                        toks.push(',');
                        i = -1;
                        // Continue with just the slash
                        tok = '/';
                    }
                // Increment depth for parentheses, braces, and brackets so that interior commas are ignored
                } else if (c === 40 || c === 123 || c === 91) { // '(', '{', '['
                    ++depth;
                } else if (c === 41 || c === 125 || c === 93) { // ')', '}', ']'
                    --depth;
                // The key will be the first token; if it's a string, trim the quotes
                } else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
                    tok = tok.slice(1, -1);
                }
                values.push(tok);
            }
        }
        return result;
    };
}));
