/*
 * 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;

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

import org.apache.log4j.Logger;
import org.broad.igv.maf.MAFManager;
import org.broad.igv.renderer.ColorScaleFactory;
import org.broad.igv.renderer.ContinuousColorScale;
import org.broad.igv.track.TrackType;
import org.broad.igv.ui.AboutDialog;
import org.broad.igv.ui.UIConstants;
import org.broad.igv.ui.util.PropertyManager;
import org.broad.igv.ui.util.ColorTable;

import static org.broad.igv.ui.util.UIUtilities.getcommaSeparatedRGBString;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

/**
 * @author eflakes
 */
public class PreferenceManager implements PropertyManager {

    /**
     * Field description
     */
    public static final String DEFAULT_CHART_TRACK_HEIGHT = "40";
    private static Logger log = Logger.getLogger(PreferenceManager.class);
    /**
     * Field description
     */
    public static final String TRACK_PROPERTIES_FILE = "TRACK_PROPERTIES_FILE";

    private static final String DEFAULT_PORT_NUMBER = "60151";

    private static final int INITIAL_TRACK_HEIGHT = 15;
    private final String SYSTEM_DEFAULT_FOR_DIRECT_DRAW = System.getProperty("sun.java2d.noddraw");
    /**
     * Property keys below
     */

    public static final String EXPAND_FEAUTRE_TRACKS = "EXPAND_FEATURE_TRACKS";
    public static final String PORT_ENABLED = "PORT_ENABLED";
    public static final String PORT_NUMBER = "PORT_NUMBER";
    public static final String TRACK_PROPERTIES_URL_KEY = "TRACK_PROPERTIES_URL";
    public static final String COLOR_SCALE_KEY = "COLOR_SCALE_";
    final public static String FRAME_BOUNDS_KEY = "IGVMainFrame.Bounds";
    final public static String DRAW_EXON_NUMBERS_KEY = "DRAW_EXON_NUMBERS";
    final public static String RECENT_SESSION_KEY = "IGVMainFrame.Session.recent.sessions";
    final public static String TRACK_HEIGHT_KEY = "IGVMainFrame.track.height";
    final public static String CHART_TRACK_HEIGHT_KEY = "IGVMainFrame.chart.track.height";
    final public static String SHOW_MISSING_DATA_KEY = "IGVMainFrame.track.show.missing.data";
    final public static String SHOW_ATTRIBUTE_VIEWS_KEY = "IGVMainFrame.track.show.attribute.views";
    final public static String SHOW_SINGLE_TRACK_PANE_KEY = "IGVMainFrame.single.track.pane";
    final public static String GENOMES_SEQUENCE_URL = "IGVMainFrame.genome.sequence.dir";
    final public static String JOIN_ADJACENT_SEGMENTS_KEY = "IGVMainFrame.join.adjacent.segments";
    final public static String SHOW_REGION_TOOL_KEY = "IGVMainFrame.show.region.tool";
    final public static String LAST_EXPORTED_REGION_DIRECTORY = "LAST_EXPORTED_REGION_DIRECTORY";
    final static public String LAST_TRACK_DIRECTORY = "LAST_TRACK_DIRECTORY";
    final static public String LAST_ATTRIBUTE_FILE_DIRECTORY = "LAST_ATTRIBUTE_FILE_DIRECTORY";
    final static public String LAST_SNAPSHOT_DIRECTORY = "LAST_SNAPSHOT_DIRECTORY";
    final static public String LAST_GENOME_IMPORT_DIRECTORY = "LAST_GENOME_IMPORT_DIRECTORY";
    final static public String LAST_SESSION_DIRECTORY = "LAST_SESSION_DIRECTORY";
    final static public String DEFAULT_GENOME_KEY = "DEFAULT_GENOME_KEY";
    final static public String LAST_CHROMOSOME_VIEWED_KEY = "LAST_CHROMOSOME_VIEWED_KEY";
    final public static String MUTATION_INDEL_COLOR_KEY = "MUTATION_INDEL_COLOR_KEY";
    final public static String MUTATION_MISSENSE_COLOR_KEY = "MUTATION_MISSENSE_COLOR_KEY";
    final public static String MUTATION_NONSENSE_COLOR_KEY = "MUTATION_NONSENSE_COLOR_KEY";
    final public static String MUTATION_SPLICE_SITE_COLOR_KEY = "MUTATION_SPLICE_SITE_COLOR_KEY";
    final public static String MUTATION_SYNONYMOUS_COLOR_KEY = "MUTATION_SYNONYMOUS_COLOR_KEY";
    final public static String MUTATION_TARGETED_REGION_COLOR_KEY = "MUTATION_TARGETED_REGION_COLOR_KEY";
    final public static String MUTATION_UNKNOWN_COLOR_KEY = "MUTATION_UNKNOWN_COLOR_KEY";
    final public static String DIRECT_DRAW_DISABLED_KEY = "DIRECT_DRAW_DISABLED_KEY";
    final public static String OVERLAY_TRACKS_KEY = "OVERLAY_TRACKS_KEY";
    final public static String DISPLAY_OVERLAY_TRACKS_KEY = "DISPLAY_OVERLAY_TRACKS_KEY";
    final public static String OVERLAY_ATTRIBUTE_KEY = "OVERLAY_ATTRIBUTE_KEY";
    final public static String COLOR_OVERLAY_KEY = "COVER_OVERLAY_KEY";
    final public static String ENABLE_LINKED_SORTING = "ENABLE_LINKED_SORTING";
    final public static String TRACK_ATTRIBUTE_NAME_KEY = "TRACK_ATTRIBUTE_NAME_KEY";
    final public static String DATA_SERVER_URL_KEY = "MASTER_RESOURCE_FILE_KEY";
    final public static String CHECKED_RESOURCES_KEY = "CHECKED_RESOURCES_KEY";
    final public static String DEFINE_GENOME_INPUT_DIRECTORY_KEY = "DEFINE_GENOME_INPUT_DIRECTORY_KEY";
    final public static String LAST_CYTOBAND_DIRECTORY_KEY = "LAST_CYTOBAND_DIRECTORY_KEY";
    final public static String LAST_REFFLAT_DIRECTORY_KEY = "LAST_REFFLAT_DIRECTORY_KEY";
    final public static String LAST_FASTA_DIRECTORY_KEY = "LAST_FASTA_DIRECTORY_KEY";
    final public static String LAST_SEQUENCE_DIRECTORY_KEY = "LAST_SEQUENCE_DIRECTORY_KEY";
    final public static String MAF_SPECIES_KEY = "MAF_SPECIES_KEY";
    final public static String PROBE_MAPPING_KEY = "PROBE_MAPPING_KEY";
    final public static String SEARCH_ZOOM = "SEARCH_ZOOM";
    final public static String NORMALIZE_COVERAGE = "NORMALIZE_COVERAGE";

    final public static String USE_PROXY = "PROXY.USE";
    final public static String PROXY_HOST = "PROXY.HOST";
    final public static String PROXY_PORT = "PROXY.PORT";
    final public static String PROXY_AUTHENTICATE = "PROXY.AUTHENTICATE";
    final public static String PROXY_USER = "PROXY.USERNAME";
    final public static String PROXY_PW = "PROXY.PW";

    final public static String KNOWN_SNPS = "KNOWN_SNPS_FILE";

    final public static String USE_BYTE_RANGE = "UseHttpByteRange";

    public static String DEFAULT_DATA_SERVER_URL;

    static {
        Properties properties = new Properties();
        try {
            properties.load(AboutDialog.class.getResourceAsStream("/resources/about.properties"));
            DEFAULT_DATA_SERVER_URL = properties.getProperty("master-resource-url", "http://www.broadinstitute.org/igvdata/$$_dataServerRegistry.txt");
            if (DEFAULT_DATA_SERVER_URL.equals("@DEFAULT_MASTER_RESOURCE_URL")) {
                DEFAULT_DATA_SERVER_URL = "http://www.broadinstitute.org/igvdata/$$_dataServerRegistry.txt";
            }
        } catch (IOException e) {
            DEFAULT_DATA_SERVER_URL = "http://www.broadinstitute.org/igvdata/$$_dataServerRegistry.txt";
        }
    }

    /**
     * The preference cache
     */
    //final static private Preferences IGV_PREFERENCE_MANAGER = Preferences.userNodeForPackage(PreferenceManager.class);
    IGVPreferences preferences;

    /**
     * Cache of preference values.  Profiling reveals that Preferences.get()
     * is taking huge amounts of time.  There are hundereds of thousands of
     * calls to this to get the track height,  this is possibly a bad design
     * decision, however caching the preference values solves the peformance
     * problem for now.
     */
    private Map<String, String> stringCache = new HashMap(1000);
    private Map<String, Boolean> booleanCache = new Hashtable();
    private Map<String, Object> objectCache = new Hashtable();
    private Map<TrackType, ContinuousColorScale> colorScaleCache = new Hashtable();

    public static PreferenceManager getInstance() {

        return instance;
    }

    private static PreferenceManager instance = new PreferenceManager();

    private PreferenceManager() {
        preferences = new IGVPreferences();
        if (preferences.get("migrated") == null) {
            migratePreferences();
        }
    }

    private void migratePreferences() {

        try {
            preferences.suspendStorage();
            Preferences oldPreferences = Preferences.userNodeForPackage(PreferenceManager.class);
            for (String key : oldPreferences.keys()) {
                String value = oldPreferences.get(key, null);
                if (value != null) {
                    preferences.put(key, value);
                }
            }
            oldPreferences.clear();
            preferences.resumeStorage();
            preferences.put("migrated", "true");

        } catch (BackingStoreException e) {
            log.error("Error migrating preferences", e);
        }
    }

    public void mergePreferences(Map<String, String> newPrefs) {
        for (Map.Entry<String, String> entry : newPrefs.entrySet()) {
            String key = entry.getKey();
            if (!newPrefs.containsKey(key)) {
                put(key, entry.getValue());
            }
        }
    }


    /**
     * Return the preference as a boolean value.
     *
     * @param key
     * @param defaultValue
     * @return
     */
    public boolean getBooleanPreference(String key, boolean defaultValue) {
        Boolean boolValue = booleanCache.get(key);
        if (boolValue == null) {

            String value = get(key, null);
            if ((value != null) && !value.trim().equals("")) {

                // If value ok
                boolValue = new Boolean(value);
            } else {
                boolValue = new Boolean(defaultValue);
            }
            booleanCache.put(key, boolValue);
        }
        return boolValue.booleanValue();
    }


    public boolean isMapProbesToGenes() {
        return getBooleanPreference(PROBE_MAPPING_KEY, false);
    }

    public boolean isJoinAdjacentSegments() {
        return getBooleanPreference(JOIN_ADJACENT_SEGMENTS_KEY, false);
    }

    public boolean isUseByteRange() {
        return getBooleanPreference(USE_BYTE_RANGE, true);
    }

    /**
     * Update any cached values with the new key/value pair
     *
     * @param key
     * @param value
     */
    private void updateCaches(String key, String value) {
        stringCache.put(key, value);
        if (booleanCache.containsKey(key)) {
            booleanCache.put(key, new Boolean(value));
        }
        objectCache.remove(key);
    }

    private void clearCaches() {
        stringCache.clear();
        booleanCache.clear();
        objectCache.clear();
    }


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


    public void putAll(Map<String, String> updatedPrefs) {
        for (Map.Entry<String, String> entry : updatedPrefs.entrySet()) {
            if (entry.getValue() == null) {
                remove(entry.getKey());

            } else {
                put(entry.getKey(), entry.getValue());
            }
        }
        clearCaches();

    }


    public String get(String key, String defaultString) {
        String rtn = stringCache.get(key);
        if (rtn == null) {
            rtn = preferences.get(key, defaultString);
            stringCache.put(key, rtn);
        }
        return rtn;
    }


    public void remove(String key) {
        preferences.remove(key);
        stringCache.remove(key);
        booleanCache.remove(key);
        objectCache.clear();
    }


    public void clear() {
        preferences.clear();
        stringCache.clear();
        booleanCache.clear();
        objectCache.clear();

        // Also clear the old preferences form the old JavaPreferences store
        Preferences oldPreferences = Preferences.userNodeForPackage(PreferenceManager.class);
        try {
            oldPreferences.clear();
        } catch (BackingStoreException e) {
            log.error("Error clearing preferences store: ", e);
        }
    }


    public String getGenomeListURL() {
        return get(PreferenceManager.GENOMES_SEQUENCE_URL,
                UIConstants.DEFAULT_SERVER_GENOME_ARCHIVE_LIST);
    }


    public boolean getOverlayTracks() {
        return getBooleanPreference(OVERLAY_TRACKS_KEY, true);
    }


    public void setOverlayTracks(boolean value) {
        this.put(OVERLAY_TRACKS_KEY, String.valueOf(value));
    }

    public List<String> getMafSpecies() {
        String tmp = get(MAF_SPECIES_KEY, null);

        String[] species = null;
        if (tmp == null) {
            species = MAFManager.species;
        } else {
            species = tmp.split(":");
        }
        return Arrays.asList(species);
    }

    public void setMafSpecies(List<String> species) {
        StringBuffer buf = new StringBuffer(species.size() * 7);
        Iterator<String> iter = species.iterator();
        while (iter.hasNext()) {
            buf.append(iter.next());
            if (iter.hasNext()) {
                buf.append(":");
            }
        }
        put(MAF_SPECIES_KEY, buf.toString());

    }


    /**
     * @return
     */
    public boolean getColorOverlay() {
        return getBooleanPreference(COLOR_OVERLAY_KEY, false);
    }

    /**
     * @return
     */
    public boolean getDiplayOverlayTracks() {
        return getBooleanPreference(DISPLAY_OVERLAY_TRACKS_KEY, true);
    }


    /**
     * @param value
     */
    public void setDirectDrawDisabled(boolean value) {

        if (value) {
            System.setProperty("sun.java2d.noddraw", "true");
        } else if (System.getProperty("sun.java2d.noddraw") != null) {
            System.clearProperty("sun.java2d.noddraw");
        }

        put(DIRECT_DRAW_DISABLED_KEY, String.valueOf(value));
    }

    /**
     * @return
     */
    public String getOverlayAttribute() {
        return get(OVERLAY_ATTRIBUTE_KEY, "LINKING_ID");
    }

    public boolean isLinkedSortingEnabled() {
        return this.getBooleanPreference(ENABLE_LINKED_SORTING, true);
    }

    public void setLinkedSortingEnabled(boolean value) {
        put(ENABLE_LINKED_SORTING, Boolean.toString(value));
    }

    /**
     * @return
     */
    public String getCheckedResources() {
        return preferences.get(CHECKED_RESOURCES_KEY, null);
    }

    /**
     * @param value
     */
    public void setCheckedResources(String value) {
        preferences.put(CHECKED_RESOURCES_KEY, value);
    }

    /**
     * @return
     */
    public boolean getShowSingleTrackPane() {
        return getBooleanPreference(SHOW_SINGLE_TRACK_PANE_KEY, false);
    }

    /**
     * @param bounds
     */
    public void setApplicationFrameBounds(Rectangle bounds) {

        StringBuffer buffer = new StringBuffer();
        buffer.append(bounds.x);
        buffer.append(",");
        buffer.append(bounds.y);
        buffer.append(",");
        buffer.append(bounds.width);
        buffer.append(",");
        buffer.append(bounds.height);
        put(FRAME_BOUNDS_KEY, buffer.toString());
    }

    /**
     * @return
     */
    public Rectangle getApplicationFrameBounds() {

        Rectangle bounds = null;

        // Set the application's previous location and size
        String applicationBounds = preferences.get(FRAME_BOUNDS_KEY, null);

        if (applicationBounds != null) {
            String[] values = applicationBounds.split(",");
            int x = Integer.parseInt(values[0]);
            int y = Integer.parseInt(values[1]);
            int width = Integer.parseInt(values[2]);
            int height = Integer.parseInt(values[3]);
            bounds = new Rectangle(x, y, width, height);
        }
        return bounds;
    }

    /**
     * @param directory
     */
    public void setLastExportedRegionDirectory(File directory) {

        put(LAST_EXPORTED_REGION_DIRECTORY, directory.getAbsolutePath());
    }

    /**
     * @return
     */
    public File getLastExportedRegionDirectory() {

        File exportedRegionDirectory = null;

        String lastFilePath = get(LAST_EXPORTED_REGION_DIRECTORY, null);

        if (lastFilePath != null) {

            // Create the exported region directory
            exportedRegionDirectory = new File(lastFilePath);
        }

        return exportedRegionDirectory;
    }

    /**
     * @param directory
     */
    public void setLastSnapshotDirectory(File directory) {

        put(LAST_SNAPSHOT_DIRECTORY, directory.getAbsolutePath());
    }

    /**
     * @return
     */
    public File getLastSnapshotDirectory() {

        File snapshotDirectory = null;

        String lastFilePath = get(LAST_SNAPSHOT_DIRECTORY, null);

        if (lastFilePath != null) {

            // Create the snapshot directory
            snapshotDirectory = new File(lastFilePath);
        }

        return snapshotDirectory;
    }

    /**
     * @param directory
     */
    public void setDefineGenomeInputDirectory(File directory) {

        put(DEFINE_GENOME_INPUT_DIRECTORY_KEY, directory.getAbsolutePath());
    }

    /**
     * @return
     */
    public File getDefineGenomeInputDirectory() {

        File directory = null;

        String lastFilePath = get(DEFINE_GENOME_INPUT_DIRECTORY_KEY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            directory = new File(lastFilePath);
        }

        return directory;
    }

    /**
     * @param directory
     */
    public void setLastGenomeImportDirectory(File directory) {

        put(LAST_GENOME_IMPORT_DIRECTORY, directory.getAbsolutePath());
    }

    /**
     * @return
     */
    public File getLastGenomeImportDirectory() {

        File genomeImportDirectory = null;

        String lastFilePath = get(LAST_GENOME_IMPORT_DIRECTORY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            genomeImportDirectory = new File(lastFilePath);
        }

        return genomeImportDirectory;
    }

    /**
     * @param directory
     */
    public void setLastCytobandDirectory(File directory) {

        if (directory != null) {
            put(LAST_CYTOBAND_DIRECTORY_KEY, directory.getAbsolutePath());
        }
    }

    /**
     * @return
     */
    public File getLastCytobandDirectory() {

        File directory = null;

        String lastFilePath = get(LAST_CYTOBAND_DIRECTORY_KEY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            directory = new File(lastFilePath);
        }

        return directory;
    }

    /**
     * @param directory
     */
    public void setLastRefFlatDirectory(File directory) {

        if (directory != null) {
            put(LAST_REFFLAT_DIRECTORY_KEY, directory.getAbsolutePath());
        }
    }

    /**
     * @return
     */
    public File getLastRefFlatDirectory() {

        File directory = null;

        String lastFilePath = get(LAST_REFFLAT_DIRECTORY_KEY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            directory = new File(lastFilePath);
        }

        return directory;
    }

    /**
     * @param directory
     */
    public void setLastFastaDirectory(File directory) {

        if (directory != null) {
            put(LAST_FASTA_DIRECTORY_KEY, directory.getAbsolutePath());
        }
    }

    /**
     * @return
     */
    public File getLastFastaDirectory() {

        File directory = null;

        String lastFilePath = get(LAST_FASTA_DIRECTORY_KEY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            directory = new File(lastFilePath);
        }

        return directory;
    }

    /**
     * @param directory
     */
    public void setLastSequenceDirectory(File directory) {

        if (directory != null) {
            put(LAST_SEQUENCE_DIRECTORY_KEY, directory.getAbsolutePath());
        }
    }

    /**
     * @return
     */
    public File getLastSequenceDirectory() {

        File directory = null;

        String lastFilePath = get(LAST_SEQUENCE_DIRECTORY_KEY, UIConstants.getDefaultUserDirectory());

        if (lastFilePath != null) {
            directory = new File(lastFilePath);
        }

        return directory;
    }

    /**
     * @param directory
     */
    public void setLastSessionDirectory(File directory) {

        put(LAST_SESSION_DIRECTORY, directory.getAbsolutePath());
    }

    /**
     * @return
     */
    public File getLastSessionDirectory() {

        File sessionDirectory = null;

        String lastFilePath = get(LAST_SESSION_DIRECTORY, null);

        if (lastFilePath != null) {

            // Create the session directory
            sessionDirectory = new File(lastFilePath);
        }

        return sessionDirectory;
    }

    /**
     * @return
     */
    public boolean getShowRegionTool() {
        return getBooleanPreference(SHOW_REGION_TOOL_KEY, true);

    }

    /**
     * @param recentSessions
     */
    public void setRecentSessions(String recentSessions) {
        put(RECENT_SESSION_KEY, recentSessions);
    }

    /**
     * @return
     */
    public String getRecentSessions() {

        return get(RECENT_SESSION_KEY, null);
    }

    /**
     * @param masterResourceFile
     */
    public void setDataServerURL(String masterResourceFile) {
        put(DATA_SERVER_URL_KEY, masterResourceFile);
    }

    /**
     * @return
     */
    public int getPortNumber() {

        try {
            return Integer.parseInt(get(PORT_NUMBER, DEFAULT_PORT_NUMBER));
        } catch (NumberFormatException numberFormatException) {
            return Integer.parseInt(DEFAULT_PORT_NUMBER);
        }
    }

    public boolean isPortEnabled() {
        return getBooleanPreference(PORT_ENABLED, true);
    }

    public boolean isExpandTracks() {
        return getBooleanPreference(EXPAND_FEAUTRE_TRACKS, false);
    }

    /**
     * @return
     */
    public String getDataServerURL() {

        String masterResourceFile = get(DATA_SERVER_URL_KEY, DEFAULT_DATA_SERVER_URL);
        return masterResourceFile;
    }

    /**
     * @param value
     */
    public void setShowRegionTool(boolean value) {
        put(SHOW_REGION_TOOL_KEY, Boolean.toString(value));
    }

    /**
     * @param isShowable
     */
    public void setShowAttributeView(boolean isShowable) {

        put(PreferenceManager.SHOW_ATTRIBUTE_VIEWS_KEY, Boolean.toString(isShowable));
    }

    public boolean getShowAttributeView() {
        return getBooleanPreference(SHOW_ATTRIBUTE_VIEWS_KEY, true);
    }

    /**
     * @return
     */
    public boolean getShowMissingData() {
        return getBooleanPreference(SHOW_MISSING_DATA_KEY, false);
    }

    /**
     * @return
     */
    public boolean isShowSingleTrackPane() {

        String showSingleTrackPane =
                PreferenceManager.getInstance().get(PreferenceManager.SHOW_SINGLE_TRACK_PANE_KEY,
                        "false");

        return Boolean.parseBoolean(showSingleTrackPane);
    }


    /**
     * @param chromosome
     */
    public void setLastChromosomeViewed(String chromosome) {
        put(LAST_CHROMOSOME_VIEWED_KEY, chromosome);
    }

    /**
     * @return
     */
    public String getLastChromosomeViewed() {
        String chromosome = get(LAST_CHROMOSOME_VIEWED_KEY, "chr1");
        return chromosome;
    }

    /**
     * @param attributeName
     */
    public void setTrackAttributeName(String attributeName) {
        put(TRACK_ATTRIBUTE_NAME_KEY, attributeName);
    }

    /**
     * @return
     */
    public String getTrackAttributeName() {
        return get(TRACK_ATTRIBUTE_NAME_KEY, "SAMPLE");
    }

    /**
     * @param url
     */
    public void setTrackPropertiesURL(String url) {
        put(TRACK_PROPERTIES_URL_KEY, url);
    }

    /**
     * @return
     */
    public String getTrackPropertiesURL() {
        return get(TRACK_PROPERTIES_URL_KEY, null);
    }

    /**
     * @param file
     */
    public void setTrackPropertiesFile(String file) {
        put(TRACK_PROPERTIES_FILE, file);
    }

    /**
     * @return
     */
    public String getTrackPropertiesFile() {
        return get(TRACK_PROPERTIES_FILE, null);
    }

    /**
     * @param genomeId
     */
    public void setDefaultGenome(String genomeId) {
        put(DEFAULT_GENOME_KEY, genomeId);
    }

    /**
     * @return
     */
    public String getDefaultGenome() {

        String genome = get(DEFAULT_GENOME_KEY, "hg18");
        return genome;
    }

    /**
     * @param directory
     */
    public void setLastTrackDirectory(File directory) {
        String lastDirectory = directory.getAbsolutePath();
        put(LAST_TRACK_DIRECTORY, lastDirectory);
    }

    /**
     * @return
     */
    public File getLastTrackDirectory() {

        String lastDirectoryPath = get(LAST_TRACK_DIRECTORY, null);

        File lastDirectoryFile = null;
        if (lastDirectoryPath != null) {
            lastDirectoryFile = new File(lastDirectoryPath);
        }

        return lastDirectoryFile;
    }

    /**
     * @param directory
     */
    public void setLastAttributeDirectory(File directory) {
        String lastDirectory = directory.getAbsolutePath();
        put(LAST_ATTRIBUTE_FILE_DIRECTORY, lastDirectory);
    }

    /**
     * @return
     */
    public File getLastAttributeDirectory() {

        String lastDirectoryPath = get(LAST_ATTRIBUTE_FILE_DIRECTORY, null);

        File lastDirectoryFile = null;
        if (lastDirectoryPath != null) {
            lastDirectoryFile = new File(lastDirectoryPath);
        }

        return lastDirectoryFile;
    }

    /**
     * @return
     */
    public int getDefaultTrackHeight() {
        String key = PreferenceManager.TRACK_HEIGHT_KEY;
        Number val = (Number) objectCache.get(key);
        if (val == null) {
            val = new Double(get(key, String.valueOf(INITIAL_TRACK_HEIGHT)));
            objectCache.put(key, val);
        }
        return val.intValue();
    }

    /**
     * @return
     */
    public int getDefaultChartTrackHeight() {
        String key = PreferenceManager.CHART_TRACK_HEIGHT_KEY;
        Number val = (Number) objectCache.get(key);
        if (val == null) {
            val = new Double(get(key, DEFAULT_CHART_TRACK_HEIGHT));
            objectCache.put(key, val);
        }
        return val.intValue();
    }

    /**
     * Set the color scheme for the track type.
     *
     * @param type
     * @param colorScaleString
     */
    public void setColorScaleString(TrackType type, String colorScaleString) {

        put(COLOR_SCALE_KEY + type.toString(), colorScaleString);

        // Force cache restorePersistentState
        colorScaleCache.remove(type);
    }

    /**
     * Set the color scheme for the track type.  Its unfortunate that this is a public
     * method,  color schemes are managed by ColorScaleFactory and that is the
     * only object that should call this method.
     *
     * @param type
     * @param colorScale
     */
    public void setColorScale(TrackType type, ContinuousColorScale colorScale) {
        String colorScaleString = colorScale.asString();
        put(COLOR_SCALE_KEY + type.toString(), colorScaleString);
        colorScaleCache.put(type, colorScale);
    }

    /**
     * Get the default color scheme for the track type.  Only specific TrackTypes have default schemes.  The scale
     * returned is marked as "default".
     *
     * @param type
     * @return
     */


    public ContinuousColorScale getColorScale(TrackType type) {
        if(type == null) {
            return null;
        }

        ContinuousColorScale scale = colorScaleCache.get(type);

        if (scale == null && scaledTypes.contains(type)) {
            String colorScaleString = get(COLOR_SCALE_KEY + type.toString(), null);
            if (colorScaleString != null) {
                scale = (ContinuousColorScale) ColorScaleFactory.getScaleFromString(colorScaleString);
            } else {
                scale = getDefaultColorScale(type);
            }
            if (scale != null) {
                scale.setDefault(true);
                colorScaleCache.put(type, scale);
            }
        }
        return scale;
    }


    static Set<String> scaledTypes = new HashSet(Arrays.asList(
            TrackType.LOH, TrackType.RNAI, TrackType.POOLED_RNAI, TrackType.DNA_METHYLATION,
            TrackType.GENE_EXPRESSION, TrackType.COPY_NUMBER, TrackType.ALLELE_SPECIFIC_COPY_NUMBER, TrackType.CNV));


    /**
     * Return the default color scale.  This si the scale for track type "generic",
     * as well as any track type without a specific scale.
     *
     * @param type
     * @return
     */
    public ContinuousColorScale getDefaultColorScale(TrackType type) {
        switch (type) {
            case LOH:
                return new ContinuousColorScale(0, -1, 0, 1, Color.red, UIConstants.LIGHT_YELLOW, Color.blue);
            case RNAI:
            case POOLED_RNAI:
                ContinuousColorScale cs = new ContinuousColorScale(0, -3, 0, 3, Color.red, Color.white, Color.blue);
                cs.setNoDataColor(new Color(225, 225, 225));
                return cs;

            case DNA_METHYLATION:
                cs = new ContinuousColorScale(0.4, 1, Color.WHITE, Color.GREEN.darker());
                cs.setNoDataColor(new Color(225, 225, 225));
                return cs;

            case GENE_EXPRESSION:
                cs = new ContinuousColorScale(-0.1, -1.5, 0.1, 1.5, Color.BLUE, Color.WHITE, Color.RED);
                cs.setNoDataColor(new Color(225, 225, 225));
                return cs;

            case COPY_NUMBER:
            case ALLELE_SPECIFIC_COPY_NUMBER:
            case CNV:
                return new ContinuousColorScale(-0.1, -1.5, 0.1, 1.5, Color.BLUE, Color.WHITE, Color.RED);

            default:
                return null;
        }
    }

    /**
     * @param colorScheme
     */
    public void setMutationColorScheme(ColorTable colorScheme) {

        setMutationColorScheme(
                getcommaSeparatedRGBString(colorScheme.get("Indel")),
                getcommaSeparatedRGBString(colorScheme.get("Missense")),
                getcommaSeparatedRGBString(colorScheme.get("Nonsense")),
                getcommaSeparatedRGBString(colorScheme.get("Splice_site")),
                getcommaSeparatedRGBString(colorScheme.get("Synonymous")),
                getcommaSeparatedRGBString(colorScheme.get("Targeted_Region")),
                getcommaSeparatedRGBString(colorScheme.get("Unknown")));
    }

    /**
     * @param indel
     * @param missense
     * @param nonsense
     * @param spliceSite
     * @param synonymous
     * @param targetedRegion
     * @param unknown
     */
    public void setMutationColorScheme(String indel, String missense, String nonsense,
                                       String spliceSite, String synonymous, String targetedRegion,
                                       String unknown) {

        put(MUTATION_INDEL_COLOR_KEY, indel);
        put(MUTATION_MISSENSE_COLOR_KEY, missense);
        put(MUTATION_NONSENSE_COLOR_KEY, nonsense);
        put(MUTATION_SPLICE_SITE_COLOR_KEY, spliceSite);
        put(MUTATION_SYNONYMOUS_COLOR_KEY, synonymous);
        put(MUTATION_TARGETED_REGION_COLOR_KEY, targetedRegion);
        put(MUTATION_UNKNOWN_COLOR_KEY, unknown);
    }

    /**
     * @return
     */
    public ColorTable getMutationColorScheme() {

        String indelColor = get(MUTATION_INDEL_COLOR_KEY, null);
        String missenseColor = get(MUTATION_MISSENSE_COLOR_KEY, null);
        String nonsenseColor = get(MUTATION_NONSENSE_COLOR_KEY, null);
        String spliceSiteColor = get(MUTATION_SPLICE_SITE_COLOR_KEY, null);
        String synonymousColor = get(MUTATION_SYNONYMOUS_COLOR_KEY, null);
        String targetedRegionColor = get(MUTATION_TARGETED_REGION_COLOR_KEY, null);
        String unknownColor = get(MUTATION_UNKNOWN_COLOR_KEY, null);

        ColorTable colorTable = new ColorTable();
        if ((indelColor != null) && (missenseColor != null) && (nonsenseColor != null) &&
                (spliceSiteColor != null) &&
                (synonymousColor != null) &&
                (targetedRegionColor != null) &&
                (unknownColor != null)) {


            String rgb[] = indelColor.split(",");
            Color color1 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
            colorTable.put("Indel", color1);

            rgb = missenseColor.split(",");
            Color color2 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
            colorTable.put("Missense", color2);

            rgb = nonsenseColor.split(",");
            Color color3 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
            colorTable.put("Nonsense", color3);

            rgb = spliceSiteColor.split(",");
            Color color4 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
            colorTable.put("Splice_site", color4);

            rgb = synonymousColor.split(",");
            Color color5 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]),
                    Integer.parseInt(rgb[2]));
            colorTable.put("Synonymous", color5);

            rgb = targetedRegionColor.split(",");
            Color color6 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]),
                    Integer.parseInt(rgb[2]));
            colorTable.put("Targeted_Region", color6);

            rgb = unknownColor.split(",");
            Color color7 = new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]),
                    Integer.parseInt(rgb[2]));
            colorTable.put("Unknown", color7);
        }

        return colorTable;
    }


    /**
     */
    public ChartPreferences getChartPreferences() {
        ChartPreferences prefs =
                (ChartPreferences) objectCache.get(ChartPreferences.class.getName());
        if (prefs == null) {
            prefs = new ChartPreferences();
            objectCache.put(ChartPreferences.class.getName(), prefs);
        }
        prefs.setDrawTopBorder(getBooleanPreference(ChartPreferences.DRAW_TOP_BORDER, false));
        prefs.setDrawBottomBorder(getBooleanPreference(ChartPreferences.DRAW_BOTTOM_BORDER, false));
        prefs.setColorBorders(getBooleanPreference(ChartPreferences.COLOR_BORDERS, true));
        prefs.setDrawAxis(getBooleanPreference(ChartPreferences.Y_AXIS, false));
        prefs.setDrawTrackName(getBooleanPreference(ChartPreferences.DRAW_TRACK_NAME, false));
        prefs.setColorTrackName(getBooleanPreference(ChartPreferences.COLOR_TRACK_NAME, true));
        prefs.setAutoscale(getBooleanPreference(ChartPreferences.AUTOSCALE, true));
        prefs.setShowDataRange(getBooleanPreference(ChartPreferences.SHOW_DATA_RANGE, true));
        return prefs;
    }


    /**
     * Class description
     *
     * @author Enter your name here...
     * @version Enter version here..., 08/11/02
     */
    public static class ChartPreferences {

        public static final String DRAW_TOP_BORDER = "CHART.DRAW_TOP_BORDER";
        public static final String DRAW_BOTTOM_BORDER = "CHART.DRAW_BOTTOM_BORDER";
        public static final String COLOR_BORDERS = "CHART.COLOR_BORDERS";
        public static final String Y_AXIS = "CHART.DRAW_AXIS";
        public static final String DRAW_TRACK_NAME = "CHART.DRAW_TRACK_NAME";
        public static final String COLOR_TRACK_NAME = "CHART.COLOR_TRACK_NAME";
        public static final String AUTOSCALE = "CHART.AUTOSCALE";
        public static final String SHOW_DATA_RANGE = "CHART.SHOW_DATA_RANGE";
        private boolean drawTopBorder;
        private boolean drawBottomBorder;
        private boolean colorBorders;
        private boolean drawAxis;
        private boolean drawTrackName;
        private boolean colorTrackName;
        private boolean autoscale;
        private boolean showDataRange;

        public ChartPreferences() {
        }

        public boolean isDrawTopBorder() {
            return drawTopBorder;
        }

        public void setDrawTopBorder(boolean drawBorders) {
            this.drawTopBorder = drawBorders;
        }

        public boolean isColorBorders() {
            return colorBorders;
        }

        public void setColorBorders(boolean colorBorders) {
            this.colorBorders = colorBorders;
        }

        public boolean isDrawAxis() {
            return drawAxis;
        }

        public void setDrawAxis(boolean drawAxis) {
            this.drawAxis = drawAxis;
        }

        public boolean isDrawBottomBorder() {
            return drawBottomBorder;
        }

        public void setDrawBottomBorder(boolean drawBottomBorder) {
            this.drawBottomBorder = drawBottomBorder;
        }

        public boolean isDrawTrackName() {
            return drawTrackName;
        }

        public void setDrawTrackName(boolean drawTrackName) {
            this.drawTrackName = drawTrackName;
        }

        public boolean isColorTrackName() {
            return colorTrackName;
        }

        public void setColorTrackName(boolean colorTrackName) {
            this.colorTrackName = colorTrackName;
        }

        public boolean isAutoscale() {
            return autoscale;
        }

        public void setAutoscale(boolean autoscale) {
            this.autoscale = autoscale;
        }

        public boolean isShowDataRange() {
            return showDataRange;
        }

        public void setShowDataRange(boolean showDataRange) {
            this.showDataRange = showDataRange;
        }
    }

    /**
     */
    public SAMPreferences getSAMPreferences() {
        SAMPreferences prefs = (SAMPreferences) objectCache.get(SAMPreferences.class.getName());
        if (prefs == null) {
            prefs = new SAMPreferences();
            objectCache.put(SAMPreferences.class.getName(), prefs);

            prefs.setMaxVisibleRange(Float.parseFloat(get(SAMPreferences.MAX_VISIBLE_RANGE, "30")));
            prefs.setShowDuplicates(getBooleanPreference(SAMPreferences.SHOW_DUPLICATES, false));
            prefs.setQualityThreshold(Integer.parseInt(get(SAMPreferences.QUALITY_THRESHOLD, "0")));
            prefs.setInsertSizeThreshold(Integer.parseInt(get(SAMPreferences.INSERT_SIZE_THRESHOLD, "10000")));
            prefs.setFlagUnmappedPair(getBooleanPreference(SAMPreferences.FLAG_UNMAPPED_PAIR, false));
            prefs.setAutosort(getBooleanPreference(SAMPreferences.AUTO_SORT, false));
            prefs.setShadeCenter(getBooleanPreference(SAMPreferences.SHADE_CENTER, true));
            prefs.setShowRefSequence(getBooleanPreference(SAMPreferences.SHOW_REF_SEQ, false));
            prefs.setShowCoverageTrack(getBooleanPreference(SAMPreferences.SHOW_COV_TRACK, true));
            prefs.setMaxLevels(getIntegerPreference(SAMPreferences.MAX_LEVELS, 500000));
            prefs.setMaxReads(getIntegerPreference(SAMPreferences.MAX_READS, 500000));
            prefs.setShadeBaseQuality(getBooleanPreference(SAMPreferences.SHADE_BASE_QUALITY, true));
            prefs.setBaseQualityMin(getIntegerPreference(SAMPreferences.BASE_QUALITY_MIN, 5));
            prefs.setBaseQualityMax(getIntegerPreference(SAMPreferences.BASE_QUALITY_MAX, 20));
            prefs.setFilterAlignments(getBooleanPreference(SAMPreferences.FILTER_ALIGNMENTS, false));
            prefs.setAlleleFreqThreshold(Float.parseFloat(get(SAMPreferences.ALLELE_THRESHOLD, "0.2f")));
            prefs.setFilterURL(get(SAMPreferences.FILTER_URL, null));
            prefs.setColorBy(get(SAMPreferences.COLOR_BY, null));
        }

        return prefs;
    }

    public void updateSAMPreferences() {
        objectCache.remove(SAMPreferences.class.getName());
    }

    public int getIntegerPreference(String key, int def) {
        try {
            String prefString = get(key, null);
            if (prefString != null) {
                return Integer.parseInt(prefString);
            }
        } catch (Exception e) {
            log.error("Number format error for preference: " + key);

        }
        return def;
    }

    public static class SAMPreferences {

        public static final String MAX_VISIBLE_RANGE = "SAM.MAX_VISIBLE_RANGE";
        public static final String SHOW_ZERO_QUALITY = "SAM.SHOW_VISIBLE_QUALITY";
        public static final String SHOW_DUPLICATES = "SAM.SHOW_DUPLICATES";
        public static final String FLAG_UNMAPPED_PAIR = "SAM.FLAG_UNMAPPED_PAIR";
        public static final String MAX_LEVELS = "SAM.MAX_LEVELS";
        public static final String MAX_READS = "SAM.MAX_READS";
        public static final String ALLELE_THRESHOLD = "SAM.ALLELE_THRESHOLD";
        public static final String QUALITY_THRESHOLD = "SAM.QUALITY_THRESHOLD";
        public static final String INSERT_SIZE_THRESHOLD = "SAM.INSERT_SIZE_THRESHOLD";
        public static final String AUTO_SORT = "SAM.AUTOSORT";
        public static final String SHADE_CENTER = "SAM.SHADE_CENTER";
        public static final String SHOW_REF_SEQ = "SAM.SHOW_REF_SEQ";
        public static final String SHOW_COV_TRACK = "SAM.SHOW_COV_TRACK";
        public static final String SHADE_BASE_QUALITY = "SAM.SHADE_BASE_QUALITY";
        public static final String BASE_QUALITY_MIN = "SAM.BASE_QUALITY_MIN";
        public static final String BASE_QUALITY_MAX = "SAM.BASE_QUALITY_MAX";
        public static final String FILTER_ALIGNMENTS = "SAM.FILTER_ALIGNMENTS";
        public static final String FILTER_URL = "SAM.FILTER_URL";
        public static final String COLOR_BY = "SAM.COLOR_BY";

        private boolean filterAlignments = false;
        private String filterURL;
        private float maxVisibleRange;
        private int maxLevels;
        private int maxReads = 500000;
        private int qualityThreshold;
        private boolean showDuplicates;
        private boolean flagUnmappedPair;
        private boolean autosort;
        private boolean shadeBaseQuality;
        private int insertSizeThreshold;
        private int baseQualityMin = 0;
        private int baseQualityMax = 40;
        private boolean showRefSequenceFlag;
        private boolean showCoverageTrack;
        private boolean shadeCenterFlag;
        private float alleleFreqThreshold = 0.2f;
        private String colorBy = "INSERT_SIZE";

        /**
         * @return the maxVisibleRange
         */
        public float getMaxVisibleRange() {
            return maxVisibleRange;
        }

        /**
         * @param maxVisibleRange the maxVisibleRange to set
         */
        public void setMaxVisibleRange(float maxVisibleRange) {
            this.maxVisibleRange = maxVisibleRange;
        }

        /**
         * @return the showDuplicates
         */
        public boolean isShowDuplicates() {
            return showDuplicates;
        }

        /**
         * @param showDuplicates the showDuplicates to set
         */
        public void setShowDuplicates(boolean showDuplicates) {
            this.showDuplicates = showDuplicates;
        }

        /**
         * @return the flagUnmappedPair
         */
        public boolean isFlagUnmappedPair() {
            return flagUnmappedPair;
        }

        /**
         * @param flagUnmappedPair the flagUnmappedPair to set
         */
        public void setFlagUnmappedPair(boolean flagUnmappedPair) {
            this.flagUnmappedPair = flagUnmappedPair;
        }

        /**
         * @return the autosort
         */
        public boolean isAutosort() {
            return autosort;
        }

        /**
         * @param autosort the autosort to set
         */
        public void setAutosort(boolean autosort) {
            this.autosort = autosort;
        }

        /**
         * @return the maxLevels
         */
        public int getMaxLevels() {
            return maxLevels;
        }

        /**
         * @param maxLevels the maxLevels to set
         */
        public void setMaxLevels(int maxLevels) {
            this.maxLevels = maxLevels;
        }

        /**
         * @return the qualityThreshold
         */
        public int getQualityThreshold() {
            return qualityThreshold;
        }

        /**
         * @param qualityThreshold the qualityThreshold to set
         */
        public void setQualityThreshold(int qualityThreshold) {
            this.qualityThreshold = qualityThreshold;
        }

        public int getInsertSizeThreshold() {
            return insertSizeThreshold;
        }

        public void setInsertSizeThreshold(int insertSizeThreshold) {
            this.insertSizeThreshold = insertSizeThreshold;
        }

        public boolean isShadeBaseQuality() {
            return shadeBaseQuality;
        }

        public void setShadeBaseQuality(boolean shadeBaseQuality) {
            this.shadeBaseQuality = shadeBaseQuality;
        }

        public int getBaseQualityMin() {
            return baseQualityMin;
        }

        public void setBaseQualityMin(int baseQualityMin) {
            this.baseQualityMin = baseQualityMin;
        }

        public int getBaseQualityMax() {
            return baseQualityMax;
        }

        public void setBaseQualityMax(int baseQualityMax) {
            this.baseQualityMax = baseQualityMax;
        }

        /**
         * @return the shadeCenterFlag
         */
        public boolean isShowRefSequence() {
            return showRefSequenceFlag;
        }


        public void setShowRefSequence(boolean flag) {
            this.showRefSequenceFlag = flag;
        }

        /**
         * @return the showCenterline
         */
        public boolean isShadeCenter() {
            return shadeCenterFlag;
        }

        public void setShadeCenter(boolean flag) {
            this.shadeCenterFlag = flag;
        }

        /**
         * @return the showCoverageTrack
         */
        public boolean isShowCoverageTrack() {
            return showCoverageTrack;
        }

        /**
         * @param showCoverageTrack the showCoverageTrack to set
         */
        public void setShowCoverageTrack(boolean showCoverageTrack) {
            this.showCoverageTrack = showCoverageTrack;
        }

        /**
         * @return the filterAlignments
         */
        public boolean isFilterAlignments() {
            return filterAlignments;
        }

        /**
         * @param filterAlignments the filterAlignments to set
         */
        public void setFilterAlignments(boolean filterAlignments) {
            this.filterAlignments = filterAlignments;
        }

        /**
         * @return the filterURL
         */
        public String getFilterURL() {
            return filterURL;
        }

        /**
         * @param filterURL the filterURL to set
         */
        public void setFilterURL(String filterURL) {
            this.filterURL = filterURL;
        }

        public int getMaxReads() {
            return maxReads;
        }

        public void setMaxReads(int maxReads) {
            this.maxReads = maxReads;
        }

        public float getAlleleFreqThreshold() {
            return alleleFreqThreshold;
        }

        public void setAlleleFreqThreshold(float alleleFreqThreshold) {
            this.alleleFreqThreshold = alleleFreqThreshold;
        }

        public String getColorBy() {
            return colorBy;
        }

        public void setColorBy(String colorBy) {
            this.colorBy = colorBy;
        }
    }
}
