// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(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);
    // Plain browser env
    else mod(CodeMirror);
})(function (CodeMirror) {
    "use strict";

    CodeMirror.multiplexingMode = function (outer /*, others */) {
        // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
        var others = Array.prototype.slice.call(arguments, 1);

        function indexOf(string, pattern, from, returnEnd) {
            if (typeof pattern == "string") {
                var found = string.indexOf(pattern, from);
                return returnEnd && found > -1 ? found + pattern.length : found;
            }
            var m = pattern.exec(from ? string.slice(from) : string);
            return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
        }

        return {
            startState: function () {
                return {
                    outer: CodeMirror.startState(outer),
                    innerActive: null,
                    inner: null,
                };
            },

            copyState: function (state) {
                return {
                    outer: CodeMirror.copyState(outer, state.outer),
                    innerActive: state.innerActive,
                    inner:
                        state.innerActive &&
                        CodeMirror.copyState(state.innerActive.mode, state.inner),
                };
            },

            token: function (stream, state) {
                if (!state.innerActive) {
                    var cutOff = Infinity,
                        oldContent = stream.string;
                    for (var i = 0; i < others.length; ++i) {
                        var other = others[i];
                        var found = indexOf(oldContent, other.open, stream.pos);
                        if (found == stream.pos) {
                            if (!other.parseDelimiters) stream.match(other.open);
                            state.innerActive = other;
                            state.inner = CodeMirror.startState(
                                other.mode,
                                outer.indent ? outer.indent(state.outer, "") : 0
                            );
                            return (
                                other.delimStyle &&
                                other.delimStyle + " " + other.delimStyle + "-open"
                            );
                        } else if (found != -1 && found < cutOff) {
                            cutOff = found;
                        }
                    }
                    if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
                    var outerToken = outer.token(stream, state.outer);
                    if (cutOff != Infinity) stream.string = oldContent;
                    return outerToken;
                } else {
                    var curInner = state.innerActive,
                        oldContent = stream.string;
                    if (!curInner.close && stream.sol()) {
                        state.innerActive = state.inner = null;
                        return this.token(stream, state);
                    }
                    var found = curInner.close
                        ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters)
                        : -1;
                    if (found == stream.pos && !curInner.parseDelimiters) {
                        stream.match(curInner.close);
                        state.innerActive = state.inner = null;
                        return (
                            curInner.delimStyle &&
                            curInner.delimStyle + " " + curInner.delimStyle + "-close"
                        );
                    }
                    if (found > -1) stream.string = oldContent.slice(0, found);
                    var innerToken = curInner.mode.token(stream, state.inner);
                    if (found > -1) stream.string = oldContent;

                    if (found == stream.pos && curInner.parseDelimiters)
                        state.innerActive = state.inner = null;

                    if (curInner.innerStyle) {
                        if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
                        else innerToken = curInner.innerStyle;
                    }

                    return innerToken;
                }
            },

            indent: function (state, textAfter) {
                var mode = state.innerActive ? state.innerActive.mode : outer;
                if (!mode.indent) return CodeMirror.Pass;
                return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
            },

            blankLine: function (state) {
                var mode = state.innerActive ? state.innerActive.mode : outer;
                if (mode.blankLine) {
                    mode.blankLine(state.innerActive ? state.inner : state.outer);
                }
                if (!state.innerActive) {
                    for (var i = 0; i < others.length; ++i) {
                        var other = others[i];
                        if (other.open === "\n") {
                            state.innerActive = other;
                            state.inner = CodeMirror.startState(
                                other.mode,
                                mode.indent ? mode.indent(state.outer, "") : 0
                            );
                        }
                    }
                } else if (state.innerActive.close === "\n") {
                    state.innerActive = state.inner = null;
                }
            },

            electricChars: outer.electricChars,

            innerMode: function (state) {
                return state.inner
                    ? { state: state.inner, mode: state.innerActive.mode }
                    : { state: state.outer, mode: outer };
            },
        };
    };
});
