/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2015 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


package org.broad.igv.track;


import org.apache.log4j.Logger;
import org.broad.igv.Globals;
import org.broad.igv.data.CombinedDataSource;
import org.broad.igv.data.CoverageDataSource;
import org.broad.igv.data.DataSource;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.session.IGVSessionReader;
import org.broad.igv.session.SubtlyImportant;
import org.broad.igv.util.ResourceLocator;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


/**
 * @author jrobinso
 */
@XmlType(factoryMethod = "getNextTrack")
@XmlSeeAlso({CombinedDataSource.class})
public class DataSourceTrack extends DataTrack {

    private static Logger log = Logger.getLogger(DataSourceTrack.class);

    //All tracks have label "Track", we need to specify the type sometimes
    //but still preserve backwards compatibility
    @XmlAttribute
    protected Class clazz = DataSourceTrack.class;

    private DataSource dataSource;

    /**
     * We often don't have data when the track is constructed,
     * need to rescale when data is first loaded
     */
    private boolean firstDataLoaded = false;

    private boolean rescaleOnFirst = false;

    private static final String COMBINED_DATA_SOURCE = "COMBINED_DATA_SOURCE";

    @SubtlyImportant
    private DataSourceTrack(){
        super(null, null, null);
    }

    public DataSourceTrack(ResourceLocator locator, String id, String name, DataSource dataSource) {
        super(locator, id, name);

        this.dataSource = dataSource;

        if(this.dataSource != null){
            setTrackType(dataSource.getTrackType());
            List<LocusScore> scores = this.dataSource.getSummaryScoresForRange(Globals.CHR_ALL, -1, -1, 0);

            if(scores.size() == 0) rescaleOnFirst = true;

            initScale(dataSource, scores);
        }
    }

    void initScale(DataSource dataSource, List<LocusScore> scores){

        float min = (float) dataSource.getDataMin();
        float max = (float) dataSource.getDataMax();
        float baseline = 0;

        // If the range is all + numbers set the min to zero
        if (min > 0) {
            min = 0;
        }
        for (LocusScore score : scores) {
            max = Math.max(max, score.getScore());
        }

        setDataRange(new DataRange(min, baseline, max));
    }

    public LoadedDataInterval<List<LocusScore>> getSummaryScores(String chr, int startLocation, int endLocation, int zoom) {

        List<LocusScore> tmp = dataSource.getSummaryScoresForRange(chr, startLocation, endLocation, zoom);

        if (tmp == null) tmp = Collections.EMPTY_LIST;
        if(!firstDataLoaded && rescaleOnFirst){
            initScale(dataSource, tmp);
            firstDataLoaded = true;
        }


        return new LoadedDataInterval<>(chr, startLocation, endLocation, zoom, tmp);
    }


    @Override
    public void setWindowFunction(WindowFunction statType) {
        clearCaches();
        if (dataSource != null) {
            dataSource.setWindowFunction(statType);
        }
    }

    public boolean isLogNormalized() {
        return dataSource.isLogNormalized();
    }


    public WindowFunction getWindowFunction() {
        //For JAXB session loading/unloading, dataSource might be null, and we need to guard against that
        return dataSource != null ? dataSource.getWindowFunction() : null;
    }


    @Override
    public Collection<WindowFunction> getAvailableWindowFunctions() {
        return dataSource != null ? dataSource.getAvailableWindowFunctions() : null;
    }

    @Override
    public void restorePersistentState(Node node, int version) throws JAXBException {
        super.restorePersistentState(node, version);
        if (node.hasChildNodes()) {
            NodeList childNodes = node.getChildNodes();
            for (int ii = 0; ii < childNodes.getLength(); ii++) {
                Node child = childNodes.item(ii);
                String nodeName = child.getNodeName();
                if (nodeName.contains("#text")) continue;

                if (nodeName.equalsIgnoreCase(COMBINED_DATA_SOURCE)) {
                    dataSource = IGVSessionReader.getJAXBContext().createUnmarshaller().unmarshal(child, CombinedDataSource.class).getValue();
                }
            }
        }
    }

    public void marshalSource(Marshaller m, Element trackElement) throws JAXBException {
        if (dataSource == null) return;
        DataSource rawSource = dataSource;

        if(rawSource instanceof CombinedDataSource){
            JAXBElement element = new JAXBElement<CombinedDataSource>(new QName("", COMBINED_DATA_SOURCE), CombinedDataSource.class,
                    (CombinedDataSource) rawSource);
            m.marshal(element, trackElement);
        }
    }

    public void updateTrackReferences(List<Track> allTracks) {
        if (dataSource instanceof CombinedDataSource) {
            ((CombinedDataSource) dataSource).updateTrackReferences(allTracks);
        }
    }

    @Override
    public void dispose() {
        super.dispose();
        if(dataSource != null) {
            dataSource.dispose();
            dataSource = null;
        }
    }

    @SubtlyImportant
    @XmlAttribute
    private void setNormalize(boolean normalize){
        if (dataSource != null && dataSource instanceof CoverageDataSource) {
            ((CoverageDataSource) dataSource).setNormalize(normalize);
        }
    }

    @SubtlyImportant
    private boolean getNormalize(){
        if (dataSource != null && dataSource instanceof CoverageDataSource) {
            return ((CoverageDataSource) dataSource).getNormalize();
        }
        return false;
    }

    @SubtlyImportant
    private static DataSourceTrack getNextTrack(){
        DataSourceTrack out = (DataSourceTrack) IGVSessionReader.getNextTrack();
        if (out == null){
            out = new DataSourceTrack();
        }
        return out;
    }
}
