package com.nifty.cloud.ncs;

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

import com.nifty.cloud.ncs.encrypt.EncryptionConstants;


/**
 * 指定した範囲のデータを読み込むInputStreamです。
 */
public class NiftyCloudAdjustedRangeInputStream extends InputStream
{
    private InputStream decryptedContents;
    private long virtualAvailable;
    private boolean closed;

    /**
     * 読み込み範囲を指定してNiftyCloudAdjustedRangeInputStreamを構築します。
     *
     * @param in
     *  入力ストリーム
     * @param rangeBeginning
     *  読み込み開始位置
     * @param rangeEnd
     *  読み込み終了位置
     * @throws IOException
     */
    public NiftyCloudAdjustedRangeInputStream(InputStream in, long rangeBeginning, long rangeEnd) throws IOException
    {
        this.decryptedContents = in;
        this.closed = false;
        initializeForRead(rangeBeginning, rangeEnd);
    }

    /**
     * 指定した読み込み位置までスキップします。
     *
     * @param rangeBeginning
     * @param rangeEnd
     * @throws IOException
     */
    private void initializeForRead(long rangeBeginning, long rangeEnd) throws IOException
    {
        int numBytesToSkip;
        if(rangeBeginning < EncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE) {
            numBytesToSkip = (int)rangeBeginning;
        } else {
            int offsetIntoBlock = (int)(rangeBeginning % EncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE);
            numBytesToSkip = EncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE + (int)offsetIntoBlock;
        }
        if(numBytesToSkip != 0) {
            while(numBytesToSkip > 0) {
                this.decryptedContents.read();
                numBytesToSkip--;
            }
        }
        this.virtualAvailable = (int)(rangeEnd - rangeBeginning) + 1;
    }

    @Override
    public int read() throws IOException
    {
        int result = 0;
        if (this.virtualAvailable <= 0) {
            result = -1;
        } else {
            result = this.decryptedContents.read();
        }

        if (result != -1) {
            this.virtualAvailable--;
        } else {
            super.close();
            this.virtualAvailable = 0;
        }
        return result;
    }

    @Override
    public int read(byte[] buffer, int offset, int length) throws IOException
    {
        int numBytesRead;
        if(this.virtualAvailable <= 0) {
            numBytesRead = -1;
        } else {
            if(length > this.virtualAvailable) {
                length = (this.virtualAvailable < Integer.MAX_VALUE) ? (int)this.virtualAvailable : Integer.MAX_VALUE;
            }
            numBytesRead = super.read(buffer, offset, length);
        }
        if(numBytesRead != -1) {
            this.virtualAvailable -= numBytesRead;
        } else {
            super.close();
            this.virtualAvailable = 0;
        }
        return numBytesRead;
    }

    @Override
    public int available() throws IOException
    {
        int available = this.decryptedContents.available();
        if(available < this.virtualAvailable) {
            return available;
        } else {
            return (int)this.virtualAvailable;
        }
    }

    @Override
    public void close() throws IOException
    {
        if(!this.closed) {
            this.closed = true;
            this.decryptedContents.close();
        }
    }
}
