package gov.nasa.gsfc.spdf.cdfj;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.net.*;
/**
 * ReaderFactory creates an instance of CDFReader from a CDF source.
 * Uses array backed ByteBuffer for CDFReader.
 * The source CDF can  be a file,  or a URL.
 */
public final class ReaderFactory {
    /**
     * creates  CDFReader object from a file using array backed ByteBuffer.
     */
    static int preamble = 3000;
    
    /**
     * Return a reader for the CDF file.  This looks at the CDF file internals
     * to calculate the version and returns an implementation.
     * @param fname
     * @return a reader for the CDF file
     * @throws CDFException.ReaderError if there is an issue while reading the file
     * @throws IllegalArgumentException if the file is not a CDF file
     */
    public static CDFReader getReader(String fname) throws
        CDFException.ReaderError {
        return getReader( fname, false );
    }

    /**
     * creates  CDFReader object from a URL using array backed ByteBuffer.
     */
    public static CDFReader getReader(URL url) throws
        CDFException.ReaderError {
        CDFImpl cdf = null;
        try {
            URLConnection con = new CDFUrl(url).openConnection();
            int remaining = con.getContentLength();
            InputStream is = con.getInputStream();
            byte [] ba = new byte[remaining];
            int offset = 0;
            while (remaining > 0) {
                int got = is.read(ba, offset, remaining);
                offset += got;
                remaining -= got;
            }
            ((HttpURLConnection)con).disconnect();
            ByteBuffer buf = ByteBuffer.wrap(ba);
            cdf = CDFFactory.getVersion(buf);
            if ( cdf==null ) {
                throw new IllegalArgumentException("File is not a CDF-format file.");
            }
        } catch (Throwable th) {
            throw new CDFException.ReaderError("I/O Error reading " + url,th);
        }
        CDFReader rdr = new CDFReader();
        rdr.setImpl(cdf);
        final String _url = url.toString();
        cdf.setSource(new CDFFactory.CDFSource() {
            public String getName() {return _url;};
            public boolean isFile() {return false;};
        });
        return rdr;
    }

    /**
     * returns an implementation for reading the CDF, or null if the file
     * does not have magic numbers indicating it is a CDF.
     * @param buf the buffer containing the CDF data.
     * @param ch the file channel for the data.
     * @return the implementation for reading the CDF, or null.
     * @throws Throwable 
     */
    static CDFImpl getVersion(ByteBuffer buf, FileChannel ch) throws
        Throwable {
        LongBuffer lbuf = buf.asLongBuffer();
        long magic = lbuf.get();
        if (magic == CDFFactory.CDF3_MAGIC) {
            return new CDF3Impl(buf, ch);
        }
        if (magic == CDFFactory.CDF3_COMPRESSED_MAGIC) {
            ByteBuffer mbuf = CDFFactory.uncompressed(buf, 3);
            return new CDF3Impl(mbuf);
        }
        if (magic == CDFFactory.CDF2_MAGIC_DOT5) {
            int release = buf.getInt(24);
            return new CDF2Impl(buf, release, ch);
        } else {
            ShortBuffer sbuf = buf.asShortBuffer();
            if (sbuf.get() == (short)0xcdf2) {
                if (sbuf.get() == (short)0x6002) {
                    short x = sbuf.get();
                    if (x == 0) {
                        if (sbuf.get() == -1) {
                            return new CDF2Impl(buf, 6, ch);
                        }
                    } else {
                        if ((x == (short)0xcccc) && (sbuf.get() == 1)) {
                            // is compressed - positioned at CCR
                            ByteBuffer mbuf = CDFFactory.uncompressed(buf, 2);
                            return new CDF2Impl(mbuf, 6, ch);
                        }
                    }
                        
                }
            }
        }
        return null;
    }
    
    /**
     * get a reader, forcing that all is read into memory in one heap byte array.
     * This has the limitation that the file cannot be greater than 2GB in length.
     * @param fname the file name.
     * @param map read the file into a memory block on the heap
     * @return a CDFReader
     * @throws gov.nasa.gsfc.spdf.cdfj.CDFException.ReaderError 
     */
    public static CDFReader getReader(String fname, boolean map) throws
        CDFException.ReaderError {
        CDFImpl cdf = null;
        File file = new File(fname);
        try {
            if ( map ) {
                int len = (int)file.length();
                byte[] ba = new byte[len];
                int rem = len;
                try (FileInputStream fis = new FileInputStream(file)) {
                    int n = 0;
                    while (rem > 0) {
                        len = fis.read(ba, n, rem);
                        n += len;
                        rem -= len;
                    }
                }
                cdf = CDFFactory.getCDF(ba);
            } else {
                RandomAccessFile raf = new RandomAccessFile(file, "r");
                long len = raf.length(); 
                if (len > preamble) len = preamble;
                byte[] ba = new byte[(int)len];
                raf.readFully(ba);
                ByteBuffer buf = ByteBuffer.wrap(ba);                
                cdf = getVersion(buf, raf.getChannel());
            }
            if ( cdf==null ) {
                throw new IllegalArgumentException("File is not a CDF-format file: "+fname);
            }
        } catch (Throwable th) {
            throw new CDFException.ReaderError("I/O Error reading " + fname);
        }
        final String _fname = file.getPath();
        cdf.setSource(new CDFFactory.CDFSource() {
            public String getName() {return _fname;};
            public boolean isFile() {return true;};
        });
        CDFReader rdr = new CDFReader();
        rdr.setImpl(cdf);
        return rdr;
    }
}