/*
 * Copyright (c) 2007-2009 by The Broad Institute, Inc. and the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * This software is licensed under the terms of the GNU Lesser General Public License (LGPL), Version 2.1 which
 * is available at http://www.opensource.org/licenses/lgpl-2.1.php.
 *
 * THE SOFTWARE IS PROVIDED "AS IS." THE BROAD AND MIT MAKE NO REPRESENTATIONS OR WARRANTIES OF
 * ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT
 * OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE.  IN NO EVENT SHALL THE BROAD OR MIT, OR THEIR
 * RESPECTIVE TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR ANY DAMAGES OF
 * ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ECONOMIC
 * DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER THE BROAD OR MIT SHALL
 * BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE
 * FOREGOING.
 */
package htsjdk.tribble.readers;

import htsjdk.samtools.util.LocationAware;
import htsjdk.tribble.TribbleException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;

/**
 * A simple class that provides {@link #readLine()} functionality around a PositionalBufferedStream
 *
 * {@link BufferedReader} and its {@link java.io.BufferedReader#readLine()} method should be used in preference to this class (when the
 * {@link htsjdk.samtools.util.LocationAware} functionality is not required) because it offers greater performance.
 * 
 * @author jrobinso
 */
public class AsciiLineReader implements LineReader, LocationAware {
    private static final int BUFFER_OVERFLOW_INCREASE_FACTOR = 2;
    private static final byte LINEFEED = (byte) ('\n' & 0xff);
    private static final byte CARRIAGE_RETURN = (byte) ('\r' & 0xff);

    PositionalBufferedStream is;
    char[] lineBuffer;

    public AsciiLineReader(final InputStream is){
        this(new PositionalBufferedStream(is));
    }

    public AsciiLineReader(final PositionalBufferedStream is) {
        this.is = is;
        // Allocate this only once, even though it is essentially a local variable of
        // readLine.  This makes a huge difference in performance
        lineBuffer = new char[10000];
    }

    /**
     * @return The position of the InputStream
     */
    public long getPosition(){
        if(is == null){
            throw new TribbleException("getPosition() called but no default stream was provided to the class on creation");
        }
        return is.getPosition();
    }

    /**
     * Read a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * followed immediately by a linefeed.
     *
     * @param stream the stream to read the next line from
     * @return A String containing the contents of the line or null if the
     *         end of the stream has been reached
     */
    public final String readLine(final PositionalBufferedStream stream) throws IOException{
        int linePosition = 0;

        while (true) {
            final int b = stream.read();

            if (b == -1) {
                // eof reached.  Return the last line, or null if this is a new line
                if (linePosition > 0) {
                    return new String(lineBuffer, 0, linePosition);
                } else {
                    return null;
                }
            }

            final char c = (char) (b & 0xFF);
            if (c == LINEFEED || c == CARRIAGE_RETURN) {
                if (c == CARRIAGE_RETURN && stream.peek() == LINEFEED) {
                    stream.read(); // <= skip the trailing \n in case of \r\n termination
                }

                return new String(lineBuffer, 0, linePosition);
            } else {
                // Expand line buffer size if neccessary.  Reserve at least 2 characters
                // for potential line-terminators in return string

                if (linePosition > (lineBuffer.length - 3)) {
                    final char[] temp = new char[BUFFER_OVERFLOW_INCREASE_FACTOR * lineBuffer.length];
                    System.arraycopy(lineBuffer, 0, temp, 0, lineBuffer.length);
                    lineBuffer = temp;
                }

                lineBuffer[linePosition++] = c;
            }
        }
    }

    /**
     * Same as {@link #readLine(PositionalBufferedStream)} but uses the stream provided in the constructor
     *
     * @return
     */
    public final String readLine() throws IOException{
        if ( is == null ){
            throw new TribbleException("readLine() called without an explicit stream argument but no default stream was provided to the class on creation");
        }
        return readLine(is);
    }

    @Override
    public void close() {
        if ( is != null ) is.close();
        lineBuffer = null;
    }

    public static void main(final String[] args) throws Exception {
        final File testFile = new File(args[0]);
        final int iterations = Integer.valueOf(args[1]);
        final boolean includeBufferedReader = Boolean.valueOf(args[2]);
        long t0, lineCount, dt;
        double rate;

        System.out.printf("Testing %s%n", args[0]);
        for (int i = 0; i < iterations; i++) {
            if ( includeBufferedReader ) {
                final BufferedReader reader2 = new BufferedReader(new FileReader(testFile));
                t0 = System.currentTimeMillis();
                lineCount = 0;
                while (reader2.readLine() != null) {
                    lineCount++;
                }
                dt = System.currentTimeMillis() - t0;
                rate = ((double) lineCount) / dt;
                printStatus("BufferedReader", lineCount, rate, dt);
                reader2.close();
            }

            if ( includeBufferedReader ) {
                final LongLineBufferedReader longLineBufferedReader = new LongLineBufferedReader(new BufferedReader(new FileReader(testFile)));
                t0 = System.currentTimeMillis();
                lineCount = 0;
                while (longLineBufferedReader.readLine() != null) {
                    lineCount++;
                }
                dt = System.currentTimeMillis() - t0;
                rate = ((double) lineCount) / dt;
                printStatus("BufferedReader", lineCount, rate, dt);
                longLineBufferedReader.close();
            }
            
            final PositionalBufferedStream pbs = new PositionalBufferedStream(new FileInputStream(testFile));
            final LineReader reader = new AsciiLineReader(pbs);
            t0 = System.currentTimeMillis();
            lineCount = 0;
            while (reader.readLine() != null) {
                lineCount++;
            }
            dt = System.currentTimeMillis() - t0;
            rate = ((double) lineCount) / dt;
            printStatus("PositionalBufferedStream", lineCount, rate, dt);
            pbs.close();
        }
    }

    private static final void printStatus(final String name, final long lineCount, final double rate, final long dt) {
        System.out.printf("%30s: %d lines read.  Rate = %.2e lines per second.  DT = %d%n", name, lineCount, rate, dt);
        System.out.flush();
    }
}

