/* global CodeMirror */
CodeMirror.defineMode('ficheblock', function() {

    var ZONE_STANDARD = 0;
    var ZONE_TABLE = 1;
    var ZONE_CODE = 2;
    var ZONE_CDATADIV = 3;
    var ZONE_INSERT = 4;

    function testInitial(initial) {
        switch (initial) {
            case '%':
            case '$':
            case '`':
            case 'c':
            case '<':
            case '&':
            case 'v':
                return 1;
            case '>':
            case '^':
            case '_':
            case '@':
            case '#':
            case 'a':
            case 'b':
            case 'f':
            case '?':
            case 'e':
            case 'i':
            case 'm':
            case ':':
            case 'q':
            case '=':
            case '+':
            case 'w':
            case '-':  
            case '*':
                return 2;
            default:
                return -1;
        }
    }
    
    function testInsertFirstChar(firstChar) {
        switch(firstChar) {
            case 'n':
            case 'l':
            case 'a':
            case 'c':
                return 2;
            case 'i':
            case 's':
            case 'w':
            case 'h':
            case 'r':
            case 'd':
                return 1;
            default :
                return -1;
        }
    }
    
    function getFirstTreeChars(stream) {
        let p = 0;
        let result = "";
        while(p < 3) {
            if (stream.eol()) {
                if (p > 0) {
                    stream.backUp(p);
                }
                return "";
            }
            let carac = stream.next();
            result = result + carac;
            p++;
        }
        stream.backUp(p);
        return result;
    }
    
    function threeAdvance(stream) {
        stream.next();
        stream.next();
        stream.next();
    }
    
    function tokenize(stream, state) {
        let mainClass = state.currentTokenFunction(stream, state);
        if (state.firstInLine) {
            state.firstInLine = false;
            mainClass = checkOnDiv(mainClass, state);
        }
        return mainClass;
    }
    
    function checkOnDiv(mainClass, state) {
        if (state.onDiv > 0) {
            state.onDiv = 2;
            if (!mainClass) {
                mainClass = "line-ficheblock-div";
            } else {
                mainClass = mainClass + " line-ficheblock-div";
            }
        }
        return mainClass;
    }
    
    function checkTextContentClass(mainClass, state) {
        if ((state.supplementaryClassesStack) && (state.supplementaryClassesStack.length > 0)) {
            let supplementaryClass = state.supplementaryClassesStack[state.supplementaryClassesStack.length - 1];
            if (!mainClass) {
                return supplementaryClass;
            } else {
                mainClass = mainClass + " " + supplementaryClass;
                return mainClass;
            }
        } else {
            return mainClass;
        }
    }
    
    function syntaxError(stream, state) {
        if (!stream.eol()) {
            stream.skipToEnd();
        }
        return "error";
    }
   
    function onZoneParametersToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            return null;
        }
        let zone = state.currentZone;
        if (state.onDiv === 1) {
            let langDone = stream.eat(/[a-z]/);
            if (langDone) {
                langDone = stream.eat(/[a-z]/);
                if (langDone) {
                    if (!stream.eol()) {
                        langDone = stream.eatWhile(/[-a-zA-Z]/);
                        stream.eatSpace();
                        if (!stream.eol()) {
                            langDone = false;
                        }
                    }
                }
            }
            if (langDone) {
                return "ficheblock-zone-parameters";
            } else {
                return syntaxError(stream, state);
            }
        } else if (zone === ZONE_CODE) {
            let firstChar = stream.next();
            if ((firstChar === 'x') || (firstChar === 's')) {
                stream.skipToEnd();
                return "ficheblock-zone-parameters";
            } else {
                return syntaxError(stream, state);
            }
        } else if (zone === ZONE_TABLE) {
            let isTableChar = stream.eat(/[hvt,;*:|]/);
            if (isTableChar) {
                return "ficheblock-zone-parameters";
            } else {
                stream.next();
                return "error";
            }
        } else if (zone === ZONE_INSERT) {
            let insertDone = false;
            let firstInsertChar = stream.next();
            if ((firstInsertChar === 'i') || (firstInsertChar === 'a')) {
                insertDone = true;
                stream.eatSpace();
            } else if (firstInsertChar === 'l') {
                insertDone = true;
                if (!stream.eol()) {
                    let secondChar = stream.next();
                    if ((secondChar !== 'r') && (secondChar !== 'l')) {
                        stream.backUp(1);
                    }
                }
                stream.eatSpace();
            }
            if (insertDone) {
                return "ficheblock-zone-parameters";
            } else {
                stream.next();
                return "error";
            }
        } else if (zone === ZONE_CDATADIV) {
            return syntaxError(stream, state);
        } else  {
            return syntaxError(stream, state);
        }
    }
    
    function onInsertSolToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            return null;
        }
        let first = stream.next();
        let test =  testInsertFirstChar(first);
        if ((stream.eol()) || (test === -1) || (stream.next() !== '=')) {
            return syntaxError(stream, state);
        }
        if (test === 2) {
            state.onContent = true;
            state.contentType = null;
            state.currentTokenFunction = onTextContentToken;
        } else {
            state.currentTokenFunction = onInsertValue;
        }
        return "ficheblock-zone-elementname";
    }
    
    function onZoneElementSolToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
            return null;
        }
        let first = stream.next();
        if ((first === 'n') || (first === 'l')) {
            if (stream.peek() === '=') {
                stream.next();
                state.onContent = true;
                state.contentType = null;
                state.currentTokenFunction = onTextContentToken;
                return "ficheblock-zone-elementname";
            }
        }
        if (state.onDiv === 1) {
            state.onDiv = 2;
        }
        stream.backUp(1);
        state.supplementaryClassesStack.pop();
        state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
        state.currentTokenFunction =  state.currentSolTokenFunction;
        return state.currentSolTokenFunction(stream, state);
    }    
  
    function onAttsSolToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            return null;
        }
        let next = stream.next();
        if (stream.eol()) {
            stream.backUp(1);
            return state.startContentTokenFunction(stream, state);
        }
        if (next === '[') {
            next = stream.next();
            if (next === '(') {
                state.currentTokenFunction = onNameAttsToken;
                return "operator";
            } else {
                stream.backUp(2);
            }
            
        } else {
            stream.backUp(1);
        }
        state.currentTokenFunction = state.startContentTokenFunction;
        return state.startContentTokenFunction(stream, state);
    }
    
    function onCdatadivSolToken(stream, state) {
        stream.skipToEnd();
        return "ficheblock-cdatacontent";
    }
    
    function startZoneCodeContentToken(stream, state) {
        stream.skipToEnd();
        return "ficheblock-codecontent";
    }
    
    function startZoneTableContentToken(stream, state) {
        state.onContent = true;
        state.contentType = null;
        stream.eatSpace();
        if (stream.eol()) {
            return null;
        }
        let next;
        let first = stream.next();
        let alone = true;
        let p = 1;
        while((next = stream.next()) != null) {
            p++;
            if (next !== ' ') {
                alone = false;
                break;
            }
        }
        if ((alone) && (first === '-')) {
            stream.eatSpace();
            return "ficheblock-emptyrow";
        } else if (first === '#') {
            stream.backUp(p - 1);
            state.supplementaryClassesStack = ["ficheblock-tableheader"];
            state.currentTokenFunction = onTextContentToken;
            return onTextContentToken(stream, state);
        } else {
            stream.backUp(p);
            state.supplementaryClassesStack = ["ficheblock-tablecontent"];
            state.currentTokenFunction = onTextContentToken;
            return onTextContentToken(stream, state);
        }
    }
    
    function startTextContentToken(stream, state) {
        state.onContent = true;
        state.contentType = null;
        state.supplementaryClassesStack = null;
        stream.eatSpace();
        if (stream.eol()) {
            state.contentType = "standard";
            return null;
        }
        let depth = 0;
        let next = stream.next();
        if ((next === '>') || (next === '!') || (next === '?')) {
            state.ulDepth = 0;
            state.currentTokenFunction = onTexteSourceToken;
            state.contentType = "special";
            return "ficheblock-firstchars";
        } else if (next === '#') {
            state.ulDepth = 0;
            stream.eatWhile('#');
            state.supplementaryClassesStack = ["ficheblock-hcontent"];
            state.currentTokenFunction = onTextContentToken;
            state.contentType = "h";
            return "ficheblock-firstchars";
         } else if (next === '-') {
            depth = 1;
            while((next = stream.next()) != null) {
                if (next === '-') {
                    depth++;
                } else {
                    stream.backUp(1);
                    break;
                }
            }
            if (depth > (state.ulDepth + 1)) {
                stream.backUp(depth - state.ulDepth - 1);
                state.ulDepth++;
                depth = state.ulDepth;
            } else {
                state.ulDepth = depth;
            }
            state.currentTokenFunction = onTexteSourceToken;
            state.contentType = "li-first";
            return "ficheblock-firstchars";
        } else if ((next === '_') && (state.ulDepth > 0)) {
            depth = 1;
            while((next = stream.next()) != null) {
                if (next === '_') {
                    depth++;
                } else {
                    stream.backUp(1);
                    break;
                }
            }
            if (depth > state.ulDepth) {
                stream.backUp(depth - state.ulDepth);
                depth = state.ulDepth;
            } else {
                state.ulDepth = depth;
            }
            state.currentTokenFunction = onTexteSourceToken;
            state.contentType = "li-next";
            return "ficheblock-firstchars";
        } else {
            stream.backUp(1);
            state.ulDepth = 0;
            state.currentTokenFunction = onTexteSourceToken;
            state.contentType = "standard";
            return onTexteSourceToken(stream, state);
        }
    }
    
    function onInsertValue(stream, state) {
        stream.skipToEnd();
        return "ficheblock-zone-elementvalue";
    }
    
    function onTexteSourceToken(stream, state) {
        stream.eatSpace();
        if (stream.peek() !== '@') {
            state.currentTokenFunction = onTextContentToken;
            return onTextContentToken(stream, state);
        }
        stream.next();
        let p = 1;
        let next;
        let end = false;
        let escaped = false;
        while ((next = stream.next()) != null) {
            p++;
            if (next === ':' && !escaped) {
                end = true;
                break;
            }
            escaped = !escaped && next === '\\';
        }
        state.currentTokenFunction = onTextContentToken;
        if (!end) {
            stream.backUp(p);
            return onTextContentToken(stream, state);
        } else {
            state.hasSource = true;
            return "ficheblock-source";
        }
    }
    
    function onTextContentToken(stream, state) {
        let ch = stream.next();
        if (ch === '{') {
            let emphasisCount = 1;
            state.emphasisType = "ficheblock-span-em";
            if (stream.peek() === '{') {
                stream.next();
                emphasisCount++;
                state.emphasisType = "ficheblock-span-strong";
                if (stream.peek() === '{') {
                    stream.next();
                    emphasisCount++;
                    state.emphasisType = "ficheblock-span-emstrong";
                }
            }
            state.emphasisCount = emphasisCount;
            state.currentTokenFunction = onEmphasisToken;
            stream.eatSpace();
            if (!stream.eol()) {
                return "operator";
            }
        } else if (ch === '[') {
            if (!stream.eol()) {
                let initialChar = stream.next();
                if (!stream.eol()) {
                    let followingChar = stream.next();
                    if ((initialChar === '%') && (followingChar === ']')) {
                        return "operator";
                    } else if ((followingChar === ' ') || (followingChar === '(')) {
                        let test = testInitial(initialChar);
                        if (test < 0) {
                            return syntaxError(stream, state);
                        } if (test === 2) {
                            state.techSpan = false;
                        } else {
                            state.techSpan = true;
                        }
                        stream.backUp(2);
                        state.currentTokenFunction = onInitialeToken;
                        return "operator";
                    }
                }
            }
        }
        stream.eatWhile(/[^\x5b\x7b]/);
        return checkTextContentClass(null, state);
    }
    
    function onInitialeToken(stream, state) {
        stream.next();
        let peek = stream.peek();
        if (peek === ' ') {
            state.currentTokenFunction = onSpanBracketToken;
        } else {
            state.currentTokenFunction = startSpanRefToken;
        }
        return "ficheblock-initial";
    }
    
    function startSpanRefToken(stream, state) {
        stream.next();
        state.currentTokenFunction = onSpanRefToken;
        return "operator";
    }
    
    function onSpanRefToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            return syntaxError(stream, state);
        }
        let next;
        let end = false;
        let escaped = false;
        let firstChar = stream.next();
        if (firstChar === '"') {
             while ((next = stream.next()) != null) {
                if (next === '"' && !escaped) {
                    end = true;
                    break;
                }
                escaped = !escaped && next === '\\';
            }
            if (!end) {
                return syntaxError(stream, state);
            }
            next = stream.peek();
            if ((next === ' ') || (next === ')')) {
                state.currentTokenFunction = onNameAttsToken;
            } else {
                 return syntaxError(stream, state);
            }
            return "string";
        } else if (firstChar === ')') {
            state.currentTokenFunction = onSpanBracketToken;
            return "operator";
        } else {
            end = false;
            let isAttName = true;
            if (!/[-_a-zA-Z]/.test(firstChar)) {
                isAttName = false;
            }
            while ((next = stream.next()) != null) {
                if (next === '"') {
                    return syntaxError(stream, state);
                } else if (next === ' ') {
                    isAttName = false;
                    end = true;
                    break;
                } else if (next === ')') {
                    isAttName = false;
                    end = true;
                    break;
                } else if (next === '=') {
                    if (isAttName) {
                        state.currentTokenFunction = onValueAttsToken;
                        return "ficheblock-attname";
                    }
                } else if (isAttName) {
                    if (!/[-_\.a-zA-Z0-9]/.test(next)) {
                        isAttName = false;    
                    }
                }
            }
            if (!end) {
                return syntaxError(stream, state);
            }
            stream.backUp(1);
            state.currentTokenFunction = onNameAttsToken;
            return "ficheblock-ref";
        }
    }

    function onNameAttsToken(stream, state) {
        stream.eatSpace();
        if (stream.eol()) {
            return syntaxError(stream, state);
        }
        let next = stream.next();
        let end = false;
        if (next === ')') {
            if (state.onContent) {
                state.currentTokenFunction = onSpanBracketToken;
            } else {
                 state.currentTokenFunction = endBlockBracketToken;
            }
            return "operator";
        }
        if (!/[-_a-zA-Z]/.test(next)) {
           return syntaxError(stream, state);     
        }
        while((next = stream.next()) != null) {
            if (next === '=') {
                end = true;
                break;
            }
            if (!/[-_\.a-zA-Z0-9]/.test(next)) {
            return syntaxError(stream, state);     
            }
        }
        if (!end) {
            return syntaxError(stream, state);
        }
        state.currentTokenFunction = onValueAttsToken;
        return "ficheblock-attname";
    }
    
    function onValueAttsToken(stream, state) {
        let end = false;
        let escaped = false;
        let next = stream.next();
        if (next === ')') {
            if (state.onContent) {
                state.currentTokenFunction = onSpanBracketToken;
            } else {
                 state.currentTokenFunction = endBlockBracketToken;
            }
            return "operator";
        } else if (next === ' ') {
            state.currentTokenFunction = onNameAttsToken;
            return "operator";
        } else if (next === '"') {
             while ((next = stream.next()) != null) {
                if (next === '"' && !escaped) {
                    end = true;
                    break;
                }
                escaped = !escaped && next === '\\';
            }
            if (!end) {
                return syntaxError(stream, state);
            }
            next = stream.peek();
            if ((next === ' ') || (next === ')')) {
                state.currentTokenFunction = onNameAttsToken;
            } else {
                 return syntaxError(stream, state);
            }
            return "string";
        }
        while((next = stream.next()) != null) {
            if ((next === ' ') || (next === ')'))  {
                end = true;
                break;
            }
        }
        if (!end) {
            return syntaxError(stream, state);
        }
        stream.backUp(1);
        state.currentTokenFunction = onNameAttsToken;
        return "ficheblock-attvalue";
    }
    
    function onEmphasisToken(stream, state) {
        let next;
        let end = false;
        let escaped = false;
        while((next = stream.next()) != null) {
            if (next === '}' && !escaped) {
                end = true;
                break;
            }
            escaped = !escaped && next === '\\';
        }
        if (!end) {
            return syntaxError(stream, state);
        }
        stream.backUp(1);
        state.currentTokenFunction = endEmphasisToken;
        return checkTextContentClass(state.emphasisType, state);
    }

    function endEmphasisToken(stream, state) {
        stream.next();
        if (state.emphasisCount > 1) {
            if (stream.peek() === '}') {
                 stream.next();
                 if (state.emphasisCount > 1) {
                    if (stream.peek() === '}') {
                        stream.next();
                    }
                }
            }
        }
        state.currentTokenFunction = onTextContentToken;
        return "operator";
    }
    
    function onSpanBracketToken(stream, state) {
        let next;
        let end = false;
        let escaped = false;
        while((next = stream.next()) != null) {
            if (next === ']' && !escaped) {
                end = true;
                break;
            }
            escaped = !escaped && next === '\\';
        }
        if (!end) {
            return syntaxError(stream, state);
        }
        stream.backUp(1);
        state.currentTokenFunction = endSpanBracketToken;
        let className = "ficheblock-span-default";
        if (state.techSpan) {
            className = "ficheblock-span-tech";
        }        
        className = checkTextContentClass(className, state);
        return className;
    }
    
    function endBlockBracketToken(stream, state) {
        let next = stream.next();
        if (next === ']') {
            state.currentTokenFunction = state.startContentTokenFunction;
            return "operator";
        } else if (next === '(') {
             state.currentTokenFunction = onNameAttsToken;
            return "operator";
        } else {
            return syntaxError(stream, state);
        }
    }
    
    function endSpanBracketToken(stream, state) {
        stream.next();
        state.currentTokenFunction = onTextContentToken;
        return "operator";
    }

    
    return {
        startState: function() {
            return {
                currentTokenFunction: onAttsSolToken,
                currentSolTokenFunction: onAttsSolToken,
                zoneSolTokenFunctionStack: [],
                supplementaryClassesStack: null,
                startContentTokenFunction: startTextContentToken,
                currentZone: ZONE_STANDARD,
                emphasisCount: 0,
                emphasisType: "",
                techSpan: false,
                onContent: false,
                onSyntaxError: false,
                firstInLine: false,
                onDiv: 0,
                ulDepth: 0,
                lineType: null
            };
        },
        token: function (stream, state) {
            if (stream.sol()) {
                state.firstInLine = true;
                state.onContent = false;
                state.contentType = null;
                state.emphasisCount = 0;
                state.emphasisType = "";
                state.techSpan = false;
                state.hasSource = false;
                state.currentTokenFunction = state.currentSolTokenFunction;
                stream.eatSpace();
                if (stream.eol()) {
                    if (state.currentZone === ZONE_STANDARD) {
                        state.onContent = true;
                        state.contentType = "standard";
                    }
                    return null;
                }
                let firstThreeChars = getFirstTreeChars(stream);
                if (state.currentZone === ZONE_STANDARD) {
                    if (firstThreeChars === "???") {
                        _specialClear(state);
                        state.currentZone = ZONE_CDATADIV;
                        threeAdvance(stream);
                        state.currentTokenFunction = onZoneParametersToken;
                        state.zoneSolTokenFunctionStack = [onCdatadivSolToken, onZoneElementSolToken];
                        state.supplementaryClassesStack = ["ficheblock-zone-elementcontent"];
                        state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
                        return checkOnDiv("ficheblock-cdata ficheblock-zone-firstchars", state);
                    } else if (firstThreeChars === "+++") {
                        _specialClear(state);
                        state.currentZone = ZONE_CODE;
                        threeAdvance(stream);
                        state.currentTokenFunction = onZoneParametersToken;
                        state.zoneSolTokenFunctionStack = [onAttsSolToken, onZoneElementSolToken];
                        state.supplementaryClassesStack = ["ficheblock-zone-elementcontent"];
                        state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
                        state.startContentTokenFunction = startZoneCodeContentToken;
                        return checkOnDiv("ficheblock-code ficheblock-zone-firstchars", state);
                    } else if (firstThreeChars === "!!!") {
                        _specialClear(state);
                        if (state.onDiv > 0) {
                            _backToStandard(state);
                            state.onDiv = 0;
                            stream.skipToEnd();
                        } else {
                            state.onDiv = 1;
                            threeAdvance(stream);
                            state.currentTokenFunction = onZoneParametersToken;
                            state.zoneSolTokenFunctionStack = [onAttsSolToken, onZoneElementSolToken];
                            state.supplementaryClassesStack = ["ficheblock-zone-elementcontent"];
                            state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
                        }
                        return "ficheblock-div ficheblock-zone-firstchars";
                    } else if (firstThreeChars === "===") {
                        _specialClear(state);
                        state.currentZone = ZONE_TABLE;
                        threeAdvance(stream);
                        state.currentTokenFunction = onZoneParametersToken;
                        state.zoneSolTokenFunctionStack = [onAttsSolToken, onZoneElementSolToken];
                        state.supplementaryClassesStack = ["ficheblock-tablecontent", "ficheblock-zone-elementcontent"];
                        state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
                        state.startContentTokenFunction = startZoneTableContentToken;
                        return checkOnDiv("ficheblock-table ficheblock-zone-firstchars", state);
                    } else if (firstThreeChars === ":::") {
                        _specialClear(state);
                        state.currentZone = ZONE_INSERT;
                        threeAdvance(stream);
                        state.currentTokenFunction = onZoneParametersToken;
                        state.zoneSolTokenFunctionStack = [onInsertSolToken];
                        state.supplementaryClassesStack = ["ficheblock-zone-elementcontent"];
                        state.currentSolTokenFunction = state.zoneSolTokenFunctionStack.pop();
                        return checkOnDiv("ficheblock-insert ficheblock-zone-firstchars", state);
                    }
                } else if (state.currentZone === ZONE_CDATADIV) {
                    if (firstThreeChars === "???") {
                        stream.skipToEnd();
                        _backToStandard(state);
                        return checkOnDiv("ficheblock-cdata ficheblock-zone-firstchars", state);
                    }
                } else if (state.currentZone === ZONE_CODE) {
                    if (firstThreeChars === "+++") {
                        stream.skipToEnd();
                        _backToStandard(state);
                        return checkOnDiv("ficheblock-code ficheblock-zone-firstchars", state);
                    }
                } else if (state.currentZone === ZONE_TABLE) {
                    if (firstThreeChars === "===") {
                        stream.skipToEnd();
                        _backToStandard(state);
                        return checkOnDiv("ficheblock-table ficheblock-zone-firstchars", state);
                    }
                } else if (state.currentZone === ZONE_INSERT) {
                    if (firstThreeChars === ":::") {
                        stream.skipToEnd();
                        _backToStandard(state);
                        return checkOnDiv("ficheblock-insert ficheblock-zone-firstchars", state);
                    }
                }
                
            } else {
                state.firstInLine = false;
            }
            return tokenize(stream, state);
            
            function _specialClear(state) {
                state.supplementaryClassesStack = null;
                state.ulDepth = 0;
            }
            
            function _backToStandard(state) {
                state.currentZone = ZONE_STANDARD;
                state.currentSolTokenFunction = onAttsSolToken;
                state.startContentTokenFunction = startTextContentToken;
                state.supplementaryClassesStack = null;
                state.ulDepth = 0;
            }
        }
    };

});//Fin de CodeMirror.defineMode

CodeMirror.defineMIME('text/plain', 'ficheblock');