/*
 * 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.ui.panel;

import org.apache.log4j.Logger;
import org.broad.igv.exceptions.DataLoadException;
import org.broad.igv.feature.FeatureUtils;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.track.*;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.MessageCollection;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.util.HttpUtils;
import org.broad.igv.util.ResourceLocator;

import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.io.File;
import java.util.*;
import java.util.List;

/**
 * @author jrobinso
 * @date Sep 8, 2010
 */
public class DataPanelContainer extends TrackPanelComponent implements Paintable {

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

    static int lastStateHash = 0;   // TODO -- could synchronization be an issue?

    TrackPanel parent;


    public static void resetStateHash() {
        lastStateHash = 0;
    }


    public DataPanelContainer(TrackPanel trackPanel) {
        super(trackPanel);

        DropTarget target = new DropTarget(this, new FileDropTargetListener(trackPanel));
        setDropTarget(target);
        target.setActive(true);
        this.setLayout(new DataPanelLayout());
        this.parent = trackPanel;
        createDataPanels();

    }

    public void createDataPanels() {
        removeAll();
        for (ReferenceFrame f : FrameManager.getFrames()) {
            if (f.isVisible()) {
                DataPanel dp = new DataPanel(f, this);
                add(dp);
            }
        }
        invalidate();
    }

    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        for (Component c : this.getComponents()) {
            if (c instanceof DataPanel) {
                c.setBackground(color);
            }
        }
    }


    public Collection<TrackGroup> getTrackGroups() {
        TrackPanel dataTrackView = (TrackPanel) getParent();
        return dataTrackView.getGroups();
    }


    public int getVisibleHeight() {
        TrackPanel dataTrackView = (TrackPanel) getParent();
        return dataTrackView.getVisibleRect().height;
    }

    public void setCurrentTool(RegionOfInterestTool regionOfInterestTool) {
        for (Component c : this.getComponents()) {
            if (c instanceof DataPanel) {
                ((DataPanel) c).setCurrentTool(regionOfInterestTool);
            }
        }
    }

    /**
     * Paint to an offscreen graphic, e.g. a graphic for an image or svg file.
     *
     * @param g
     * @param rect
     */
    public void paintOffscreen(Graphics2D g, Rectangle rect) {

        // Get the components of the sort by X position.
        Component[] components = getComponents();
        Arrays.sort(components, new Comparator<Component>() {
            public int compare(Component component, Component component1) {
                return component.getX() - component1.getX();
            }
        });

        for (Component c : this.getComponents()) {

            if (c instanceof DataPanel) {
                Graphics2D g2d = (Graphics2D) g.create();
                Rectangle clipRect = new Rectangle(c.getBounds());
                clipRect.height = rect.height;
                g2d.setClip(clipRect);
                g2d.translate(c.getX(), 0);
                ((DataPanel) c).paintOffscreen(g2d, rect);

            }
        }
        //super.paintBorder(g);
    }

    @Override
    protected void paintChildren(Graphics g) {

        autoscale();

        super.paintChildren(g);
        if (IGV.getInstance().isRulerEnabled()) {
            int start = MouseInfo.getPointerInfo().getLocation().x - getLocationOnScreen().x;
            g.setColor(Color.BLACK);
            g.drawLine(start, 0, start, getHeight());
        }
    }


    private class FileDropTargetListener implements DropTargetListener {

        private TrackPanel panel;

        public FileDropTargetListener(TrackPanel dataPanel) {
            panel = dataPanel;
        }

        public void dragEnter(DropTargetDragEvent event) {

            if (!isDragAcceptable(event)) {
                event.rejectDrag();
                return;
            }
        }

        public void dragExit(DropTargetEvent event) {
        }

        public void dragOver(DropTargetDragEvent event) {
            // you can provide visual feedback here
        }

        public void dropActionChanged(DropTargetDragEvent event) {
            if (!isDragAcceptable(event)) {
                event.rejectDrag();
                return;
            }
        }

        public void drop(DropTargetDropEvent event) {
            if (!isDropAcceptable(event)) {
                event.rejectDrop();
                return;
            }

            event.acceptDrop(DnDConstants.ACTION_COPY);

            Transferable transferable = event.getTransferable();

            MessageCollection messages = new MessageCollection();
            try {
                List<File> files = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);

                for (File file : files) {
                    try {
                        ResourceLocator locator = new ResourceLocator(file.getAbsolutePath());
                        IGV.getInstance().load(locator, panel);
                    } catch (DataLoadException de) {
                        messages.append(de.getMessage());
                    }
                }
                String obj = transferable.getTransferData(DataFlavor.stringFlavor).toString();
                if (HttpUtils.isRemoteURL(obj)) {
                    IGV.getInstance().load(new ResourceLocator(obj), panel);
                }
                if (messages != null && !messages.isEmpty()) {
                    log.error(messages.getFormattedMessage());
                    MessageUtils.showMessage(messages.getFormattedMessage());
                }
            } catch (Exception e) {
                String obj = null;
                try {
                    obj = transferable.getTransferData(DataFlavor.stringFlavor).toString();
                    if (HttpUtils.isRemoteURL(obj)) {
                        IGV.getInstance().load(new ResourceLocator(obj), panel);
                    }
                } catch (Exception e1) {
                    log.error(e1);
                    if (messages != null && !messages.isEmpty()) {
                        MessageUtils.showMessage(messages.getFormattedMessage());
                    }
                }
            }
            IGV.getMainFrame().repaint();
            event.dropComplete(true);
        }

        public boolean isDragAcceptable(DropTargetDragEvent event) { // usually, you
            // check the
            // available
            // data flavors
            // here
            // in this program, we accept all flavors
            return (event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0;
        }

        public boolean isDropAcceptable(DropTargetDropEvent event) { // usually, you
            // check the
            // available
            // data flavors
            // here
            // in this program, we accept all flavors
            return (event.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0;
        }
    }


    private void autoscale() {

        int stateHash = FrameManager.getStateHash();

        //if(lastStateHash == stateHash) return;

        lastStateHash = stateHash;

        final Collection<Track> trackList = IGV.getInstance().getAllTracks();

        Map<String, List<Track>> autoscaleGroups = new HashMap<String, List<Track>>();

        for (Track track : trackList) {

            if(!track.isVisible()) continue;

            String asGroup = track.getAttributeValue(AttributeManager.GROUP_AUTOSCALE);
            if (asGroup != null) {
                if (!autoscaleGroups.containsKey(asGroup)) {
                    autoscaleGroups.put(asGroup, new ArrayList<Track>());
                }
                autoscaleGroups.get(asGroup).add(track);
            } else if (track.getAutoScale()) {
                autoscaleGroup(Arrays.asList(track));
            }


        }

        if (autoscaleGroups.size() > 0) {
            for (List<Track> tracks : autoscaleGroups.values()) {
                autoscaleGroup(tracks);
            }
        }
    }

    private void autoscaleGroup(List<Track> trackList) {


        List<ReferenceFrame> frames =
                FrameManager.isGeneListMode() ? FrameManager.getFrames() :
                        Arrays.asList(FrameManager.getDefaultFrame());


        List<Range> inViewRanges = new ArrayList<Range>();

        synchronized (trackList) {
            for (Track track : trackList) {
                if (track instanceof ScalableTrack) {
                    for (ReferenceFrame frame : frames) {
                        Range range = ((ScalableTrack) track).getInViewRange(frame);
                        if (range != null) {
                            inViewRanges.add(range);
                        }
                    }
                }
            }

            if (inViewRanges.size() > 0) {

                Range inter = computeScale(inViewRanges);

                for (Track track : trackList) {

                    DataRange dr = track.getDataRange();
                    float min = Math.min(0, inter.min);
                    float base = Math.max(min, dr.getBaseline());
                    float max = inter.max;
                    // Pathological case where min ~= max  (no data in view)
                    if (max - min <= (2 * Float.MIN_VALUE)) {
                        max = min + 1;
                    }

                    DataRange newDR = new DataRange(min, base, max, dr.isDrawBaseline());
                    newDR.setType(dr.getType());
                    track.setDataRange(newDR);

                }
            }
        }
    }


    public static Range computeScale(List<Range> ranges) {

        float min = 0;
        float max = 0;

        if (ranges.size() > 0) {
            max = ranges.get(0).max;
            min = ranges.get(0).min;

            for (int i = 1; i < ranges.size(); i++) {

                Range r = ranges.get(i);
                max = Math.max(r.max, max);
                min = Math.min(r.min, min);

            }
        }

        return new Range(min, max);
    }


}
