/*
 * 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.
 */
package org.broad.igv.session;

import org.apache.log4j.Logger;
import org.broad.igv.renderer.ColorScale;
import org.broad.igv.renderer.ColorScaleFactory;
import org.broad.igv.renderer.ContinuousColorScale;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.track.AttributeManager;
import org.broad.igv.track.Track;
import org.broad.igv.track.TrackLoader;
import org.broad.igv.track.TrackType;
import org.broad.igv.ui.IGVMainFrame;
import org.broad.igv.ui.RegionOfInterest;
import org.broad.igv.ui.TrackFilter;
import org.broad.igv.ui.TrackFilterElement;
import org.broad.igv.ui.panel.TrackPanel;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.util.ColorUtilities;
import org.broad.igv.util.FileUtils;
import org.broad.igv.util.FilterElement.BooleanOperator;
import org.broad.igv.util.FilterElement.Operator;
import org.broad.igv.util.IGVHttpUtils;
import org.broad.igv.util.ResourceLocator;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;

/**
 *
 */
public class SessionReader {

    private static Logger log = Logger.getLogger(SessionReader.class);
    private static String INPUT_FILE_KEY = "INPUT_FILE_KEY";
    // Temporary values used in processing

    private Collection<ResourceLocator> dataFiles;
    private Collection<ResourceLocator> missingDataFiles;
    private static Map<String, String> attributeSynonymMap = new HashMap();
    private boolean panelElementPresent = false;
    private int version;


    /**
     * Map of track id -> track.  It is important to maintin the order in which tracks are added, thus
     * the use of LinkedHashMap.
     */
    Map<String, List<Track>> trackDictionary = new LinkedHashMap();


    static {
        attributeSynonymMap.put("DATA FILE", "DATA SET");
        attributeSynonymMap.put("TRACK NAME", "NAME");
    }

    /**
     * Session Element types
     */
    public static enum SessionElement {

        PANEL("Panel"),
        TRACK("Track"),
        COLOR_SCALE("ColorScale"),
        COLOR_SCALES("ColorScales"),
        DATA_TRACK("DataTrack"),
        DATA_TRACKS("DataTracks"),
        FEATURE_TRACKS("FeatureTracks"),
        DATA_FILE("DataFile"),
        RESOURCE("Resource"),
        RESOURCES("Resources"),
        FILES("Files"),
        FILTER_ELEMENT("FilterElement"),
        FILTER("Filter"),
        SESSION("Session"),
        GLOBAL("Global"),
        REGION("Region"),
        REGIONS("Regions"),
        DATA_RANGE("DataRange"),
        PREFERENCES("Preferences"),
        PROPERTY("Property");

        private String name;

        SessionElement(String name) {
            this.name = name;
        }

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

        /**
         * Method description
         *
         * @return
         */
        @Override
        public String toString() {
            return getText();
        }

        /**
         * Method description
         *
         * @param value
         * @return
         */
        static public SessionElement findEnum(String value) {

            if (value == null) {
                return null;
            } else {
                return SessionElement.valueOf(value);
            }
        }
    }

    /**
     * Session Attribute types
     */
    public static enum SessionAttribute {

        BOOLEAN_OPERATOR("booleanOperator"),
        COLOR("color"),
        CHROMOSOME("chromosome"),
        END_INDEX("end"),
        EXPAND("expand"),
        FILTER_MATCH("match"),
        FILTER_SHOW_ALL_TRACKS("showTracks"),
        GENOME("genome"),
        GROUP_TRACKS_BY("groupTracksBy"),
        HEIGHT("height"),
        ID("id"),
        ITEM("item"),
        LOCUS("locus"),
        NAME("name"),
        SAMPLE_ID("sampleID"),
        RESOURCE_TYPE("resourceType"),
        OPERATOR("operator"),
        RELATIVE_PATH("relativePath"),
        RENDERER("renderer"),
        SCALE("scale"),
        START_INDEX("start"),
        VALUE("value"),
        VERSION("version"),
        VISIBLE("visible"),
        WINDOW_FUNCTION("windowFunction"),
        DISPLAY_NAME("displayName"),
        COLOR_SCALE("colorScale"),

        //RESOURCE ATTRIBUTES
        PATH("path"),
        LABEL("label"),
        SERVER_URL("serverURL"),
        HYPERLINK("hyperlink"),
        INFOLINK("infolink"),
        URL("url"),
        FEATURE_URL("featureURL"),
        DESCRIPTION("description"),
        TYPE("type"),
        COVERAGE("coverage"),
        TRACK_LINE("trackLine");

        //TODO Add the following into the Attributes
        /*
        boolean shadeBases;
        boolean shadeCenters;
        boolean flagUnmappedPairs;
        boolean showAllBases;
        int insertSizeThreshold;
        boolean colorByStrand;
        boolean colorByAmpliconStrand;
         */


        private String name;

        SessionAttribute(String name) {
            this.name = name;
        }

        public String getText() {
            return name;
        }

        @Override
        public String toString() {
            return getText();
        }

        static public SessionAttribute findEnum(String value) {

            if (value == null) {
                return null;
            } else {
                return SessionAttribute.valueOf(value);
            }
        }
    }


    /**
     * @param inputStream
     * @param sessionName
     * @return
     * @throws RuntimeException
     */

    public Session loadSession(InputStream inputStream, Session session, String sessionName)
            throws RuntimeException {

        log.debug("Load session");


        Document document = null;
        try {
            document = createDOMDocumentFromXmlFile(inputStream);
        } catch (Exception e) {
            log.error("Session Management Error", e);
            throw new RuntimeException(e);
        }

        HashMap additionalInformation = new HashMap();
        additionalInformation.put(INPUT_FILE_KEY, sessionName);

        NodeList nodes = document.getElementsByTagName(SessionElement.GLOBAL.getText());
        if (nodes == null || nodes.getLength() == 0) {
            nodes = document.getElementsByTagName(SessionElement.SESSION.getText());
        }

        processRootNode(session, nodes.item(0), additionalInformation);

        // Add tracks not explicitly set in file.  It is legal to define sessions with the DataFile section only (no
        // Panel or Track elements).
        addLeftoverTracks(trackDictionary.values());

        if (session.getGroupTracksBy() != null && session.getGroupTracksBy().length() > 0) {
            IGVMainFrame.getInstance().getTrackManager().setGroupByAttribute(session.getGroupTracksBy());
        }

        IGVMainFrame.getInstance().getTrackManager().resetOverlayTracks();

        return session;
    }

    //TODO Check to make sure tracks are not being created twice
    //TODO -- DONT DO THIS FOR NEW SESSIONS

    private void addLeftoverTracks(Collection<List<Track>> tmp) {
        if (version < 3 || !panelElementPresent) {
            for (List<Track> tracks : tmp) {
                for (Track track : tracks) {
                    TrackPanel group = IGVMainFrame.getInstance().getTrackManager().getPanelFor(new ResourceLocator(track.getSourceFile()));
                    group.addTrack(track);
                }
            }
        }
    }

    /**
     * Process a single session element node.
     *
     * @param session
     * @param element
     */
    private void process(Session session, Node element, HashMap additionalInformation) {

        if ((element == null) || (session == null)) {
            return;
        }

        String nodeName = element.getNodeName();
        if (true) {

            if (nodeName.equalsIgnoreCase(SessionElement.GLOBAL.getText()) ||
                    nodeName.equalsIgnoreCase(SessionElement.SESSION.getText())) {
                processGlobal(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.FILES.getText())) {
                processFiles(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.DATA_FILE.getText())) {
                processDataFile(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.RESOURCES.getText())) {
                processResources(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.RESOURCE.getText())) {
                processResource(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.REGIONS.getText())) {
                processRegions(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.REGION.getText())) {
                processRegion(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.FILTER.getText())) {
                processFilter(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.FILTER_ELEMENT.getText())) {
                processFilterElement(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.COLOR_SCALES.getText())) {
                processColorScales(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.COLOR_SCALE.getText())) {
                processColorScale(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.PREFERENCES.getText())) {
                processPreferences(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.PROPERTY.getText())) {
                processPreference(session, (Element) element, additionalInformation);
            } else if (nodeName.equalsIgnoreCase(SessionElement.DATA_TRACKS.getText()) ||
                    nodeName.equalsIgnoreCase(SessionElement.FEATURE_TRACKS.getText()) ||
                    nodeName.equalsIgnoreCase(SessionElement.PANEL.getText())) {
                processPanel(session, (Element) element, additionalInformation);
            }
        }
    }

    private void processRootNode(Session session, Node element, HashMap additionalInformation) {

        if ((element == null) || (session == null)) {
            return;
        }

        String nodeName = element.getNodeName();
        if (!(nodeName.equalsIgnoreCase(SessionElement.GLOBAL.getText()) || nodeName.equalsIgnoreCase(SessionElement.SESSION.getText()))) {
            MessageUtils.showMessage("Session files must begin with a \"Global\" or \"Session\" element.  Found: " + nodeName);
        }
        process(session, element, additionalInformation);
    }

    private void processGlobal(Session session, Element element, HashMap additionalInformation) {

        IGVMainFrame.getInstance().selectGenomeFromList(getAttribute(element, SessionAttribute.GENOME.getText()));
        session.setLocus(getAttribute(element, SessionAttribute.LOCUS.getText()));
        session.setGroupTracksBy(getAttribute(element, SessionAttribute.GROUP_TRACKS_BY.getText()));
        String versionString = getAttribute(element, SessionAttribute.VERSION.getText());
        try {
            version = Integer.parseInt(versionString);
        }
        catch (NumberFormatException e) {
            log.error("Non integer version number in session file: " + versionString);
        }
        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);

        // ViewContext.getInstance().invalidateLocationScale();
    }

    /**
     * Process the Files element.
     * <p/>
     * The RELATIVE_PATH attribute specifies whether file paths are relative
     * or absolute.
     *
     * @param session
     * @param element
     */
    private void processFiles(Session session, Element element, HashMap additionalInformation) {

        dataFiles = new ArrayList();
        missingDataFiles = new ArrayList();
        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);

        if (missingDataFiles.size() > 0) {
            StringBuffer message = new StringBuffer();
            message.append("<html>The following data file(s) could not be located.<ul>");
            for (ResourceLocator file : missingDataFiles) {
                if (file.isLocal()) {
                    message.append("<li>");
                    message.append(file.getPath());
                    message.append("</li>");
                } else {
                    message.append("<li>Server: ");
                    message.append(file.getServerURL());
                    message.append("  Path: ");
                    message.append(file.getPath());
                    message.append("</li>");
                }
            }
            message.append("</ul>");
            message.append("Common reasons for this include: ");
            message.append("<ul><li>The session or data files have been moved.</li> ");
            message.append("<li>The data files are located on a drive that is not currently accessible.</li></ul>");
            message.append("</html>");

            MessageUtils.showMessage(message.toString());
        }
        if (dataFiles.size() > 0) {

            TrackLoader loader = new TrackLoader();
            for (ResourceLocator locator : dataFiles) {
                //TODO Go through the tracks and reassign them to the resourceLocator
                try {
                    List<Track> tracks = loader.load(locator);
                    if (tracks.size() > 0) {
                        for (Track track : tracks) {

                            String id = version < 3 ? track.getId_142() : track.getId();

                            List<Track> trackList = trackDictionary.get(id);
                            if (trackList == null) {
                                trackList = new ArrayList();
                                trackDictionary.put(id, trackList);

                            }
                            trackList.add(track);

                            track.setAttributeValue("DATA FILE", locator.getPath());
                            track.setAttributeValue("DATA TYPE", track.getTrackType().toString());

                        }
                    } else {
                        AttributeManager.getInstance().loadSampleInfo(locator);
                    }
                } catch (Exception e) {
                    log.error("Error loading " + locator.getPath(), e);
                    MessageUtils.showMessage("<html>Error loading: " + locator.getPath() + "<br>" + e.toString());
                }
            }

            // TODO -- sample info file

        }
        dataFiles = null;
    }

    /**
     * @param session
     * @param element
     * @Deprecated // TODO -- this method should not be called.  Delete?
     * @Deprectated -- user processResource
     * Process the data file element.  If relativePaths == true treat the
     * file path as relative to the session file path.  If false
     */
    private void processDataFile(Session session, Element element, HashMap additionalInformation) {

        ResourceLocator resourceLocator = null;
        String serverURL = getAttribute(element, SessionAttribute.SERVER_URL.getText());
        String filePath = getAttribute(element, SessionAttribute.NAME.getText());

        // e.g. DAS
        String resourceType = getAttribute(element, SessionAttribute.RESOURCE_TYPE.getText());

        // If file is local
        if ((serverURL == null || serverURL.trim().equals("")) &&
                !(filePath.startsWith("http:") || filePath.startsWith("https:") ||
                        filePath.startsWith("file:"))) {
            String relPathValue = getAttribute(element, SessionAttribute.RELATIVE_PATH.getText());
            boolean relativePaths = ((relPathValue != null) && relPathValue.equalsIgnoreCase("true"));
            File parent = (relativePaths ? new File(session.getPath()).getParentFile() : null);
            File file = new File(parent, filePath);
            resourceLocator = new ResourceLocator(file.getAbsolutePath());
        } else {    // ...else must be from Server
            resourceLocator = new ResourceLocator(serverURL, filePath);
        }

        if (resourceType != null) {
            resourceLocator.setType(resourceType);
        }

        if (resourceLocator.exists()) {
            dataFiles.add(resourceLocator);
        } else {
            missingDataFiles.add(resourceLocator);
        }

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processResources(Session session, Element element, HashMap additionalInformation) {
        processFiles(session, element, additionalInformation);
    }

    private void processResource(Session session, Element element, HashMap additionalInformation) {

        String label = getAttribute(element, SessionAttribute.LABEL.getText());
        String name = getAttribute(element, SessionAttribute.NAME.getText());
        String sampleId = getAttribute(element, SessionAttribute.SAMPLE_ID.getText());
        String description = getAttribute(element, SessionAttribute.DESCRIPTION.getText());
        String type = getAttribute(element, SessionAttribute.TYPE.getText());
        String coverage = getAttribute(element, SessionAttribute.COVERAGE.getText());
        String trackLine = getAttribute(element, SessionAttribute.TRACK_LINE.getText());
        String colorString = getAttribute(element, SessionAttribute.COLOR.getText());

        String relPathValue = getAttribute(element, SessionAttribute.RELATIVE_PATH.getText());
        boolean relativePaths = ((relPathValue != null) && relPathValue.equalsIgnoreCase("true"));
        String serverURL = getAttribute(element, SessionAttribute.SERVER_URL.getText());
        String path = getAttribute(element, SessionAttribute.PATH.getText());

        ResourceLocator resourceLocator = new ResourceLocator(serverURL, path);
        if (relativePaths) {
            if (FileUtils.isRemote(session.getPath())) {
                int idx = session.getPath().lastIndexOf("/");
                String basePath = session.getPath().substring(0, idx);
                String resPath = basePath + "/" + path;
                resourceLocator = new ResourceLocator(serverURL, resPath);
            } else {
                File parent = (relativePaths ? new File(session.getPath()).getParentFile() : null);
                File file = new File(parent, path);
                resourceLocator = new ResourceLocator(serverURL, file.getAbsolutePath());
            }
        }


        String url = getAttribute(element, SessionAttribute.URL.getText());
        if (url == null) {
            url = getAttribute(element, SessionAttribute.FEATURE_URL.getText());
        }
        resourceLocator.setUrl(url);

        String infolink = getAttribute(element, SessionAttribute.HYPERLINK.getText());
        if (infolink == null) {
            infolink = getAttribute(element, SessionAttribute.INFOLINK.getText());
        }
        resourceLocator.setInfolink(infolink);


        // Label is deprecated in favor of name.
        if (name != null) {
            resourceLocator.setName(name);
        } else {
            resourceLocator.setName(label);
        }

        resourceLocator.setSampleId(sampleId);


        resourceLocator.setDescription(description);
        // This test added to get around earlier bug in the writer
        if (type != null && !type.equals("local")) {
            resourceLocator.setType(type);
        }
        resourceLocator.setCoverage(coverage);
        resourceLocator.setTrackLine(trackLine);

        if (colorString != null) {
            try {
                Color c = ColorUtilities.getColorFromString(colorString);
                resourceLocator.setColor(c);
            } catch (Exception e) {
                log.error("Error setting color: ", e);
            }
        }

        if (resourceLocator.exists()) {
            dataFiles.add(resourceLocator);
        } else {
            missingDataFiles.add(resourceLocator);
        }

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);

    }

    private void processRegions(Session session, Element element, HashMap additionalInformation) {

        session.clearRegionsOfInterest();
        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processRegion(Session session, Element element, HashMap additionalInformation) {

        String chromosome = getAttribute(element, SessionAttribute.CHROMOSOME.getText());
        String start = getAttribute(element, SessionAttribute.START_INDEX.getText());
        String end = getAttribute(element, SessionAttribute.END_INDEX.getText());
        String description = getAttribute(element, SessionAttribute.DESCRIPTION.getText());

        RegionOfInterest region = new RegionOfInterest(chromosome, new Integer(start), new Integer(end), description);
        IGVMainFrame.getInstance().addRegionOfInterest(region);

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processFilter(Session session, Element element, HashMap additionalInformation) {

        String match = getAttribute(element, SessionAttribute.FILTER_MATCH.getText());
        String showAllTracks = getAttribute(element, SessionAttribute.FILTER_SHOW_ALL_TRACKS.getText());

        String filterName = getAttribute(element, SessionAttribute.NAME.getText());
        TrackFilter filter = new TrackFilter(filterName, null);
        additionalInformation.put(SessionElement.FILTER, filter);

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);

        // Save the filter
        session.setFilter(filter);

        // Set filter properties
        if ("all".equalsIgnoreCase(match)) {
            IGVMainFrame.getInstance().setFilterMatchAll(true);
        } else if ("any".equalsIgnoreCase(match)) {
            IGVMainFrame.getInstance().setFilterMatchAll(false);
        }

        if ("true".equalsIgnoreCase(showAllTracks)) {
            IGVMainFrame.getInstance().setFilterShowAllTracks(true);
        } else {
            IGVMainFrame.getInstance().setFilterShowAllTracks(false);
        }
    }

    private void processFilterElement(Session session, Element element,
                                      HashMap additionalInformation) {

        TrackFilter filter = (TrackFilter) additionalInformation.get(SessionElement.FILTER);
        String item = getAttribute(element, SessionAttribute.ITEM.getText());
        String operator = getAttribute(element, SessionAttribute.OPERATOR.getText());
        String value = getAttribute(element, SessionAttribute.VALUE.getText());
        String booleanOperator = getAttribute(element, SessionAttribute.BOOLEAN_OPERATOR.getText());

        TrackFilterElement trackFilterElement = new TrackFilterElement(filter, item,
                Operator.findEnum(operator), value,
                BooleanOperator.findEnum(booleanOperator));
        filter.add(trackFilterElement);

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processPanel(Session session, Element element, HashMap additionalInformation) {
        panelElementPresent = true;
        String nodeName = element.getNodeName();
        String panelName = nodeName;
        int height = 0;
        int width = 0;
        if (nodeName.equalsIgnoreCase(SessionElement.PANEL.getText())) {
            panelName = element.getAttribute("name");
            try {
                height = Integer.valueOf(element.getAttribute("height"));
                width = Integer.valueOf(element.getAttribute("width"));
            } catch (NumberFormatException nfe) {
            }
        }

        List<Track> panelTracks = new ArrayList();
        NodeList elements = element.getChildNodes();
        for (int i = 0; i < elements.getLength(); i++) {
            Node childNode = elements.item(i);
            if (childNode.getNodeName().equalsIgnoreCase(SessionElement.DATA_TRACK.getText()) ||
                    childNode.getNodeName().equalsIgnoreCase(SessionElement.TRACK.getText())) {

                List<Track> tracks = processTrack(session, (Element) childNode, additionalInformation);
                if (tracks != null) {
                    panelTracks.addAll(tracks);
                }
            } else {
                process(session, childNode, additionalInformation);
            }
        }

        TrackPanel panel = IGVMainFrame.getInstance().getDataPanel(panelName);
        if (height == 0) {
            height = panel.getPreferredPanelHeight();
        }
        if (width == 0) {
            width = panel.getViewportWidth();
        }
        panel.setHeight(height);
        panel.addTracks(panelTracks);
    }


    /**
     * Process a track element.  This should return a single track, but could return multiple tracks since the
     * uniqueness of the track id is not enforced.
     *
     * @param session
     * @param element
     * @param additionalInformation
     * @return
     */

    private List<Track> processTrack(Session session, Element element, HashMap additionalInformation) {

        String id = getAttribute(element, SessionAttribute.ID.getText());

        // TODo -- put in utility method, extacts attributes from element **Definitely need to do this
        HashMap<String, String> tAttributes = new HashMap();
        HashMap<String, String> drAttributes = new HashMap();

        NamedNodeMap tNodeMap = element.getAttributes();
        for (int i = 0; i < tNodeMap.getLength(); i++) {
            Node node = tNodeMap.item(i);
            String value = node.getNodeValue();
            if (value != null && value.length() > 0) {
                tAttributes.put(node.getNodeName(), value);
            }
        }


        if (element.hasChildNodes()) {
            Node childNode = element.getFirstChild();
            Node sibNode = childNode.getNextSibling();
            NamedNodeMap drNodeMap = sibNode.getAttributes();

            for (int i = 0; i < drNodeMap.getLength(); i++) {
                Node node = drNodeMap.item(i);
                String value = node.getNodeValue();
                if (value != null && value.length() > 0) {
                    drAttributes.put(node.getNodeName(), value);
                }
            }
        }

        // Get matching tracks.  The trackNameDictionary is used for pre V 2 files, where ID was loosely defined
        List<Track> matchedTracks = trackDictionary.get(id);

        if (matchedTracks == null) {
            log.info("Warning.  No tracks were found with id: " + id + " in session file");
        } else {
            for (final Track track : matchedTracks) {
                track.restorePersistentState(tAttributes);
                DataRange dr = track.getDataRange();
                dr.restorePersistentState(drAttributes);
                track.setDataRange(dr);
            }
            trackDictionary.remove(id);
        }

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);

        return matchedTracks;
    }

    private void processColorScales(Session session, Element element, HashMap additionalInformation) {

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processColorScale(Session session, Element element, HashMap additionalInformation) {

        String trackType = getAttribute(element, SessionAttribute.TYPE.getText());
        String value = getAttribute(element, SessionAttribute.VALUE.getText());

        setColorScaleSet(session, trackType, value);

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processPreferences(Session session, Element element, HashMap additionalInformation) {

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    private void processPreference(Session session, Element element, HashMap additionalInformation) {

        String name = getAttribute(element, SessionAttribute.NAME.getText());
        String value = getAttribute(element, SessionAttribute.VALUE.getText());

        session.setPreference(name, value);

        NodeList elements = element.getChildNodes();
        process(session, elements, additionalInformation);
    }

    /**
     * Process a list of session element nodes.
     *
     * @param session
     * @param elements
     */
    private void process(Session session, NodeList elements, HashMap additionalInformation) {
        for (int i = 0; i < elements.getLength(); i++) {
            Node childNode = elements.item(i);
            process(session, childNode, additionalInformation);
        }
    }


    /**
     * Reads an xml from an input file and creates DOM document.
     *
     * @param
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private Document createDOMDocumentFromXmlFile(InputStream inputStream)
            throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document xmlDocument = documentBuilder.parse(inputStream);
        return xmlDocument;
    }


    public void setColorScaleSet(Session session, String type, String value) {

        if (type == null | value == null) {
            return;
        }

        TrackType trackType = TrackType.OTHER;

        if (TrackType.ALLELE_SPECIFIC_COPY_NUMBER.name().equalsIgnoreCase(type)) {
            trackType = TrackType.ALLELE_SPECIFIC_COPY_NUMBER;
        } else if (TrackType.CHIP.name().equalsIgnoreCase(type)) {
            trackType = TrackType.CHIP;
        } else if (TrackType.COPY_NUMBER.name().equalsIgnoreCase(type)) {
            trackType = TrackType.COPY_NUMBER;
        } else if (TrackType.DNA_METHYLATION.name().equalsIgnoreCase(type)) {
            trackType = TrackType.DNA_METHYLATION;
        } else if (TrackType.OTHER.name().equalsIgnoreCase(type)) {
            trackType = TrackType.OTHER;
        } else if (TrackType.GENE_EXPRESSION.name().equalsIgnoreCase(type)) {
            trackType = TrackType.GENE_EXPRESSION;
        } else if (TrackType.LOH.name().equalsIgnoreCase(type)) {
            trackType = TrackType.LOH;
        } else if (TrackType.MUTATION.name().equalsIgnoreCase(type)) {
            trackType = TrackType.MUTATION;
        } else if (TrackType.PHASTCON.name().equalsIgnoreCase(type)) {
            trackType = TrackType.PHASTCON;
        } else if (TrackType.TILING_ARRAY.name().equalsIgnoreCase(type)) {
            trackType = TrackType.TILING_ARRAY;
        }

        // TODO -- refactor to remove instanceof / cast.  Currently only ContinuousColorScale is handled
        ColorScale colorScale = ColorScaleFactory.getScaleFromString(value);
        if (colorScale instanceof ContinuousColorScale) {
            session.setColorScale(trackType, (ContinuousColorScale) colorScale);
        }

        // ColorScaleFactory.setColorScale(trackType, colorScale);
    }

    private String getAttribute(Element element, String key) {
        String value = element.getAttribute(key);
        if (value != null) {
            if (value.trim().equals("")) {
                value = null;
            }
        }
        return value;
    }


}
