package scanner;

import static util.Characters.*;

import java.io.*;

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

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

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

    @Override
    public int read() {
        try {
            return readWithIOException();
        } catch (IOException ioException) {
            throw new UncheckedIOException(ioException);
        }
    }

    private int readWithIOException() throws IOException {
        if (!rereadLastCharacter)
            return readFromUnderlyingInputStream();

        rereadLastCharacter = false;

        return currentCharacter;
    }

    private int readFromUnderlyingInputStream() throws IOException {
        readNextCharacter();

        if (haveEnteredComment())
            consumeAllBytesInComment();

        return currentCharacter;

    }

    private void readNextCharacter() throws IOException {
        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() throws IOException {
        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;
    }

}
