package io;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class provides iterator over decorated file input stream. It facilitates
 * splitting file into several fragments. This class is iterable over input
 * streams that read can fixed length fragments of file. Decorated input streams
 * prevents user from skipping out of fragment of file and paddes last fragment
 * to ensure equal lenght of all fragments.
 *
 * Inner iterator provides input streams that enable user to read fixed lenght
 * portions or fragments of file. Only parts of file corresponding to input
 * stream can be read. Fragments are non overlaping and continuous. Every byte
 * of file will be accessible via some of iterated input streams.
 * 
 * @author Marcel Duris
 * @see io.PaddingInputStream
 */
public class FileFragmentInputStream implements Iterable<InputStream> {

    /**
     * Inner class that provides iterator over decorated input streams.
     */
    private class FragmentStreamIterator implements Iterator<InputStream> {

        private File file;
        private int pos;
        private long fragmentSize;

        /**
         * Constructor of iterator, sets file and fragment size for later use.
         *
         * @param file File to be split into fragments
         * @param fragmentSize Size of each fragment
         */
        public FragmentStreamIterator(File file, long fragmentSize) {
            this.file = file;
            this.fragmentSize = fragmentSize;
            pos = 0;
        }

        /**
         * Returns true if and only if there is any byte of file, that can not
         * be read by streams iterated over so far.
         * 
         * @return if next stream can be returned
         */
        public boolean hasNext() {
            return pos * fragmentSize < file.length();
        }

        /**
         * Returns input stream reading next fragment of file. Fixed number of
         * bytes is guaranteed to be able to be read, although fewer bytes can
         * be read. Number of bytes to be read is specified by constructor of
         * iterator.
         *
         * @return padding input stream capable of reading exact number of bytes
         */
        public InputStream next() {
            FileInputStream fin = null;
            try {
                fin = new FileInputStream(file);
                fin.skip(pos++ * fragmentSize);
            } catch (IOException ex) {
                Logger.getLogger(FileFragmentInputStream.class.getName()).log(Level.SEVERE, null, ex);
            }

            InputStream in = new BufferedInputStream(fin);
            in = new PaddingInputStream(in, fragmentSize);

            return in;
        }

        public void remove() {
            return;
        }
    }
    /* File to be read */
    private File file;
    /* Size of each fragment */
    private long fragmentSize;

    /**
     * Constructor sets input parameters for later use by inner iterator.
     *
     * @param file File to be divided into fragments
     * @param fragmentSize Size of every fragment
     */
    public FileFragmentInputStream(File file, long fragmentSize) {
        this.file = file;
        this.fragmentSize = fragmentSize;
    }

    /**
     * Returns iterator over decorated input streams. This input streams are
     * padded, if they reach end of file, so it is ensured that equal number
     * of bytes can be read from each stream. On the other hand, input streams
     * will only read portion of file coresponding to their fragment efectively
     * preventing mixing of fragments.
     * 
     * @return Iterator over input streams.
     */
    public Iterator<InputStream> iterator() {
        return new FragmentStreamIterator(file, fragmentSize);
    }
}
