class LineEndingProcessingProvider implements Provider {

    private static final int EOF = -1;

    private static final int DEFAULT_BUFFER_SIZE = 2048;

    /**
     * The "other" provider which we are wrapping around / reading from.
     */
    private final Provider _input;

    /**
     * The buffer that we're storing data within.
     */
    private final char[] _data;

    /**
     * The number of characters in {@link #_data}.
     */
    private int _len = 0;

    /**
     * The position in {@link #_data} where to read the next source character from.
     */
    private int _pos = 0;

    private final Map<LineSeparator, Integer> eolCounts = new HashMap<>();

    public LineEndingProcessingProvider(Provider input) {
        this(DEFAULT_BUFFER_SIZE, input);
    }

    public LineEndingProcessingProvider(int bufferSize, Provider input) {
        _input = input;
        _data = new char[bufferSize];
    }

    @Override
    public void close() throws IOException {
        _input.close();
    }

    private int fillBuffer() throws IOException {
        _pos = 0;
        int direct = _input.read(_data, 0, _data.length);
        if (direct != 0) {
            _len = direct;
        }
        return direct;
    }

    public LineSeparator getDetectedLineEnding() {
        return LineSeparator.getLineEnding(eolCounts.getOrDefault(LineSeparator.CR, 0), eolCounts.getOrDefault(LineSeparator.LF, 0), eolCounts.getOrDefault(LineSeparator.CRLF, 0));
    }

    private boolean isBufferEmpty() {
        return _pos >= _len;
    }

    /**
     * Retrieves the next un-escaped character from the buffered {@link #_input}.
     *
     * @return The next character or {@code -1} if no more input is available.
     */
    private int nextBufferedChar() throws IOException {
        while (isBufferEmpty()) {
            int direct = fillBuffer();
            if (direct < 0) {
                return EOF;
            }
        }
        return _data[_pos++];
    }

    @Override
    public int read(char[] buffer, final int offset, int len) throws IOException {<FILL_FUNCTION_BODY>}
}

        int pos = offset;
        int stop = offset + len;
        LineSeparator previousLineSeparator = null;
        while (pos < stop) {
            int ch = nextBufferedChar();
            if (ch < 0) {
                if (pos == offset) {
                    // Nothing read yet, this is the end of the stream.
                    return EOF;
                }
                break;
            }
            String str = String.valueOf((char) ch);
            Optional<LineSeparator> lookup = LineSeparator.lookup(str);
            if (lookup.isPresent()) {
                    LineSeparator lineSeparator = lookup.get();
                    // Track the number of times this character is found..
                    eolCounts.putIfAbsent(lineSeparator, 0);
                    eolCounts.put(lineSeparator, eolCounts.get(lineSeparator) + 1);
                    // Handle line separators of length two (specifically CRLF)
                    // TODO: Make this more generic than just CRLF (e.g. track the previous char rather than the previous line separator
                    if (lineSeparator == LineSeparator.LF) {
                        if (previousLineSeparator == LineSeparator.CR) {
                            eolCounts.putIfAbsent(LineSeparator.CRLF, 0);
                            eolCounts.put(LineSeparator.CRLF, eolCounts.get(LineSeparator.CRLF) + 1);
                        }
                    }
                    // If "this" (current) char <strong>is</strong> a line separator, set the next loop's "previous" to this
                    previousLineSeparator = lineSeparator;
                } else {
                    // If "this" (current) char <strong>is not</strong> a line separator, set the next loop's "previous" to null
                    previousLineSeparator = null;
                }
            buffer[pos++] = (char) ch;
        }
        return pos - offset;
    
<no_super_class>