/*
 * The Broad Institute
 * SOFTWARE COPYRIGHT NOTICE AGREEMENT
 * This is copyright (2007-2008) by the Broad Institute/Massachusetts Institute
 * of Technology.  It is licensed to You under the Gnu Public License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *    http://www.opensource.org/licenses/gpl-2.0.php
 *
 * This software is supplied without any warranty or guaranteed support
 * whatsoever. Neither the Broad Institute nor MIT can be responsible for its
 * use, misuse, or functionality.
 */
package org.broad.igv.preprocess;

//~--- non-JDK imports --------------------------------------------------------
import org.broad.igv.data.Dataset;
import org.broad.igv.data.IntArrayList;
import org.broad.igv.feature.Genome;
import org.broad.igv.feature.GenomeManager;
import org.broad.igv.track.TrackType;

//~--- JDK imports ------------------------------------------------------------

import java.io.*;

import java.util.*;

/**
 * Represents a set of alignments for a single sample (track).
 *
 * @author Mitchell Guttman, Jim Robinson
 */
public class AlignmentDataset implements Dataset {

    int resolution;
    String genomeId;
    Genome genome;
    String name;
    Map<String, Integer> lastPositionPerChr;
    /** Map of chromosome -> start location vector */
    Map<String, int[]> starts;
    /** Map of chromosome -> value (counts) vector */
    Map<String, float[]> values;
    boolean normalize = false;
    double normFactor = 1;

    /**
     *
     * @param file
     * @param extensionFactor
     * @param maskDir
     * @param resolution
     * @param genome
     * @param controlCounts
     *
     * @throws IOException
     */
    public AlignmentDataset(File file,
            int extensionFactor,
            String maskDir,
            int resolution,
            String genomeId,
            Map<String, short[]> controlCounts,
            boolean normalize)
            throws IOException {
        this.genomeId = genomeId;
        this.genome = GenomeManager.getInstance().getGenome(genomeId);
        this.starts = new HashMap();
        this.values = new HashMap();
        this.resolution = resolution;
        this.name = file.getName();
        this.lastPositionPerChr = new HashMap();
        this.normalize = normalize;

        System.out.println("Extension factor = " + extensionFactor);

        ReadCounter parser = new ReadCounter(file);
        ReadCounter.Counts countsRecord = parser.doReadCounts(resolution,
                extensionFactor,
                genomeId, lastPositionPerChr);
        Map<String, short[]> countsByChr = countsRecord.countsPreChr;

        if (normalize) {
            normFactor = 1000000.0 /  countsRecord.totalReadCount;
            System.out.println("Normalization factor = " + normFactor);
        }

        for (String chr : countsByChr.keySet()) {
            System.out.println(chr);
            short[] bpScores = countsByChr.get(chr);
            short[] controls = (controlCounts == null) ? null : controlCounts.get(
                    chr);
            recordCounts(chr, bpScores, maskDir, controls);
        }
    }

    /**
     * Record the count data for the given chromosome.
     * @param counts
     * @param chr
     * @param maskDir
     * @throws java.io.IOException
     */
    private void recordCounts(String chr, short[] counts, String maskDir,
            short[] controls)
            throws IOException {

        MaskData maskSet = ((maskDir == null) || (maskDir.trim().length() == 0))
                ? null : parseMaskFile(maskDir, chr);
        if (maskSet != null) {
            System.out.println("Mask set size = " + maskSet.getSize());
        }

        int len = ((Integer) this.lastPositionPerChr.get(chr) / resolution) + 1;

        int[] startPositions = new int[len];
        float[] vals = new float[len];

        int nextMaskedPosition = ((maskSet == null) ? -1 : maskSet.getNext());
        for (int pos = 0; pos < (Integer) this.lastPositionPerChr.get(chr); pos += resolution) {

            int idx = pos / resolution;

            // An  index > counts length indicates data beyond chromosome.  
            if (idx < counts.length) {

                int data = counts[idx];    // map.get(pos);
                if (controls != null) {
                    data -= controls[idx];
                }
                startPositions[idx] = pos;

                if (pos == nextMaskedPosition) {
                    vals[idx] = Float.NaN;
                    nextMaskedPosition = maskSet.getNext();
                } else {
                    vals[idx] = normalize ? (float) (data * normFactor) : data;
                }
            }

        }

        this.starts.put(chr, startPositions);
        this.values.put(chr, vals);
    }

    /**
     * Parse the mask file for the given chromosome.  Return set of indeces
     * corresponding to non alignable sections.
     * <br/>
     * Note: The mask file is valid for a specific resolution and it is assumed
     * that this resolution matches the value being used in this processing run.
     * This is obviously a fragile assumption,  some check should be added here.
     *
     * @param maskDir
     * @param chr
     * @return
     * @throws java.io.IOException
     */
    private MaskData parseMaskFile(String maskDir, String chr) throws
            IOException {
        IntArrayList maskIndeces = new IntArrayList(4000000);
        File[] files = new File(maskDir).listFiles();
        if (files == null) {
            System.out.println(
                    "No mask directory or no files in mask directory: " + maskDir);
        }
        File file = files[0];
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().startsWith(chr)) {
                file = files[i];
            }
        }

        InputStream is = new FileInputStream(file);
        DataInputStream dis = new DataInputStream(is);
        int i = 0;
        try {
            while (true) {
                int temp = dis.readUnsignedByte();
                if (temp == 0) {
                    maskIndeces.add((i * resolution));
                }
                i++;
            }
        } catch (Exception ex) {
        }
        return new MaskData(maskIndeces.toArray());
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public String getGenome() {
        return genomeId;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public String[] getChromosomes() {
        String[] rtrn = new String[this.values.size()];
        int i = 0;
        for (String chr : this.values.keySet()) {
            rtrn[i] = chr;
            i++;
        }
        return rtrn;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public String[] getDataHeadings() {
        String[] rtrn = {name};
        return rtrn;
    }

    /**
     * Method description
     *
     *
     * @param chr
     *
     * @return
     */
    public int[] getStartLocations(String chr) {
        return this.starts.get(chr);
    }

    /**
     * Method description
     *
     *
     * @param chr
     *
     * @return
     */
    public int[] getEndLocations(String chr) {
        return null;
    }

    /**
     * Method description
     *
     *
     * @param chr
     *
     * @return
     */
    public String[] getFeatureNames(String chr) {
        return null;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public int getWindowSpan() {
        return this.resolution;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public boolean isNormalized() {
        return true;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public boolean isLogValues() {
        return false;
    }

    /**
     * Method description
     *
     *
     * @param heading
     * @param chr
     *
     * @return
     */
    public float[] getData(String heading, String chr) {
        return this.values.get(chr);
    }

    /**
     * Method description
     *
     *
     * @param name
     */
    public void setName(String name) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public TrackType getType() {
        return TrackType.CHIP;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public boolean isLogNormalized() {
        return true;
    }

    static class MaskData {

        private int[] maskedPositions;
        private int next;

        MaskData(int[] maskIndeces) {
            next = 0;
            this.maskedPositions = maskIndeces;
        }

        int getNext() {
            if (next >= maskedPositions.length) {
                return -1;
            } else {
                int rtn = maskedPositions[next];
                next++;
                return rtn;
            }
        }

        int getSize() {
            return maskedPositions.length;
        }
    }

    public String getColorString() {
        return null;
    }
}
