package com.jscape.util.io;

import java.io.IOException;
import java.io.InputStream;

import static com.jscape.util.Parameters.assertGreater;
import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class EolFilterInputStream
        extends InputStream {

    private static final byte[] WINDOWS_EOL = {'\r', '\n'};
    private static final byte[] UNIX_EOL = {'\n'};
    private static final byte[] MAC_EOL = {'\r'};

    private final InputStream base;
    private final byte[] baseEol;
    private final byte[] targetEol;
    private final ByteArrayInputStream eolStream;
    private final ByteArrayOutputStream buffer;

    public static EolFilterInputStream commonEolFilterStream(InputStream base, byte[] targetEol) {
        return new EolFilterInputStream(new EolFilterInputStream(new EolFilterInputStream(base, WINDOWS_EOL, UNIX_EOL), MAC_EOL, UNIX_EOL), UNIX_EOL, targetEol);
    }

    public EolFilterInputStream(InputStream base, byte[] baseEol, byte[] targetEol) {
        assertNotNull(base);
        this.base = base;

        assertGreater(baseEol.length, 0, "Bad base EOL length.");
        this.baseEol = baseEol;

        assertNotNull(targetEol);
        this.targetEol = targetEol;

        this.eolStream = new ByteArrayInputStream(new byte[0]);
        this.buffer = new ByteArrayOutputStream(this.baseEol.length);
    }

    @Override
    public int read()
            throws IOException {
        int read = this.eolStream.read();

        if (read != -1) {
            return read;
        }

        read = this.base.read();

        if (read == -1 || read != this.baseEol[0]) {
            return read;
        }

        readPossibleEol(read);

        return this.eolStream.read();
    }

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

    private void readPossibleEol(int read)
            throws IOException {
        this.buffer.reset();
        this.buffer.write(read);

        for (int i = 1; i < this.baseEol.length && (read = this.base.read()) != -1; ++i) {
            this.buffer.write(read);
            if (read != this.baseEol[i]) {
                this.eolStream.put(this.buffer.buffer(), 0, this.buffer.position());
                return;
            }
        }

        if (read != -1) {
            this.eolStream.put(this.targetEol, 0, this.targetEol.length);
        } else {
            this.eolStream.put(this.buffer.buffer(), 0, this.buffer.position());
        }
    }
}
