package org.reasm.commons.source;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;

import org.reasm.source.SourceLocation;
import org.reasm.source.SourceNodeRangeReader;
import org.reasm.source.SourceNodeRangeReader.SkipHandler;

/**
 * An implementation of {@link SkipHandler} for {@link SourceLocation}s referencing a {@link LogicalLine}.
 *
 * @author Francis Gagné
 */
public final class LogicalLineRangeReaderSkipHandler extends SkipHandler {

    /**
     * The state of the process of skipping a continuation character and the following line break and initial whitespace.
     */
    @Immutable
    private enum ContinuationCharacterSkipState {
        LOOK_FOR_CONTINUATION_CHARACTER, EXPECT_CR_OR_LF, EXPECT_LF_OR_WHITESPACE, EXPECT_WHITESPACE
    }

    @Nonnull
    private ContinuationCharacterSkipState continuationCharacterSkipState = ContinuationCharacterSkipState.LOOK_FOR_CONTINUATION_CHARACTER;

    /**
     * Initializes a new LogicalLineRangeReaderSkipHandler.
     */
    public LogicalLineRangeReaderSkipHandler() {
    }

    @Override
    protected boolean skipCurrentCodePoint() {
        final SourceNodeRangeReader reader = this.getReader();
        assert reader != null;
        final LogicalLine logicalLine = SourceLocationUtils.getLogicalLineRequired(reader.getSourceLocation());

        switch (this.continuationCharacterSkipState) {
        case LOOK_FOR_CONTINUATION_CHARACTER:
            // This state is processed after the switch, because some states may reset the state, in which case we must look for a
            // continuation character.
            break;

        case EXPECT_CR_OR_LF:
            if (reader.getCurrentChar() == '\r') {
                this.continuationCharacterSkipState = ContinuationCharacterSkipState.EXPECT_LF_OR_WHITESPACE;
                return true;
            }

            if (reader.getCurrentChar() == '\n') {
                this.continuationCharacterSkipState = ContinuationCharacterSkipState.EXPECT_WHITESPACE;
                return true;
            }

            throw new AssertionError("Unexpected character: '" + reader.getCurrentChar() + "'"); // unreachable

        case EXPECT_LF_OR_WHITESPACE:
            this.continuationCharacterSkipState = ContinuationCharacterSkipState.EXPECT_WHITESPACE;

            if (reader.getCurrentChar() == '\n') {
                return true;
            }

            // fall through

        case EXPECT_WHITESPACE:
            if (Syntax.isWhitespace(reader.getCurrentCodePoint())) {
                // Remain in this state as long as we encounter whitespace.
                return true;
            }

            break;

        default:
            throw new AssertionError(); // unreachable
        }

        // Reset the state to LOOK_FOR_CONTINUATION_CHARACTER.
        this.continuationCharacterSkipState = ContinuationCharacterSkipState.LOOK_FOR_CONTINUATION_CHARACTER;

        if (logicalLine.isContinuationCharacter(reader.getCurrentPositionInSourceNode())) {
            this.continuationCharacterSkipState = ContinuationCharacterSkipState.EXPECT_CR_OR_LF;
            return true;
        }

        return false;
    }

}

@@ -1686,35 +1686,40 @@
 ader.getCurrentC
-har
+odePoint
 () == '%5Cr') %7B%0A  
@@ -1896,35 +1896,40 @@
 ader.getCurrentC
-har
+odePoint
 () == '%5Cn') %7B%0A  
@@ -2111,16 +2111,34 @@
 onError(
+new StringBuilder(
 %22Unexpec
@@ -2158,40 +2158,99 @@
 : '%22
- + reader.getCurrentChar() + %22'%22
+).appendCodePoint(reader.getCurrentCodePoint())%0A                    .append(%22'%22).toString()
 ); /
@@ -2437,19 +2437,24 @@
 CurrentC
-har
+odePoint
 () == '%5C

Stop using getCurrentChar() from CharSequenceReader