/*
 * 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.broad.igv.lists.GeneList;
import org.broad.igv.ui.IGV;

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.util.*;

/**
 * The drag & drop code was modified from the excellent example of Bryan E. Smith.
 *
 * @author jrobinso
 * @date Sep 10, 2010
 */
public class HeaderPanelContainer extends JPanel implements Paintable {

    private JPanel contentPanel;

    public HeaderPanelContainer() {
        this.setLayout(new BorderLayout());
        this.setTransferHandler(new DragAndDropTransferHandler());
        this.setDropTarget(new DropTarget(this, new HeaderDropTargetListener(this)));
        createHeaderPanels();
    }


    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        if (contentPanel != null) {
            for (Component c : contentPanel.getComponents()) {
                if (c instanceof HeaderPanel) {
                    c.setBackground(color);
                }
            }
        }
    }

    public void createHeaderPanels() {

        removeAll();
        Collection<ReferenceFrame> frames = FrameManager.getFrames();
        contentPanel = new JPanel();
        contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.X_AXIS)); //    new DataPanelLayout());
        contentPanel.setBackground(new Color(250, 250, 250));
        int hgap = DataPanelContainer.default_hgap;
        if(frames.size() > 10) {
            hgap = 1 + 20 / frames.size();
        }
        boolean first = true;
        for (ReferenceFrame f : frames) {
            if (f.isVisible()) {
                if (!first) {
                    contentPanel.add(Box.createRigidArea(new Dimension(hgap, 0)));
                }
                HeaderPanel dp = new HeaderPanel(f);
                dp.setBackground(getBackground());
                contentPanel.add(dp);
                first = false;
            }
        }
        add(contentPanel, BorderLayout.CENTER);

        // Label for the gene list itself.
        if (FrameManager.isGeneListMode()) {
            GeneList gl = IGV.getInstance().getSession().getCurrentGeneList();
            String name = gl.getDisplayName();
            if(name != null && name.length() > 0) {
                JLabel label = new JLabel(name, JLabel.CENTER);
                Border border = BorderFactory.createLineBorder(Color.lightGray);
                label.setBorder(border);
                add(label, BorderLayout.NORTH);
            }
        }

        invalidate();
    }

//    public void paintOffscreen(Graphics2D g, Rectangle rect, boolean batch) {
//       paint(g);
//    }

    /**
     * Paint to an offscreen graphic, e.g. a graphic for an image or svg file.
     *
     * @param g -- graphics context, translated as neccessary to datapanel origin
     * @param rect  -- Rectangle in which to draw datapanel container
     */
    public void paintOffscreen(Graphics2D g, Rectangle rect, boolean batch) {

        // Get the components of the sort by X position.
        Component[] components = contentPanel.getComponents();
        for (Component c : components) {
            if (c instanceof HeaderPanel) {
                Graphics2D g2d = (Graphics2D) g.create();
                Rectangle panelRect = new Rectangle(0, rect.y, c.getWidth(), rect.height);
                g2d.translate(c.getX(), 0);
                g2d.setClip(panelRect);
                ((HeaderPanel) c).paintOffscreen(g2d, panelRect, batch);

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

    @Override
    public int getSnapshotHeight(boolean batch) {
        return getHeight();
    }
}


/**
 * <p>Listens for drops and performs the updates.</p>
 * <p>The real magic behind the drop!</p>
 */
class HeaderDropTargetListener implements DropTargetListener {

    private final HeaderPanelContainer rootPanel;

    /**
     * <p>Two cursors with which we are primarily interested while dragging:</p>
     * <ul>
     * <li>Cursor for droppable condition</li>
     * <li>Cursor for non-droppable consition</li>
     * </ul>
     * <p>After drop, we manually change the cursor back to default, though does this anyhow -- just to be complete.</p>
     */
    //private static final Cursor droppableCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
    //private static final Cursor notDroppableCursor = Cursor.getDefaultCursor();
    public HeaderDropTargetListener(HeaderPanelContainer sheet) {
        this.rootPanel = sheet;
    }

    // Could easily find uses for these, like cursor changes, etc.

    public void dragEnter(DropTargetDragEvent dtde) {
    }

    public void dragOver(DropTargetDragEvent dtde) {
        //  if (!this.rootPanel.getCursor().equals(droppableCursor)) {
        //      this.rootPanel.setCursor(droppableCursor);
        //  }
    }

    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    public void dragExit(DropTargetEvent dte) {
        //  this.rootPanel.setCursor(notDroppableCursor);
    }

    /**
     * <p>The user drops the item. Performs the drag and drop calculations and layout.</p>
     *
     * @param dtde
     */
    public void drop(DropTargetDropEvent dtde) {

        // Done with cursors, dropping
        //this.rootPanel.setCursor(Cursor.getDefaultCursor());

        // Just going to grab the expected DataFlavor to make sure
        // we know what is being dropped
        DataFlavor dragAndDropPanelFlavor = null;
        Object transferableObj = null;

        try {
            // Grab expected flavor
            dragAndDropPanelFlavor = HeaderPanel.getDragAndDropPanelDataFlavor();

            Transferable transferable = dtde.getTransferable();

            // What does the Transferable support
            if (transferable.isDataFlavorSupported(dragAndDropPanelFlavor)) {
                transferableObj = dtde.getTransferable().getTransferData(dragAndDropPanelFlavor);
            }

        } catch (Exception ex) { /* nope, not the place */ }

        // If didn't find an item, bail
        if (transferableObj == null) {
            return;
        }

        // Cast it to the panel. By this point, we have verified it is a HeaderPanel
        HeaderPanel droppedPanel = (HeaderPanel) transferableObj;
        ReferenceFrame droppedFrame = droppedPanel.frame;

        // Get the y offset from the top of the WorkFlowSheetPanel
        // for the drop option (the cursor on the drop)
        final int dropXLoc = dtde.getLocation().x;


        // Find the index for the drop
        Collection<ReferenceFrame> panels = FrameManager.getFrames();

        java.util.List<ReferenceFrame> orderedPanels = new ArrayList(panels.size());
        panels.remove(droppedFrame);

        boolean dropAdded = false;


        for (ReferenceFrame frame : panels) {
            if (frame.getMidpoint() > dropXLoc && !dropAdded) {
                orderedPanels.add(droppedFrame);
                dropAdded = true;
            }
            orderedPanels.add(frame);
        }
        if (!dropAdded) {
            orderedPanels.add(droppedFrame);
        }


        // Request relayout contents, or else won't update GUI following drop.
        // Will add back in the order to which we just sorted
        FrameManager.setFrames(orderedPanels);
        IGV.getInstance().resetFrames();
    }
} // HeaderDropTargetListener



