package org.broad.tribble.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Class to wrap
 */
public class SeekableSplitStream extends SeekableStream {

    long position = 0;
    long length = 0;
    List<PartDescriptor> descriptors;
    int currentStreamIndex = 0;

    public SeekableSplitStream(String path) throws IOException {
        parseDescriptors(path);
    }

    public void seek(long position) throws IOException {
        this.position = position;
        long end = 0;
        long start = 0;
        for (PartDescriptor desc : descriptors) {
            end += desc.getContentLength();
            if (position >= start && position < end) {
                long delta = position - start;
                desc.getStream().seek(delta);
            } else {
                desc.getStream().seek(0);
            }
            start = end;
        }
    }

    public long position() throws IOException {
        return position;
    }

    @Override
    public int read(byte[] buffer, int off, int len) throws IOException {

        int bytesRead = 0;
        long end = 0;
        long start = 0;
        for (PartDescriptor desc : descriptors) {
            end += desc.getContentLength();
            if (position >= start && position < end) {
                int delta = (int) (desc.contentLength - desc.stream.position());
                int l = Math.min(len - bytesRead, delta);
                int nBytes = desc.stream.read(buffer, off + bytesRead, l);
                if (nBytes < 0) {
                    return nBytes;
                }
                bytesRead += nBytes;
                position += nBytes;
                if (bytesRead >= len) {
                    break;
                }
            }
            start = end;
        }


        // If we get this far, and haven't read any bytes, we're at EOF
        return bytesRead > 0 ? bytesRead : -1;
    }

    @Override
    public int read() throws IOException {
        int b = descriptors.get(currentStreamIndex).getStream().read();
        position++;
        return b;
    }

    @Override
    public void close() throws IOException {
        for (PartDescriptor desc : descriptors) {
            desc.getStream().close();
        }
    }

    private void parseDescriptors(String path) throws IOException {

        BufferedReader br = null;
        descriptors = new ArrayList();
        try {
            br = ParsingUtils.openBufferedReader(path);
            String nextLine;
            while ((nextLine = br.readLine()) != null) {
                String[] tokens = nextLine.split(" ");
                if (tokens.length == 2) {
                    String p = tokens[0];

                    // Require the files are in the same directory as the list file
                    String listFileName = null;
                    if(path.startsWith("http:") || path.startsWith("https:")) {
                       URL url = new URL(path);
                       listFileName = (new File(url.getPath())).getName();
                    }
                    else {
                        listFileName = (new File(path)).getName();
                    }
                    SeekableStream stream = SeekableStreamFactory.getStreamFor(path.replace(listFileName, p));

                    long length = Long.parseLong(tokens[1]);
                    descriptors.add(new SeekableSplitStream.PartDescriptor(length, stream));
                } else {
                    // TODO -- throw exception, or warning?
                }
            }
        }
        finally {
            if (br != null) {
                br.close();
            }
        }
    }



    public static class PartDescriptor {
        private long contentLength;
        private SeekableStream stream;

        public PartDescriptor(long contentLength, SeekableStream stream) {
            this.contentLength = contentLength;
            this.stream = stream;
        }

        public long getContentLength() {
            return contentLength;
        }

        public SeekableStream getStream() {
            return stream;
        }
    }
}