/*
 * Copyright (c) 2007-2010 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.
 */
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.broad.igv.session;


import org.apache.log4j.Logger;
import org.broad.igv.PreferenceManager;
import org.broad.igv.renderer.ColorScale;
import org.broad.igv.renderer.ContinuousColorScale;
import org.broad.igv.track.TrackType;
import org.broad.igv.ui.RegionOfInterest;
import org.broad.igv.ui.TrackFilter;
import org.broad.igv.ui.util.MessageUtils;

import java.util.*;

/**
 * @author eflakes
 */
public class Session {
    private static Logger log = Logger.getLogger(Session.class);

    private static int versionNumber = 3;
    private String filePath;
    private String groupTracksBy;
    private ViewContext viewContext = ViewContext.getInstance();
    private TrackFilter filter;
    private HashMap<String, String> preferences;
    private HashMap<TrackType, ContinuousColorScale> colorScales;

    /**
     * Map of chromosome -> regions of interest
     */
    private Map<String, Collection<RegionOfInterest>> regionsOfInterest;


    public Session(String filePath) {
        log.debug("New session");

        this.filePath = filePath;
        regionsOfInterest = new LinkedHashMap();
        preferences = new HashMap();
        colorScales = new HashMap();
    }


    public String getSessionVersion() {
        return String.valueOf(versionNumber);
    }

    /**
     * @return the absolute path to the file associated with this session
     */
    public String getPath() {
        return filePath;
    }

    public void setPreference(String key, String value) {
        preferences.put(key, value);
    }

    public void setColorScale(TrackType trackType, ContinuousColorScale colorScale) {
        colorScales.put(trackType, colorScale);
    }

    public ContinuousColorScale getColorScale(TrackType trackType) {
        if (colorScales.containsKey(trackType)) {
            return colorScales.get(trackType);
        } else {
            return PreferenceManager.getInstance().getColorScale(trackType);
        }
    }

    public boolean getDisplayOverlayTracks() {
        final String key = PreferenceManager.DISPLAY_OVERLAY_TRACKS_KEY;
        if (preferences.containsKey(key)) {
            try {
                return Boolean.parseBoolean(preferences.get(key));
            }
            catch (Exception e) {
                log.error("Error converting boolean preference " + key + "=" + preferences.get(key));
            }
        }
        return PreferenceManager.getInstance().getDiplayOverlayTracks();

    }

    public boolean getColorOverlay() {
        final String key = PreferenceManager.COLOR_OVERLAY_KEY;
        if (preferences.containsKey(key)) {
            try {
                return Boolean.parseBoolean(preferences.get(key));
            }
            catch (Exception e) {
                log.error("Error converting boolean preference " + key + "=" + preferences.get(key));
            }
        }
        return PreferenceManager.getInstance().getColorOverlay();

    }

    public String getOverlayAttribute() {
        final String key = PreferenceManager.OVERLAY_ATTRIBUTE_KEY;
        if (preferences.containsKey(key)) {
            return preferences.get(key);

        }
        return PreferenceManager.getInstance().getOverlayAttribute();
    }


    public String getLocus() {
        return getViewContext().getCurrentLocusString();
    }


    public void setLocus(String locusString) {
        try {
            Locus locus = getLocus(locusString);
            if (locus == null) {
                getViewContext().setChrName(getViewContext().getHomeChr());
            } else {
                getViewContext().jumpTo(locus.chr, locus.start, locus.end);
            }
        }
        catch (Exception e) {
            MessageUtils.showMessage("Error setting locus string: " + locusString + " (" + e.toString() + ")");
            log.error("Error setting locus string", e);
        }
    }


    public TrackFilter getFilter() {
        return filter;
    }

    public void setFilter(TrackFilter filter) {
        this.filter = filter;
    }

    public String getGroupTracksBy() {
        return groupTracksBy;
    }

    public void setGroupTracksBy(String groupTracksBy) {
        this.groupTracksBy = groupTracksBy;
    }

    public ViewContext getViewContext() {
        return viewContext;
    }

    public Collection<RegionOfInterest> getRegionsOfInterest(String chr) {
        return regionsOfInterest.get(chr);
    }

    public Collection<RegionOfInterest> getAllRegionsOfInterest() {
        ArrayList<RegionOfInterest> roiList = new ArrayList();
        for (Collection<RegionOfInterest> roi : regionsOfInterest.values()) {
            roiList.addAll(roi);
        }
        return roiList;
    }

    public void addRegionOfInterestWithNoListeners(RegionOfInterest roi) {
        String chr = roi.getChr();
        Collection<RegionOfInterest> roiList = regionsOfInterest.get(chr);
        if (roiList == null) {
            roiList = new ArrayList();
            regionsOfInterest.put(chr, roiList);
        }
        roiList.add(roi);
    }

    public void clearRegionsOfInterest() {
        if (regionsOfInterest != null) {
            regionsOfInterest.clear();
        }
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }


    static class Locus {
        String chr;
        int start;
        int end;

        Locus(String chr, int start, int end) {
            this.chr = chr;
            this.start = start;
            this.end = end;
        }
    }

    static Locus getLocus(String searchString) {

        if (searchString != null) {
            String chr;
            int[] startEnd;
            int colon = searchString.indexOf(":");

            if (colon > 0) {

                // The chromosome is that portion of the search string up to the colon.
                chr = searchString.substring(0, colon);
                String posString = searchString.substring(colon).replace(":", "");
                startEnd = getStartEnd(posString);

                if (startEnd != null) {
                    return new Locus(chr, startEnd[0], startEnd[1]);
                }
            } else {

                // No chromosome delimiter (color),  The search string is either chromosome name
                // or a locus in the current chromosome.
                if (searchString.contains("-")) {

                    // Presense of a dash indicates this is a locus string in the current chromosome
                    startEnd = getStartEnd(searchString);
                    if (startEnd != null) {
                        return new Locus(null, startEnd[0], startEnd[1]);
                    }
                } else {
                    // No dash, this is either a chromosome or an unkown search string
                    return new Locus(searchString, -1, -1);
                }
            }
        }
        return null;
    }


    /**
     * Return the start and end positions as a 2 element array for the input
     * position string.  UCSC conventions  are followed for coordinates,
     * specifically the internal representation is "zero" based (first base is
     * numbered 0) but the display representation is "one" based (first base is
     * numbered 1).   Consequently 1 is substracted from the parsed positions
     */
    private static int[] getStartEnd(String posString) {
        try {
            String[] posTokens = posString.split("-");
            String startString = posTokens[0].replaceAll(",", "");
            int start = Math.max(0, Integer.parseInt(startString)) - 1;

            // Default value for end

            int end = start + 1;
            if (posTokens.length > 1) {
                String endString = posTokens[1].replaceAll(",", "");
                end = Integer.parseInt(endString);
            }

            if (posTokens.length == 1 || (end - start) < 10) {
                int center = (start + end) / 2;
                start = center - 20;
                end = center + 20;
            } else {
                String endString = posTokens[1].replaceAll(",", "");

                // Add 1 bp to end position t make it "inclusive"
                end = Integer.parseInt(endString);
            }

            return new int[]{Math.min(start, end), Math.max(start, end)};
        } catch (NumberFormatException numberFormatException) {
            return null;
        }

    }
}


