package scanner;

import static util.Characters.BACKSLASH;
import static util.Characters.DOUBLE_QUOTE;
import static util.Characters.EOF;
import static util.Characters.NEWLINE;
import static util.Characters.SEMICOLON;

import java.io.InputStream;

import stream.SafeInputStream;

/**
 * Removes Lisp comments from an input stream.
 */
public class LispCommentRemovingInputStream implements LispInputStream {

    private SafeInputStream underlyingInputStream;
    private boolean isInQuotedString;
    private boolean rereadLastCharacter;
    private int previousCharacter;
    private int currentCharacter;

    public LispCommentRemovingInputStream(InputStream underlyingInputStream) {
        this.underlyingInputStream = new SafeInputStream(underlyingInputStream);
        this.isInQuotedString = false;
        this.rereadLastCharacter = false;
        this.previousCharacter = 0;
        this.currentCharacter = 0;
    }

    @Override
    public int read() {
        if (!rereadLastCharacter)
            return readFromUnderlyingInputStream();

        rereadLastCharacter = false;

        return currentCharacter;
    }

    private int readFromUnderlyingInputStream() {
        readNextCharacter();

        if (haveEnteredComment())
            consumeAllBytesInComment();

        return currentCharacter;
    }

    private void readNextCharacter() {
        previousCharacter = currentCharacter;
        currentCharacter = underlyingInputStream.read();

        if (haveEncounteredStringBoundary())
            isInQuotedString = !isInQuotedString;
    }

    private boolean haveEncounteredStringBoundary() {
        return (previousCharacter != BACKSLASH) && (currentCharacter == DOUBLE_QUOTE);
    }

    private boolean haveEnteredComment() {
        return (currentCharacter == SEMICOLON) && (!isInQuotedString);
    }

    private void consumeAllBytesInComment() {
        while (stillInComment())
            currentCharacter = underlyingInputStream.read();
    }

    private boolean stillInComment() {
        return (currentCharacter != NEWLINE) && (currentCharacter != EOF);
    }

    @Override
    public void unreadLastCharacter() {
        if (rereadLastCharacter)
            throw new MaximumUnreadsExceededException();

        this.rereadLastCharacter = true;
    }

}
