package ru.yandex.webmaster3.storage.util.yt;

import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.primitives.Ints;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;

import ru.yandex.webmaster3.storage.util.LenvalFormatException;

/**
 * @author aherman
 */
class LenvalStreamParser2<T> implements InterruptableIterator<T> {
    private static final String FIELD_KEY = "key";
    private static final String FIELD_VALUE = "value";

    private final YtRowMapper<T> ytRowMapper;

    private boolean streamEnded = false;

    private InputStream is = null;
    private BoundedInputStream keyStream;
    private boolean keyReady = false;

    private NullOutputStream devNullConsumer = new NullOutputStream();
    private byte[] copyBuffer = new byte[4 * 1024];

    LenvalStreamParser2(InputStream is, YtRowMapper<T> ytRowMapper) {
        this.is = is;
        this.streamEnded = false;
        this.keyReady = false;
        this.ytRowMapper = ytRowMapper;
    }

    @Override
    public long getRow() {
        return 0;
    }

    @Override
    public void close() throws IOException {
        this.is = null;
    }

    @Override
    public boolean hasNext() throws InterruptedException, IOException, YtException {
        if (streamEnded) {
            return false;
        }
        if (Thread.interrupted()) {
            throw new InterruptedException("Interrupted while reading table");
        }
        if (keyReady) {
            return true;
        }

        keyStream = getNextStream();
        if (keyStream == null) {
            keyReady = false;
            streamEnded = true;
            return false;
        }
        keyReady = true;
        return true;
    }

    @Override
    public T next() throws InterruptedException, IOException, YtException {
        if (streamEnded) {
            throw new NoSuchElementException("Stream already ended");
        }
        if (!keyReady) {
            throw new IllegalStateException("Key is not ready");
        }
        keyReady = false;
        ytRowMapper.nextField(FIELD_KEY, keyStream);
        consumeStream(keyStream);
        keyStream = null;

        BoundedInputStream valueStream = getNextStream();
        if (valueStream == null) {
            throw new LenvalFormatException("Value is empty");
        }
        ytRowMapper.nextField(FIELD_VALUE, valueStream);
        consumeStream(valueStream);
        return ytRowMapper.rowEnd();
    }

    private BoundedInputStream getNextStream() throws LenvalFormatException, IOException {
        int i1 = is.read();
        if (i1 < 0) {
            return null;
        }
        int i2 = is.read();
        if (i2 < 0) {
            throw new LenvalFormatException("Unable to read next entry length, stream ended");
        }
        int i3 = is.read();
        if (i3 < 0) {
            throw new LenvalFormatException("Unable to read next entry length, stream ended");
        }
        int i4 = is.read();
        if (i4 < 0) {
            throw new LenvalFormatException("Unable to read next entry length, stream ended");
        }

        int length = Ints.fromBytes((byte) i4, (byte) i3, (byte) i2, (byte) i1);
        return new BoundedInputStream(this.is, length);
    }

    private void consumeStream(BoundedInputStream stream) throws IOException {
        if (stream.available() > 0) {
            IOUtils.copyLarge(stream, devNullConsumer, copyBuffer);
        }
    }

    static class BoundedInputStream extends InputStream {
        private final int size;
        private int position;
        private final InputStream is;

        public BoundedInputStream(InputStream is, int size) {
            this.size = size;
            this.is = is;
        }

        @Override
        public int available() throws IOException {
            return size - position;
        }

        @Override
        public int read() throws IOException {
            if (position >= size) {
                return -1;
            }
            position++;
            return is.read();
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (position >= size) {
                return -1;
            }
            len = Math.min(len, size - position);
            int read = is.read(b, off, len);
            position += read;
            return read;
        }
    }

    private static class Format {
        private final String value = "yamr";
        private final FormatAttributes attributes = new FormatAttributes();

        @JsonProperty("$value")
        public String getValue() {
            return value;
        }

        @JsonProperty("$attributes")
        public FormatAttributes getAttributes() {
            return attributes;
        }
    }

    private static class FormatAttributes {
        private final boolean lenval = true;
        private final String key = "key";
        private final String value = "value";
        private final boolean has_subkey = false;

        public boolean isLenval() {
            return lenval;
        }

        public String getKey() {
            return key;
        }

        public String getValue() {
            return value;
        }

        public boolean isHas_subkey() {
            return has_subkey;
        }
    }
}
