package org.broad.tribble.index.linear;

import org.apache.log4j.Logger;
import org.broad.tribble.Feature;
import org.broad.tribble.FeatureCodec;
import org.broad.tribble.exception.UnsortedFileException;
import org.broad.tribble.index.Index;
import org.broad.tribble.index.IndexCreator;
import org.broad.tribble.util.AsciiLineReader;
import org.broad.tribble.util.LittleEndianOutputStream;
import org.broad.tribble.util.ParsingUtils;

import java.io.*;

/**
 * @author jrobinso
 */
public class LinearIndexCreator extends IndexCreator {

    private static Logger log = Logger.getLogger(LinearIndexCreator.class);

    static int DEFAULT_BINWIDTH = 16000;

    private int binWidth;

    private boolean verbose = true;

    public LinearIndexCreator(File featureFile, FeatureCodec codec) {
        super(featureFile, codec);
        this.binWidth = DEFAULT_BINWIDTH;
    }


    public Index createIndex() throws IOException {

        LinearIndex featureIndex = new LinearIndex(binWidth, featureFile.getAbsolutePath());

        FileInputStream is = new FileInputStream(featureFile);
        AsciiLineReader reader = new AsciiLineReader(is);

        // make sure to read the header off first
        codec.readHeader(reader);

        long fileLength = featureFile.length();
        long progressIncrement = fileLength / 100;
        long currentFilePosition = 0;
        String lastChr = null;
        int lastStart = 0;
        int lastBinNumber = 0;
        int lastBinStart = -1;
        int longestFeature = 0;
        int progressCounter = 1; // progress in %
        int lineNumber = 0;

        String nextLine = "";

        // Skip to first feature (past header
        long filePosition;
        Feature f = null;
        do {
            lineNumber++;
            filePosition = reader.getPosition();
            nextLine = reader.readLine();
        } while (nextLine != null && (f = codec.decodeLoc(nextLine)) == null);

        // First non-null feature.  "f" should never be null here.
        if (f != null) {
            lastChr = f.getChr();
            lastBinStart = f.getStart();
        }
        currentFilePosition = reader.getPosition();

        while ((nextLine = reader.readLine()) != null) {
            lineNumber++;

            f = codec.decodeLoc(nextLine);
            if (f == null) {
                continue;
            }

            String chr = f.getChr();

            if (!chr.equals(lastChr)) {
                // New chromosome.  Start a new bin

                int bytesCount = (int) (currentFilePosition - filePosition);
                featureIndex.add(lastChr, filePosition, bytesCount, longestFeature);
                lastBinStart = f.getStart();
                filePosition = currentFilePosition;
                lastBinNumber = 0;
                lastStart = 0;
                longestFeature = 0;

                lastChr = chr;

            } else {
                // Get alignment start and verify file is sorted.
                int start = f.getStart();

                longestFeature = Math.max(longestFeature, f.getEnd() - start);

                if (start < lastStart) {
                    throw new UnsortedFileException(" File must be sorted by start position. " +
                            "Sort test failed at: " + nextLine);
                }

                lastStart = start;

                int binNumber = start / binWidth;
                if (binNumber > lastBinNumber) {

                    int cnt = 0;
                    // if the first record isn't in the first tile, we have to fill in blank tiles
                    for (cnt = 0; cnt < ((lastBinStart / binWidth) - lastBinNumber); cnt++) {
                        featureIndex.add(lastChr, filePosition, 0, longestFeature);

                    }
                    lastBinNumber += cnt;
                    // We have crossed a tile boundary.  Record index and counts for previous tile
                    int bytesCount = (int) (currentFilePosition - filePosition);
                    featureIndex.add(lastChr, filePosition, bytesCount, longestFeature);
                    lastBinStart = f.getStart();
                    filePosition = currentFilePosition;

                    // If tiles were skipped record zero counts for these.
                    for (cnt = 0; cnt < (binNumber - lastBinNumber - 1); cnt++) {
                        featureIndex.add(lastChr, filePosition, 0, longestFeature);
                    }


                    lastBinNumber = binNumber;
                }
            }


            currentFilePosition = reader.getPosition();

            if (currentFilePosition > (progressCounter * progressIncrement)) {
                updateProgress(progressCounter);
                progressCounter++;

            }
        }

        // Record remainder bin
        int byteCount = (int) (reader.getPosition() - filePosition);
        featureIndex.add(lastChr, filePosition, byteCount, longestFeature);


        is.close();

        if (idxFile != null) {
            LittleEndianOutputStream stream = null;
            try {
                stream = new LittleEndianOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile)));
                featureIndex.write(stream);
            }
            finally {
                if (stream != null) {
                    stream.close();
                }
            }
        }

        return featureIndex;
    }

    private void updateProgress(int progressCounter) {
        if(verbose) System.out.println("Progress: " + progressCounter + "%");
    }

    /*
    public String checksum(File file) {
  try {
    InputStream fin = new FileInputStream(file);
    java.security.MessageDigest md5er =
        MessageDigest.getInstance("MD5");
    byte[] buffer = new byte[1024];
    int read;
    do {
      read = fin.read(buffer);
      if (read > 0)
        md5er.update(buffer, 0, read);
    } while (read != -1);
    fin.close();
    byte[] digest = md5er.digest();
    if (digest == null)
      return null;
    String strDigest = "0x";
    for (int i = 0; i < digest.length; i++) {
      strDigest += Integer.toString((digest[i] & 0xff)
                + 0x100, 16).substring(1).toUpperCase();
    }
    return strDigest;
  } catch (Exception e) {
    return null;
  }
}
     */

    public int getBinWidth() {
        return binWidth;
    }

    public void setBinWidth(int binWidth) {
        this.binWidth = binWidth;
    }

    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }
}
