/*
 * DiscreteColourScaleDialog.java
 *
 * Copyright (C) 2006-2014 Andrew Rambaut
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package figtree.treeviewer;

import figtree.treeviewer.decorators.*;
import figtree.ui.components.RangeSlider;
import jam.panels.OptionsPanel;

import javax.activation.DataHandler;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DragSource;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

/**
 * @author Andrew Rambaut
 * @version $Id$
 *
 * $HeadURL$
 *
 * $LastChangedBy$
 * $LastChangedDate$
 * $LastChangedRevision$
 */
public class DiscreteColourScaleDialog {
    private static final int SLIDER_RANGE = 1000;

    private static final String HSB_SPECTRUM = "HSB Spectrum";
    private static final String FIXED_COLOURS = "Fixed Colours";

    private JFrame frame;



    private JComboBox colourSchemeCombo = new JComboBox(new String[] { HSB_SPECTRUM, FIXED_COLOURS} );

    CardLayout cardLayout = new CardLayout();
    private final JPanel colourSchemePanel;

    private Map<String, ColourSchemePanel> colourSchemeNamePanelMap = new HashMap<String, ColourSchemePanel>();
    private Map<Class, String> colourSchemeClassNameMap = new HashMap<Class, String>();

//    private DiscreteColourDecorator decorator;

    private java.util.List<Object> discreteValues = null;

      private JTable table;
      private ColourTableModel tableModel;

    private JDialog dialog;

    public DiscreteColourScaleDialog(final JFrame frame) {
        this.frame = frame;

        colourSchemeNamePanelMap.put(HSB_SPECTRUM, new HSBColourSchemePanel());
        colourSchemeNamePanelMap.put(FIXED_COLOURS, new FixedColourSchemePanel());

        colourSchemeClassNameMap.put(HSBDiscreteColourDecorator.class, HSB_SPECTRUM);
        colourSchemeClassNameMap.put(FixedDiscreteColourDecorator.class, FIXED_COLOURS);


        colourSchemePanel = new JPanel(cardLayout);

        for (String name : colourSchemeNamePanelMap.keySet()) {
            ColourSchemePanel panel = colourSchemeNamePanelMap.get(name);
            colourSchemePanel.add(panel.getPanel(), name);
        }

        colourSchemeCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                cardLayout.show(colourSchemePanel, colourSchemeCombo.getSelectedItem().toString());
//                decorator = colourSchemeNamePanelMap.get(colourSchemeCombo.getSelectedItem()).getDecorator();
                tableModel.fireTableDataChanged();
                dialog.pack();
            }
        });


        tableModel = new ColourTableModel();

        table = new JTable(tableModel);
        table.setDefaultRenderer(Color.class, new ColorRenderer(true));
        table.setDefaultRenderer(Paint.class, new ColorRenderer(true));
        table.setDragEnabled(true);
        table.setDropMode(DropMode.INSERT_ROWS);
        table.setTransferHandler(new TableRowTransferHandler(table));

        table.setRowSelectionAllowed(true);
        table.setColumnSelectionAllowed(false);

        table.getColumnModel().getColumn(1).setWidth(80);
        table.getColumnModel().getColumn(1).setMinWidth(80);
        table.getColumnModel().getColumn(1).setMaxWidth(80);
        table.getColumnModel().getColumn(1).setResizable(false);
    }

    public int showDialog() {

        DiscreteColourDecorator currentDecorator = getDecorator();

        final OptionsPanel options = new OptionsPanel(6, 6);

//        options.addSpanningComponent(new JLabel("Editing colour range for attribute: " + decorator.getAttributeName()));

        JScrollPane scrollPane = new JScrollPane(table);
        scrollPane.setMinimumSize(new Dimension(120, 120));
        options.addSpanningComponent(scrollPane);

        options.addComponentWithLabel("Scheme: ", colourSchemeCombo);

        colourSchemePanel.setBorder(BorderFactory.createBevelBorder(1));
        options.addSpanningComponent(colourSchemePanel);

//        setDecorator(currentDecorator);

        JOptionPane optionPane = new JOptionPane(options,
                JOptionPane.QUESTION_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION,
                null,
                null,
                null);
        optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));

        dialog = optionPane.createDialog(frame, "Setup colour range: " + currentDecorator.getAttributeName());

        colourSchemeCombo.setSelectedItem(colourSchemeClassNameMap.get(currentDecorator.getClass()));

        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);

        int result = JOptionPane.CANCEL_OPTION;
        Integer value = (Integer)optionPane.getValue();
        if (value != null && value.intValue() != -1) {
            result = value.intValue();
        }

        return result;
    }

    public void setDecorator(DiscreteColourDecorator decorator) {
//        this.decorator = decorator;

        discreteValues = new ArrayList<Object>(decorator.getValues());

        for (String key : colourSchemeNamePanelMap.keySet()) {
            colourSchemeNamePanelMap.get(key).setDecorator(decorator);
        }
    }

    public DiscreteColourDecorator getDecorator() {
        String name = colourSchemeCombo.getSelectedItem().toString();
        DiscreteColourDecorator decorator = colourSchemeNamePanelMap.get(name).getDecorator();
        decorator.setValuesOrder(discreteValues);
        return decorator;
    }



    interface Reorderable {
        public void reorder(java.util.List<Integer> sourceIndices, int destinationIndex);
    };

    class ColourTableModel extends DefaultTableModel implements Reorderable {
        private final String[] COLUMN_NAMES = { "Value", "Colour" };

        @Override
        public int getRowCount() {
            if (discreteValues == null) return 0;
            return discreteValues.size();
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int column) {
            return COLUMN_NAMES[column];
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }

        @Override
        public Object getValueAt(int row, int column) {
            switch (column) {
                case 0:
                    return discreteValues.get(row);
                case 1:
                    DiscreteColourDecorator decorator = getDecorator();
                    // until the OK button is pressed the trait values are not actually
                    // reordered so use the default colour order.
                    return decorator.getColor(decorator.getValues().get(row));
            }
            return null;
        }

        @Override
        public Class<?> getColumnClass(int column) {
            if (column == 1) {
                return Color.class;
            }
            return String.class;
        }

        @Override
        public void setValueAt(Object o, int row, int column) {
        }

        @Override
        public void reorder(java.util.List<Integer> sourceIndices, int destinationIndex) {
            java.util.List<Object> itemsToMove = new ArrayList<Object>();
            for (int src : sourceIndices) {
                itemsToMove.add(discreteValues.get(src));
            }
            for (int i = sourceIndices.size() - 1; i >= 0; i--) {
                int src = sourceIndices.get(i);
                discreteValues.remove(src);
                if (destinationIndex > src) {
                    destinationIndex -= 1;
                }
            }
            discreteValues.addAll(destinationIndex, itemsToMove);
            fireTableDataChanged();
        }
    }

    public class ColorRenderer extends JLabel
            implements TableCellRenderer {
        Border unselectedBorder = null;
        Border selectedBorder = null;
        boolean isBordered = true;

        public ColorRenderer(boolean isBordered) {
            this.isBordered = isBordered;
            setOpaque(true); //MUST do this for background to show up.
        }

        public Component getTableCellRendererComponent(
                JTable table, Object color,
                boolean isSelected, boolean hasFocus,
                int row, int column) {
            Color newColor = (Color)color;
            setBackground(newColor);
            if (isBordered) {
                if (isSelected) {
                    if (selectedBorder == null) {
                        selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
                                table.getSelectionBackground());
                    }
                    setBorder(selectedBorder);
                } else {
                    if (unselectedBorder == null) {
                        unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
                                table.getBackground());
                    }
                    setBorder(unselectedBorder);
                }
            }

            setToolTipText("RGB value: " + newColor.getRed() + ", "
                    + newColor.getGreen() + ", "
                    + newColor.getBlue());
            return this;
        }
    }

    /**
     * Handles drag & drop row reordering
     */
    public class TableRowTransferHandler extends TransferHandler {
        //        private final DataFlavor localObjectFlavor = new ActivationDataFlavor(Integer.class, DataFlavor.javaJVMLocalObjectMimeType, "Integer Row Index");
        private final DataFlavor localObjectFlavor = new DataFlavor(ArrayList.class, "Integer Row Index");
        private JTable table = null;

        public TableRowTransferHandler(JTable table) {
            this.table = table;
        }

        @Override
        protected Transferable createTransferable(JComponent c) {
            assert (c == table);
            java.util.List<Integer> selectedRows = new ArrayList<Integer>();
            for (int row : table.getSelectedRows()) {
                selectedRows.add(row);
            }

            return new DataHandler(selectedRows, localObjectFlavor.getMimeType());
        }

        @Override
        public boolean canImport(TransferHandler.TransferSupport info) {
            boolean b = info.getComponent() == table && info.isDrop() && info.isDataFlavorSupported(localObjectFlavor);
            table.setCursor(b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop);
            return b;
        }

        @Override
        public int getSourceActions(JComponent c) {
            return TransferHandler.MOVE;
        }

        @Override
        public boolean importData(TransferHandler.TransferSupport info) {
            JTable target = (JTable) info.getComponent();
            JTable.DropLocation dl = (JTable.DropLocation) info.getDropLocation();
            int index = dl.getRow();
            int max = table.getModel().getRowCount();
            if (index < 0 || index > max)
                index = max;
            target.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            try {
                java.util.List<Integer> selectedRows = (java.util.List)info.getTransferable().getTransferData(localObjectFlavor);
                if (selectedRows != null && selectedRows.size() > 0) {
                    ((Reorderable)table.getModel()).reorder(selectedRows, index);
                    for (int row : selectedRows) {
                    if (index > row)
                        index--;
                    }
                    target.getSelectionModel().addSelectionInterval(index, index + selectedRows.size() - 1);
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        protected void exportDone(JComponent c, Transferable t, int act) {
            if (act == TransferHandler.MOVE) {
                table.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }

    }

    private interface ColourSchemePanel {
        void setDecorator(DiscreteColourDecorator decorator);
        DiscreteColourDecorator getDecorator();

        JPanel getPanel();


        String getName();
    }

    private class HSBColourSchemePanel implements ColourSchemePanel  {
        public HSBColourSchemePanel() {
            hueSlider = new RangeSlider(0, SLIDER_RANGE);
            saturationSlider = new RangeSlider(0, SLIDER_RANGE);
            brightnessSlider = new RangeSlider(0, SLIDER_RANGE);
        }

        public void setDecorator(DiscreteColourDecorator decorator) {
            if (decorator instanceof HSBDiscreteColourDecorator) {
                hsbDecorator = (HSBDiscreteColourDecorator)decorator;
            } else {
                if (hsbDecorator == null) {
                    hsbDecorator = new HSBDiscreteColourDecorator(decorator.getAttributeName());
                }
            }

            settingValues = true;

            secondaryCountSpinnerModel.setValue(hsbDecorator.getSecondaryCount());

            hueSlider.setValue((int)(hsbDecorator.getHueLower() * SLIDER_RANGE));
            hueSlider.setUpperValue((int) (hsbDecorator.getHueUpper() * SLIDER_RANGE));

            saturationSlider.setValue((int)(hsbDecorator.getSaturationLower() * SLIDER_RANGE));
            saturationSlider.setUpperValue((int)(hsbDecorator.getSaturationUpper() * SLIDER_RANGE));

            brightnessSlider.setValue((int)(hsbDecorator.getBrightnessLower() * SLIDER_RANGE));
            brightnessSlider.setUpperValue((int)(hsbDecorator.getBrightnessUpper() * SLIDER_RANGE));

            settingValues = false;

            getDecorator();
            tableModel.fireTableDataChanged();
        }

        @Override
        public  DiscreteColourDecorator getDecorator() {
            hsbDecorator.setSecondaryCount(secondaryCountSpinnerModel.getNumber().intValue());

            hsbDecorator.setHueLower(((float) hueSlider.getValue()) / SLIDER_RANGE);
            hsbDecorator.setHueUpper(((float) hueSlider.getUpperValue()) / SLIDER_RANGE);

            hsbDecorator.setSaturationLower(((float) saturationSlider.getValue()) / SLIDER_RANGE);
            hsbDecorator.setSaturationUpper(((float) saturationSlider.getUpperValue()) / SLIDER_RANGE);

            hsbDecorator.setBrightnessLower(((float) brightnessSlider.getValue()) / SLIDER_RANGE);
            hsbDecorator.setBrightnessUpper(((float) brightnessSlider.getUpperValue()) / SLIDER_RANGE);

            return hsbDecorator;
        }

        private boolean settingValues = false;

        @Override
        public JPanel getPanel() {
            if (panel == null) {
                final OptionsPanel options = new OptionsPanel(6, 6);

                options.addComponentWithLabel("Primary: ", primaryAxisCombo);
                options.addComponentWithLabel("Secondary count: ", secondaryCountSpinner);

                options.addComponentWithLabel("Hue: ", hueSlider);
                options.addComponentWithLabel("Saturation: ", saturationSlider);
                options.addComponentWithLabel("Brightness: ", brightnessSlider);

                primaryAxisCombo.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        getDecorator();
                        tableModel.fireTableDataChanged();
                    }
                });

                ChangeListener listener = new ChangeListener() {
                    public void stateChanged(ChangeEvent e) {
                        if (!settingValues) {
                            getDecorator();
                            tableModel.fireTableDataChanged();
                        }
                    }
                };

                secondaryCountSpinner.addChangeListener(listener);
                hueSlider.addChangeListener(listener);
                saturationSlider.addChangeListener(listener);
                brightnessSlider.addChangeListener(listener);

                panel = options;
            }
            return panel;
        }

        @Override
        public String getName() {
            return HSB_SPECTRUM;
        }

        private JComboBox primaryAxisCombo = new JComboBox(HSBDiscreteColourDecorator.Axis.values());
        private SpinnerNumberModel secondaryCountSpinnerModel = new SpinnerNumberModel(2, 1, 100, 1);
        private JSpinner secondaryCountSpinner = new JSpinner(secondaryCountSpinnerModel);

        private RangeSlider hueSlider;
        private RangeSlider saturationSlider;
        private RangeSlider brightnessSlider;

        private HSBDiscreteColourDecorator hsbDecorator = null;

        private JPanel panel = null;
    }

    private class FixedColourSchemePanel implements ColourSchemePanel  {
        public FixedColourSchemePanel() {
        }

        public void setDecorator(DiscreteColourDecorator decorator) {
            if (decorator instanceof FixedDiscreteColourDecorator) {
                fixedDecorator = (FixedDiscreteColourDecorator)decorator;
            } else {
                if (fixedDecorator == null) {
                    fixedDecorator = new FixedDiscreteColourDecorator(decorator.getAttributeName());
                }
            }
            fixedDecorator.setValues(discreteValues);
        }

        @Override
        public  DiscreteColourDecorator getDecorator() {
            return fixedDecorator;
        }

        @Override
        public JPanel getPanel() {
            if (panel == null) {
                final OptionsPanel options = new OptionsPanel(6, 6);

                panel = options;
            }
            return panel;
        }

        @Override
        public String getName() {
            return FIXED_COLOURS;
        }

        private FixedDiscreteColourDecorator fixedDecorator = null;

        private JPanel panel = null;
    }

}