import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.function.Predicate;

public class CastScanner implements AutoCloseable {
    private final Reader reader;
    private final char[] buffer;
    private int pose = -2;
    private int bufferLength;
    private final char lineSep = System.lineSeparator().charAt(System.lineSeparator().length() - 1);

    private final Predicate<Character> filter;

    public CastScanner(InputStream in, Predicate<Character> filter) {
        this (new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)), filter);
    }

    public CastScanner(File in, Predicate<Character> filter) throws FileNotFoundException {
        this(new BufferedReader(new InputStreamReader(new FileInputStream(in), StandardCharsets.UTF_8)), filter);
    }

    public CastScanner(Reader in, Predicate<Character> filter) {
        this.reader = in;
        this.filter = filter;
        this.buffer = new char[8192];
    }

    private boolean hasNextChar() throws IOException {
        if (newBuffer() && bufferLength != -1) {
            if ((pose != bufferLength && (isCorrectChar(buffer[pose]) || buffer[pose] == lineSep || buffer[pose] == '\r')) ||
                    !filter.test(buffer[pose])) {
                nextChar();
                return false;
            }
            return true;
        }
        return false;
    }

    private void nextChar() throws IOException {
        while (newBuffer()) {
            while ((bufferLength != -1) && (pose != bufferLength) &&
                    ((isCorrectChar(buffer[pose]) || buffer[pose] == lineSep || buffer[pose] == '\r') ||
                            !filter.test(buffer[pose]))) {
                if (buffer[pose] == lineSep || buffer[pose] == '\r') {
                    break;
                }
                pose++;
            }
            if (pose != bufferLength) {
                break;
            }
        }
    }

    private boolean newBuffer() throws IOException {
        if (pose == -2 || pose == bufferLength) {
            bufferLength = reader.read(buffer);
            pose = 0;
        } else return bufferLength != -1;
        return true;
    }

    public boolean hasNext() throws IOException {
        newBuffer();
        return bufferLength != -1;
    }

    public boolean hasNextInLine() throws IOException {
        if (!(newBuffer() && buffer[pose] != lineSep && buffer[pose] != '\r')) {
            pose += 1;
            return false;
        }
        return true;
    }

    public String next() throws IOException {
        StringBuilder word = new StringBuilder();
        while (true) {
            if (hasNextChar()) {
                word.append(buffer[pose]);
                pose++;
            } else {
                break;
            }
        }
        return word.toString();
    }

    private static boolean isDash(char ch) {
        return Character.getType(ch) == Character.DASH_PUNCTUATION;
    }

    private static boolean isCorrectChar(char ch) {
        return (!Character.isLetter(ch) && ch != '\'' && !isDash(ch) && !Character.isDigit(ch) &&
                ch != '$' && ch != '_' && !Character.isDigit(ch));
    }

    public void close() throws IOException {
        reader.close();
    }
}
