package net.sf.picard.util;

import net.sf.samtools.SAMRecord;
import net.sf.samtools.util.ProgressLoggerInterface;

import java.text.DecimalFormat;
import java.text.NumberFormat;

/**
 * Little progress logging class to facilitate consistent output of useful information when progressing
 * through a stream of SAM records.
 *
 * @author Tim Fennell
 */
public class ProgressLogger implements ProgressLoggerInterface {
    private final Log log;
    private final int n;
    private final String verb;
    private final String noun;
    private final long startTime = System.currentTimeMillis();
    
    private final NumberFormat fmt = new DecimalFormat("#,###");
    private final NumberFormat timeFmt = new DecimalFormat("00");
    
    private long processed = 0;

	// Set to -1 until the first record is added
    private long lastStartTime = -1;

    /**
     * Construct a progress logger.
     * @param log the Log object to write outputs to
     * @param n the frequency with which to output (i.e. every N records)
     * @param verb the verb to log, e.g. "Processed, Read, Written".
     * @param noun the noun to use when logging, e.g. "Records, Variants, Loci"
     */
    public ProgressLogger(final Log log, final int n, final String verb, final String noun) {
        this.log = log;
        this.n = n;
        this.verb = verb;
        this.noun = noun;
    }

    /**
     * Construct a progress logger.
     * @param log the Log object to write outputs to
     * @param n the frequency with which to output (i.e. every N records)
     * @param verb the verb to log, e.g. "Processed, Read, Written".
     */
    public ProgressLogger(final Log log, final int n, final String verb) {
        this(log, n, verb, "records");
    }

    /**
     * Construct a progress logger with the desired log and frequency and the verb "Processed".
     * @param log the Log object to write outputs to
     * @param n the frequency with which to output (i.e. every N records)
     */
    public ProgressLogger(final Log log, final int n) { this(log, n, "Processed"); }

    /**
     * Construct a progress logger with the desired log, the verb "Processed" and a period of 1m records.
     * @param log the Log object to write outputs to
     */
    public ProgressLogger(final Log log) { this(log, 1000000); }

    public synchronized boolean record(final String chrom, final int pos) {
	    if (this.lastStartTime == -1) this.lastStartTime = System.currentTimeMillis();
	    if (++this.processed % this.n == 0) {
            final long now = System.currentTimeMillis();
            final long lastPeriodSeconds = (now - this.lastStartTime) / 1000;
            this.lastStartTime = now;

            final long seconds = (System.currentTimeMillis() - startTime) / 1000;
            final String elapsed   = formatElapseTime(seconds);
            final String period    = pad(fmt.format(lastPeriodSeconds), 4);
            final String processed = pad(fmt.format(this.processed), 13);

            final String readInfo;
            if (chrom == null) readInfo = "*/*";
            else readInfo = chrom + ":" + fmt.format(pos);

            log.info(this.verb, " ", processed, " " + noun + ".  Elapsed time: ", elapsed, "s.  Time for last ", fmt.format(this.n),
                     ": ", period, "s.  Last read position: ", readInfo);
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Records that a given record has been processed and triggers logging if necessary.
     * @return boolean true if logging was triggered, false otherwise
     */
    public synchronized boolean record(final SAMRecord rec) {
        if (rec.getReferenceIndex() == SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) {
            return record(null, 0);
        }
        else {
            return record(rec.getReferenceName(), rec.getAlignmentStart());
        }
    }
    
    /** Records multiple SAMRecords and triggers logging if necessary. */
    public boolean record(final SAMRecord... recs) {
        boolean triggered = false;
        for (final SAMRecord rec : recs) triggered = record(rec) || triggered;
        return triggered;
    }
    
    /** Returns the count of records processed. */
    public long getCount() { return this.processed; }

    /** Returns the number of seconds since progress tracking began. */
    public long getElapsedSeconds() { return (System.currentTimeMillis() - this.startTime) / 1000; }
    
    /** Left pads a string until it is at least the given length. */
    private String pad (String in, final int length) {
        while (in.length() < length) {
            in = " " + in;
        }
        
        return in;
    }
    
    /** Formats a number of seconds into hours:minutes:seconds. */
    private String formatElapseTime(final long seconds) {
        final long s = seconds % 60;
        final long allMinutes = seconds / 60;
        final long m = allMinutes % 60;
        final long h = allMinutes / 60;

        return timeFmt.format(h) + ":" + timeFmt.format(m) + ":" + timeFmt.format(s);
    }
}
