// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

/**
 * An InputStreamReader that only consumes as many bytes as is necessary.
 * It does not do any read-ahead. From https://stackoverflow.com/q/2631507/2257172
 */
public class InputStreamReaderUnbuffered extends Reader {

    private final CharsetDecoder charsetDecoder;
    private final InputStream inputStream;
    private final ByteBuffer byteBuffer = ByteBuffer.allocate(1);

    public InputStreamReaderUnbuffered(InputStream in, Charset charset) {
        inputStream = in;
        charsetDecoder = charset.newDecoder();
    }

    @Override
    public int read() throws IOException {
        boolean middleOfReading = false;

        while (true) {
            int b = inputStream.read();

            if (b == -1) {
                if (middleOfReading)
                    throw new IOException(
                            "Unexpected end of stream, byte truncated");

                return -1;
            }

            byteBuffer.clear();
            byteBuffer.put((byte) b);
            byteBuffer.flip();

            CharBuffer charBuffer = charsetDecoder.decode(byteBuffer);

            // although this is theoretically possible this would violate the
            // unbuffered nature of this class so we throw an exception
            if (charBuffer.length() > 1)
                throw new IOException(
                        "Decoded multiple characters from one byte!");

            if (charBuffer.length() == 1)
                return charBuffer.get();

            middleOfReading = true;
        }
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        for (int i = 0; i < len; i++) {
            int ch = read();

            if (ch == -1)
                return i == 0 ? -1 : i;

            cbuf[i] = (char) ch;
        }

        return len;
    }

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