/*
 * 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 locator 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.data;

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

/*
 * browser hide all
browser pack knownGene ensGene refGene genscan multiz17way
track name="nucCount_CM12_Jul2808" description="nucCount_CM12_Jul2808" type="wiggle_0" color=50,50,150 yLineMark=0.0 yLineOnOff=on visibility=2
fixedStep chrom=0 strt=1  step=1
 */



import java.io.*;

import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import org.broad.igv.feature.ParsingUtils;
import org.broad.igv.util.AsciiLineReader;

/**
 * Class description
 *
 *
 * @version    Enter version here..., 08/10/31
 * @author     Enter your name here...
 */
public class WiggleParser {

    private enum Type {

        FIXED, VARIABLE, BED
    };
    WiggleDataset dataset;
    /**
     * The type of wiggle locator (see UCSC documentation).
     */
    private Type type = Type.BED;

    // State variables.  This is a serial type parser,  these variables are used to hold temporary
    // state.
    private String chr;
    String lastChr = "";
    private int start;
    private int step;
    private int windowSpan;    // <- ignored for now
    IntArrayList startLocations = null;
    IntArrayList endLocations = null;
    FloatArrayList data = null;
    private double minValue;
    private double maxValue;
    ResourceLocator resourceLocator;

    /**
     * Constructs ...
     *
     *
     * @param locator
     * @param genomeId
     */
    public WiggleParser(ResourceLocator locator, String genomeId) {
        this.resourceLocator = locator;
        dataset = new WiggleDataset(genomeId, locator.getDisplayName());
    }

    /**
     * Utility method.  Returns true if this looks like a wiggle locator.  The criteria is to scan
     * the first 100 lines looking for a valid "track" line.  According to UCSC documentation
     * track lines must contain a type attribute,  which must be equal to "wiggle_0".
     *
     *
     * @param file
     * @return
     */
    public static boolean isWiggle(ResourceLocator file) {
        AsciiLineReader reader = null;
        try {
            reader = openAsciiReader(file);
            String nextLine = null;
            int lineNo = 0;
            while ((nextLine = reader.readLine()) != null && (nextLine.trim().length() > 0)) {
                if (nextLine.startsWith("track") && nextLine.contains("wilggle_0")) {
                    return true;
                }
                if (lineNo++ > 100) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(WiggleParser.class.getName()).log(Level.SEVERE, null, ex);
                }

            }
        }
        return false;
    }

    /**
     * Method description
     *
     *
     *
     *
     *
     *
     * @return
     */
    public WiggleDataset parse() {


        String[] tokens = new String[10];


        AsciiLineReader reader = null;
        try {

            reader = openAsciiReader(resourceLocator);
            String nextLine = null;


            int position = -1;


            while ((nextLine = reader.readLine()) != null && (nextLine.trim().length() > 0)) {

                if (nextLine.startsWith("#") || nextLine.startsWith("data") || nextLine.startsWith(
                        "browser")) {
                    continue;

                // Skip
                }


                if (nextLine.startsWith("track")) {
                    type = Type.BED;
                //DatasetParserUtils.parseTrackLine(nextLine, dataset.getTrackProperties());

                } else if (nextLine.startsWith("fixedStep")) {
                    type = Type.FIXED;
                    parseStepLine(nextLine);
                    position = start;

                } else if (nextLine.startsWith("variableStep")) {
                    type = Type.VARIABLE;
                    parseStepLine(nextLine);

                } else {

                    // Must be data
                    int nTokens = ParsingUtils.split(nextLine, tokens, '\t');
                    if (nTokens == 0) {
                        continue;
                    }
                    try {


                        if (type.equals(Type.BED)) {
                            if (nTokens > 3) {
                                chr = tokens[0].trim();
                                if (!chr.equals(lastChr)) {
                                    changedChromosome(dataset, lastChr);

                                }
                                lastChr = chr;

                                startLocations.add(Integer.parseInt(tokens[1]));
                                endLocations.add(Integer.parseInt(tokens[2]));
                                data.add(Float.parseFloat(tokens[3]));
                            }
                        } else if (type.equals(Type.VARIABLE)) {
                            if (nTokens > 1) {

                                // Per UCSC specification variable and fixed step coordinates are "1" based.
                                // We need to subtract 1 to convert to the internal "zero" based coordinates.
                                start = Integer.parseInt(tokens[0]) - 1;
                                int end = start + windowSpan;
                                startLocations.add(start);
                                endLocations.add(end);
                                data.add(Float.parseFloat(tokens[1]));
                            }
                        } else {    // Fixed step
                            if (position >= 0) {
                                startLocations.add(position);
                                endLocations.add(position + windowSpan);
                                data.add(Float.parseFloat(tokens[0]));
                            }
                            position += step;

                        }

                    } catch (NumberFormatException e) {
                        System.out.println("Cannot parse: " + nextLine);
                    }


                }

            }

            // The last chromosome
            changedChromosome(dataset, lastChr);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(WiggleParser.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        dataset.sort();
        return dataset;

    }

    // fixedStep chrom=chrM strt=1 step=1
    private void parseStepLine(String header) {
        String[] tokens = header.split(" ");
        for (String token : tokens) {
            String[] keyValue = token.split("=");
            if (keyValue.length >= 2) {
                if (keyValue[0].equalsIgnoreCase("chrom")) {
                    chr = keyValue[1];
                    if (!chr.equals(lastChr)) {
                        changedChromosome(dataset, lastChr);

                    }
                    lastChr = chr;

                } else if (keyValue[0].equalsIgnoreCase("start")) {
                    // Per UCSC specification variable and fixed step coordinates are "1" based.
                    // We need to subtract 1 to convert to the internal "zero" based coordinates.

                    start = Integer.parseInt(keyValue[1]) - 1;
                } else if (keyValue[0].equalsIgnoreCase("step")) {
                    step = Integer.parseInt(keyValue[1]);
                } else if (keyValue[0].equalsIgnoreCase("span")) {
                    windowSpan = Integer.parseInt(keyValue[1]);
                }

            }
        }
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public double getMinValue() {
        return minValue;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public double getMaxValue() {
        return maxValue;
    }

    private void changedChromosome(WiggleDataset dataset, String lastChr) {
        int sz = 1000000;

        if (startLocations != null && startLocations.size() > 0) {
            dataset.addDataChunk(lastChr, startLocations, endLocations, data);
            sz = startLocations.size();
        }
        startLocations = new IntArrayList(sz);
        endLocations = new IntArrayList(sz);
        data = new FloatArrayList(sz);
    }

    public static AsciiLineReader openAsciiReader(ResourceLocator locator)
            throws FileNotFoundException, IOException {
        InputStream stream = openInputStream(locator);
        return new AsciiLineReader(stream);

    }

    public static InputStream openInputStream(ResourceLocator locator)
            throws FileNotFoundException, IOException {
        BufferedReader reader = null;

        if (locator.isLocal()) {
            File file = new File(locator.getPath());

            FileInputStream fileInput = new FileInputStream(file);
            if (file.getName().endsWith("gz")) {
                return new GZIPInputStream(fileInput);

            } else {
                return fileInput;
            }
        } else {
            URL url = new URL(
                    locator.getServerURL() + "?method=getContents&file=" + locator.getPath());
            URLConnection connection = url.openConnection();

            // Note -- assumption that url stream is compressed!
            return new GZIPInputStream(connection.getInputStream());
        }
    }
}
