/* global CodeMirror */
CodeMirror.defineMode('tableexport', function() {
    
    /*
     * Pour le format de l'exportation tabulaire, les lignes ont leur importance, une ligne blanche indique un nouveau bloc qui doit
     * contenir de deux à quatre lignes.
     * Première ligne : le nom de la colonne
     * Deuxième ligne : les champs d'origine des données
     * Troisième ligne : le formatage de ces champs
     * Quatrième ligne : une ligne d'instructions à appliquer (autrement dit, une ligne de paramètres supplémentaires)
     * Les lignes en plus sont traitées comme des erreurs (UNEXCEPTED_LINE)
     * Chaque ligne a ses spécificités, c'est pourquoi il y a une fonction de traitement des « token » par ligne
     * (colLineTokenFunction, sourceLineTokenFunction, patternLineTokenFunction, instructionLineTokenFunction, unexceptedLineTokenFunction)
     * 
     * Il y a deux types de lignes spéciales :
     * - celles qui commencent par #, ce sont des commentaires
     * - celles qui commencent par !, elles indiquent la définition d'un paramètre global
     */
    var NEW_BLOCK = 0;
    var COL_LINE = 1;
    var SOURCE_LINE = 2;
    var PATTERN_LINE = 3;
    var INSTRUCTION_LINE = 4;
    var UNEXCEPTED_LINE = 5;
    var INCLUSION_LINE = 6;
    var START_COLON = 1;
    var BEFORE_COLON = 2;
    var AFTER_COLON = 3;
    var ERROR_COLON = 4;
    
    /*
     * instructionKeys contient les constantes de la forme *_PARAMKEY des interfaces net.mapeadores.util.format.FormatConstants
     * et net.fichotheque.format.FichothequeFormatConstants
     *
     * instructionValues contient les constantes de la forme *_PARAMVALUE des interfaces net.mapeadores.util.format.FormatConstants
     * et net.fichotheque.format.FichothequeFormatConstants
     */    
    
    
    var instructionValues = ['date','decimal','money','integer','percentage','json','jsonml','jsonstructure','uri_clean','uri_encode','last'];
    var instructionKeys = ['calc','cast','defval','globalselect','limit','null','pos','posttransform','prefix','sep','suffix','sum','defprop','noitem','maxlength','fixedlength','fixedchar','fixedempty','columnsum','unique','formula','onlyitem','jsonarray','idsort',
    'sep1','sep2','sep3','sep4','sep5','sep6','sep7','sep8','sep9',
    'sep1_2','sep1_3','sep1_4','sep1_5','sep1_6','sep1_7','sep1_8','sep1_9',
    'sep2_3','sep2_4','sep2_5','sep2_6','sep2_7','sep2_8','sep2_9',
    'sep3_4','sep3_5','sep3_6','sep3_7','sep3_8','sep3_9',
    'sep4_5','sep4_6','sep4_7','sep4_8','sep4_9',
    'sep5_6','sep5_7','sep5_8','sep5_9',
    'sep6_7','sep6_8','sep6_9',
    'sep7_8','sep7_9',
    'sep8_9'];
    var thesaurusTreeValues = ['ancestors','descendants','siblings','parent','children'];
    
    function testBeforeColonSource(source) {
        var length = source.length;
        if (source.charAt(length - 1) === '*') {
            if (length === 1) {
                return "error";
            } else {
                source = source.substring(0, length - 1);
            }
        }
        if (!/^[_a-zA-Z]([-_.a-zA-Z0-9]*)$/.test(source)) {
            return "error";
        }
        return "tableexport-formatsource";
    }
    
    function testAfterColonSource(source) {
        if (!/^[_a-zA-Z]([-_.a-zA-Z0-9]*)$/.test(source)) {
            return "error";
        }
        return "tableexport-formatsource";
    }
    
    function testSubsetPath(subsetPath) {
        if (thesaurusTreeValues.indexOf(subsetPath) !== -1) {
            return "tableexport-subsetpath";
        }
        if (/^fiche_[a-z]([a-z0-9_]*)$/.test(subsetPath)) {
            return "tableexport-subsetpath";
        }
        if (/^motcle_[a-z]([a-z0-9_]*)$/.test(subsetPath)) {
            return "tableexport-subsetpath";
        }
        if (/^corpus_[a-z]([a-z0-9_]*)$/.test(subsetPath)) {
            return "tableexport-subsetpath";
        }
        if (/^thesaurus_[a-z]([a-z0-9_]*)$/.test(subsetPath)) {
            return "tableexport-subsetpath";
        }
        return "error";
    }
    
    function tokenize(stream, state) {
        var length = state.tokenFunctionStack.length;
        if (length === 0) {
            return state.mainTokenFunction(stream, state);
        }
        return state.tokenFunctionStack[length - 1](stream, state);
    }
    
    var parameterLineTokenFunction = function(stream, state) {
        if (state.onParameterValue) {
            if (stream.next() === '=') {
                return "operator";
            }
            stream.skipToEnd();
            return "tableexport-paramvalue";
        } else {
            if (stream.peek() === '=') {
                stream.skipToEnd();
                return "error";
            }
            stream.eatWhile(/[^=]/);
            if (stream.eol()) {
                return "error";
            }
            state.onParameterValue = true;
            return "tableexport-paramname";
        }
    };
    
    var bigParameterStartTokenFunction = function(stream, state) {
        stream.skipToEnd();
        state.onBigParam = true;
        return "tableexport-bigparam-name";
    };
    
    var bigParameterEndTokenFunction = function(stream, state) {
        stream.skipToEnd();
        return "comment";
    };
    
    var colLineTokenFunction = function(stream, state) {
        var ch = stream.next();
        if (ch === '>') {
            state.mainTokenFunction = instructionLineTokenFunction;
            return "operator";
        }
        stream.eatWhile(/\w/);
        return "tableexport-coldef";
    };
    
    var inclusionNameTokenFunction = function(stream, state) {
        var ch = stream.next();
        if (ch === '>') {
            state.mainTokenFunction = instructionLineTokenFunction;
            return "operator";
        }
        stream.eatWhile(/\w/);
        return "tableexport-inclusionname";
    };
    
    var sourceLineTokenFunction = function(stream, state) {
        var isValidSlash = state.validSlash;
        var colonState = state.colonState;
        state.validSlash = false;
        var ch = stream.next();
        if (ch === ',' || ch === ';') {
            state.colonState = START_COLON;
            return "operator";
        }
        if (ch === '/') {
            if ((isValidSlash) || (state.colonState === AFTER_COLON)) {
                return "operator";
            } else {
                return "error";
            }
        }
        if (ch === ':') {
            if ((state.colonState === ERROR_COLON) || (state.colonState === START_COLON) || (stream.eol()) || (/[,;:\s]/.test(stream.peek()))) {
                state.colonState = ERROR_COLON;
                return "error";
            } else {
                state.colonState = AFTER_COLON;
                return "operator";
            }
        }
        stream.eatWhile(/[^,;/:\s]/);
        if (colonState === ERROR_COLON) {
            return "error";
        } else if (colonState === AFTER_COLON) {
            return testAfterColonSource(stream.current());
        } else {
            state.colonState = BEFORE_COLON;
            if (stream.peek() === '/') {
            	stream.next();
            	if ((stream.eol()) || (/[,;/\s]/.test(stream.peek()))) {
                	return "error";
            	}
            	state.validSlash = true;
            	stream.backUp(1);
            	return testSubsetPath(stream.current());
        	} else {
            	return testBeforeColonSource(stream.current());
        	}
    	}
    };
    
    var patternLineTokenFunction = function(stream, state) {
        var testTiret = state.sol;
        var ch = stream.next();
        if (testTiret) {
            if (ch === '-') {
                stream.eatSpace();
                if (stream.eol()) {
                    return "tableexport-emptypatternsymbol";
                } else {
                    return "word";
                }
            }
        }
        if (ch === '{') {
            state.onPatternKey = true;
            state.tokenFunctionStack.push(patternTokenFunction);
            return "tableexport-formatpattern";
        }
        if (ch === '|') {
            if (stream.eat('|')) {
                return "tableexport-patternseparator";
            }
        }
        return "word";
    };
        
    var instructionLineTokenFunction = function(stream, state) {
        var ch = stream.next();
        if (ch === '"') {
            state.tokenFunctionStack.push(stringTokenFunction);
            return tokenize(stream, state);
        }
        if (ch === '=') {
            return "operator";
        }
        if (ch === ',') {
            return "operator";
        }
        if (ch === ';') {
            return "operator";
        }
        if (/\d/.test(ch)) {
            stream.eatWhile(/\d/);
            if((stream.eol()) || (!/\w/.test(stream.peek()))) {
                return 'number';
            }
        }
        stream.eatWhile(/\w/);
        var cur = stream.current();
        if (instructionKeys.indexOf(cur) !== -1) return "tableexport-instructionkey";
        if (instructionValues.indexOf(cur) !== -1) return "tableexport-instructionvalue";
        return "word";
    };
        
    var unexceptedLineTokenFunction = function (stream, state) {
        stream.skipToEnd();
        return "error";
    };

    var inclusionLineTokenFunction = function (stream, state) {
        stream.skipToEnd();
        return "tableexport-inclusionline";
    };
        
    var stringTokenFunction = function (stream, state) {
        var next, end = false, escaped = false;
        while ((next = stream.next()) != null) {
            if (next === '"' && !escaped) {
                end = true;
                break;
            }
            escaped = !escaped && next === '\\';
        }
        state.tokenFunctionStack.pop();
        if (!end) {
            return "error";
        }
        return "string";
    };
        
    var patternTokenFunction = function(stream, state) {
        var ch = stream.next();
        if (ch === '"') {
            state.tokenFunctionStack.push(stringTokenFunction);
            return tokenize(stream, state);
        }
        if (ch === '=') {
            state.onPatternKey = false;
            return "operator";
        }
        if (ch === ',') {
            state.onPatternKey = true;
            return "operator";
        }
        if (ch === ';') {
            state.onPatternKey = true;
            return "operator";
        }
        if (ch === '}') {
            state.tokenFunctionStack.pop();
            return "tableexport-formatpattern";
        }
        if (/\d/.test(ch)) {
            stream.eatWhile(/\d/);
            if((stream.eol()) || (!/\w/.test(stream.peek()))) {
                return 'number';
            }
        }
        stream.eatWhile(/\w/);
        return (state.onPatternKey)?"tableexport-formatpattern":"quote";
    };
    
    
    return {
        startState: function() {
            return {
                mainTokenFunction: colLineTokenFunction,
                tokenFunctionStack:[],
                sol:false,
                withinPattern: false,
                currentLine: NEW_BLOCK,
                validSlash: false,
                onParameterValue: false,
                onBigParam: false
            };
        },
        token: function (stream, state) {
            if (stream.sol()) {
                stream.eatSpace();
                if (stream.eol()) {
                    state.currentLine = NEW_BLOCK;
                    return null;
                }
                if (state.onBigParam) {
                    if (stream.match("!!end", true)) {
                        state.onBigParam = false;
                        state.mainTokenFunction = bigParameterEndTokenFunction;
                        return "tableexport-bigparam-end";
                    } else {
                        stream.skipToEnd();
                        return "tableexport-bigparam-content";
                    }
                }
                var firstChar = stream.peek();
                if (firstChar === '#') {
                    stream.skipToEnd();
                    return "comment";
                }
                state.sol = true;
                state.validSlash = false;
                state.colonState = START_COLON;
                state.onParameterValue = false;
                if (state.currentLine === NEW_BLOCK) {
                    if (firstChar === '!') {
                        stream.next();
                        if (stream.match("!start ", true)) {
                            stream.eatSpace();
                            if (stream.eol()) {
                                stream.skipToEnd();
                                return "error";
                            } else {
                                state.mainTokenFunction = bigParameterStartTokenFunction;
                                return "tableexport-bigparam-start";
                            }
                        } else {
                            stream.eatSpace();
                            if (stream.peek() === '!') {
                                stream.skipToEnd();
                                return "error";
                            }
                            state.mainTokenFunction = parameterLineTokenFunction;
                            return "tableexport-paramsymbol";
                        }
                    }
                    if (firstChar === '%') {
                        stream.next();
                        state.currentLine = INCLUSION_LINE;
                        state.mainTokenFunction = inclusionNameTokenFunction;
                        return "tableexport-inclusionsymbol";
                    }
                }
                switch(state.currentLine) {
                    case NEW_BLOCK :
                        state.currentLine = COL_LINE;
                        state.mainTokenFunction = colLineTokenFunction;
                        break;
                    case COL_LINE :
                        state.currentLine = SOURCE_LINE;
                        state.mainTokenFunction = sourceLineTokenFunction;
                        break;
                    case SOURCE_LINE:
                        state.currentLine = PATTERN_LINE;
                        state.mainTokenFunction = patternLineTokenFunction;
                        break;
                    case PATTERN_LINE:
                        state.currentLine = INSTRUCTION_LINE;
                        state.mainTokenFunction = instructionLineTokenFunction;
                        break;
                    case INSTRUCTION_LINE:
                        state.currentLine = UNEXCEPTED_LINE;
                        state.mainTokenFunction = unexceptedLineTokenFunction;
                        break;
                    case INCLUSION_LINE:
                        state.mainTokenFunction = inclusionLineTokenFunction;
                        break;
                }
            } else {
                state.sol = false;
            }
            if (stream.eatSpace()) return null;
            return tokenize(stream, state);
        },
        blankLine: function (state) {
            state.currentLine = NEW_BLOCK;
        }
    };
    
});//Fin de CodeMirror.defineMode

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