package org.python.pydev.core.partition;

import java.util.ArrayList;

import org.python.pydev.shared_core.partitioner.IChangeTokenRule;
import org.python.pydev.shared_core.partitioner.ICharacterScanner;
import org.python.pydev.shared_core.partitioner.IMarkScanner;
import org.python.pydev.shared_core.partitioner.IPredicateRule;
import org.python.pydev.shared_core.partitioner.IToken;
import org.python.pydev.shared_core.partitioner.Token;
import org.python.pydev.shared_core.string.FastStringBuffer;

public class SingleLineRuleWithMultipleStarts implements IPredicateRule, IChangeTokenRule {

    protected IToken fToken;
    private final char escapeCharacter;
    private final boolean escapeContinuesLine;
    private final ArrayList<char[]> fStartSequences;
    private final char[] fEndSequence;

    @Override
    public void setToken(IToken token) {
        this.fToken = token;
    }

    public SingleLineRuleWithMultipleStarts(String[] startSequences, String endSequence, Token token,
            char escapeCharacter,
            boolean escapeContinuesLine) {
        ArrayList<char[]> lst = new ArrayList<>(startSequences.length);
        for (String start : startSequences) {
            lst.add(start.toCharArray());
        }
        this.fStartSequences = lst;
        this.fEndSequence = endSequence.toCharArray();
        this.fToken = token;
        this.escapeCharacter = escapeCharacter;
        this.escapeContinuesLine = escapeContinuesLine;
    }

    @Override
    public IToken evaluate(ICharacterScanner scanner) {
        return evaluate(scanner, false);
    }

    @Override
    public IToken getSuccessToken() {
        return fToken;
    }

    @Override
    public IToken evaluate(ICharacterScanner scanner, boolean resume) {
        if (resume) {
            if (detectEnd(scanner)) {
                return fToken;
            }
        } else {
            IMarkScanner markScanner = (IMarkScanner) scanner;
            int mark = markScanner.getMark();
            int c;

            int size = fStartSequences.size();
            for (int j = 0; j < size; j++) {
                boolean found = true;

                char[] startSequence = fStartSequences.get(j);
                for (int i = 0; i < startSequence.length; i++) {
                    c = scanner.read();
                    if (c != startSequence[i]) {
                        //Backup to where we started
                        found = false;
                        markScanner.setMark(mark);
                        break;
                    }
                }
                if (found) {
                    break;
                } else {
                    //Didn't find... go to next (unless we checked all: in this case return that
                    //we didn't match the start).
                    if (j == size - 1) {
                        return Token.UNDEFINED;
                    }
                }
            }

            //if it got here, the start was detected
            if (detectEnd(scanner)) {
                return fToken;
            } else {
                markScanner.setMark(mark);
            }
        }

        return Token.UNDEFINED;
    }

    private boolean detectEnd(ICharacterScanner scanner) {
        while (true) {
            int c = scanner.read();
            if (c == ICharacterScanner.EOF) {
                //match
                return true;

            } else if (c == escapeCharacter) {
                if (escapeContinuesLine) {
                    //Consume new line and keep on matching
                    c = scanner.read();
                    if (c == '\r') {
                        c = scanner.read();
                        if (c != '\n') {
                            scanner.unread();
                        }
                    }
                } else {
                    //Escape does not continue line: if it's a new line, match it (but don't consume it).
                    c = scanner.read();
                    if (c == '\r' || c == '\n') {
                        scanner.unread();
                    }

                    return true;
                }
            } else if (c == '\r' || c == '\n') {
                //If it's a new line, match it (but don't consume it).
                //                scanner.unread();
                return true;

            } else if (c == fEndSequence[0]) {
                // Let's check if we had a match: if we did, return true, otherwise, keep on going.

                //matched first. Let's check the remainder
                boolean found = true;
                for (int i = 1; i < fEndSequence.length; i++) {
                    c = scanner.read();
                    if (c != fEndSequence[i]) {
                        found = false;
                        scanner.unread();
                        for (int j = 0; j < i; j++) {
                            scanner.unread();
                        }
                        break;
                    }
                }
                return found;

            }
        }
    }

    @Override
    public String toString() {
        FastStringBuffer buf = new FastStringBuffer("SingleLineRuleWithMultipleStarts(", fEndSequence.length + 40);
        buf.append("start: ");
        for (char[] chars : this.fStartSequences) {
            buf.append(chars).append(",\n");
        }
        buf.append("end: ")
                .append(fEndSequence)
                .append(")");
        return buf.toString();
    }
}
