package cn.codepub.redis.directory.io;

import cn.codepub.redis.directory.RedisFile;
import cn.codepub.redis.directory.util.Constants;
import lombok.extern.log4j.Log4j2;
import org.apache.lucene.store.IndexInput;

import java.io.EOFException;
import java.io.IOException;

/**
 * <p>
 * Created by wangxu on 16/10/27 18:11.
 * </p>
 * <p>
 * Description: TODO
 * </p>
 *
 * @author Wang Xu
 * @version V1.0.0
 * @since V1.0.0 <br></br>
 * WebSite: http://codepub.cn <br></br>
 * Licence: Apache v2 License
 */
@Log4j2
public class RedisInputStream extends IndexInput implements Cloneable {
    private RedisFile redisFile;
    private final long length;//the total length of the index file
    private byte[] currentBuffer;
    private int currentBufferIndex;
    private int bufferPosition;
    private int bufferLength;

    public RedisInputStream(String name, RedisFile redisFile) throws IOException {
        this(name, redisFile, redisFile.getFileLength());
    }

    private RedisInputStream(String name, RedisFile redisFile, long length) throws IOException {
        super("RedisInputStream(name=" + name + ")");
        this.redisFile = redisFile;
        this.length = length;
        if (this.length / Constants.BUFFER_SIZE >= Integer.MAX_VALUE) {
            throw new IOException("RedisInputStream too large length=" + length + ": " + name);
        }
        setCurrentBuffer();
    }

    /**
     * Get an unprocessed buffer with data to fill the currentBuffer
     */
    private void setCurrentBuffer() {
        if (currentBufferIndex < redisFile.numBuffers()) {
            currentBuffer = redisFile.getBuffer(currentBufferIndex);
            long bufferStart = (long) Constants.BUFFER_SIZE * (long) currentBufferIndex;
            bufferLength = (int) Math.min(Constants.BUFFER_SIZE, length - bufferStart);
        } else {
            currentBuffer = null;
        }
    }

    @Override
    public byte readByte() throws IOException {
        if (bufferPosition == bufferLength) {
            nextBuffer();
        }
        return currentBuffer[bufferPosition++];
    }

    private void nextBuffer() throws IOException {
        if (getFilePointer() >= length) {
            throw new EOFException("cannot read another byte at EOF: pos=" + getFilePointer() + " vs length=" + length() + ": "
                    + this);
        }
        currentBufferIndex++;
        setCurrentBuffer();
        bufferPosition = 0;
    }

    @Override
    public void readBytes(byte[] b, int offset, int len) throws IOException {
        while (len > 0) {
            if (bufferPosition == bufferLength) {
                nextBuffer();
            }
            int remainInBuffer = bufferLength - bufferPosition;
            int bytesToCopy = len < remainInBuffer ? len : remainInBuffer;
            System.arraycopy(currentBuffer, bufferPosition, b, offset, bytesToCopy);
            offset += bytesToCopy;
            len -= bytesToCopy;
            bufferPosition += bytesToCopy;
        }
    }

    @Override
    public long getFilePointer() {
        return (long) currentBufferIndex * Constants.BUFFER_SIZE + bufferPosition;
    }

    @Override
    public void seek(long pos) throws IOException {
        int newBufferIndex = (int) (pos / Constants.BUFFER_SIZE);
        if (newBufferIndex != currentBufferIndex) {
            currentBufferIndex = newBufferIndex;
            setCurrentBuffer();
        }
        bufferPosition = (int) (pos % Constants.BUFFER_SIZE);
        if (getFilePointer() > length()) {
            throw new EOFException("seek beyond EOF: pos=" + getFilePointer() + " vs length=" + length() + ": " + this);
        }
    }

    @Override
    public void close() {
        // NOOP
        redisFile = null;
    }

    @Override
    public long length() {
        return this.length;
    }

    @Override
    public IndexInput slice(String sliceDescription, final long offset, final long sliceLength) throws IOException {
        if (offset < 0 || sliceLength < 0 || offset + sliceLength > this.length) {
            throw new IllegalArgumentException("slice() " + sliceDescription + " out of bounds: " + this);
        }
        return new RedisInputStream(getFullSliceDescription(sliceDescription), redisFile, offset + sliceLength) {
            {
                seek(0L);
            }

            @Override
            public void seek(long pos) throws IOException {
                if (pos < 0) {
                    throw new IllegalArgumentException("Seeking to negative position: " + this);
                }
                super.seek(pos + offset);
            }

            @Override
            public long getFilePointer() {
                return super.getFilePointer() - offset;
            }

            @Override
            public long length() {
                return sliceLength;
            }

            @Override
            public IndexInput slice(String sliceDescription, long ofs, long len) throws IOException {
                return super.slice(sliceDescription, offset + ofs, len);
            }
        };
    }
}