package com.ibm.nmon.gui.analysis;

import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.RowFilter;
import javax.swing.TransferHandler;
import javax.swing.SwingConstants;
import javax.swing.table.TableRowSorter;

import com.ibm.nmon.analysis.AnalysisSet;
import com.ibm.nmon.analysis.AnalysisSetListener;
import com.ibm.nmon.analysis.Statistic;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.gui.GUITable;
import com.ibm.nmon.gui.Styles;
import com.ibm.nmon.gui.dnd.TableTransferHandler;
import com.ibm.nmon.gui.file.AnalysisSetFileChooser;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.gui.table.ChoosableColumnTableModel;
import com.ibm.nmon.gui.table.DoubleCellRenderer;
import com.ibm.nmon.gui.table.IntegerCellRenderer;
import com.ibm.nmon.gui.table.StringCellRenderer;
import com.ibm.nmon.gui.table.TableColumnChooser;
import com.ibm.nmon.gui.util.ScrollingTableFix;
import com.ibm.nmon.interval.Interval;
import com.ibm.nmon.interval.IntervalListener;

/**
 * Holder panel for summary data. Holds a scrolling JTable which supports drag and drop from the tree of parsed files.
 * The table is actually 2 tables, one that shows summary data (min, max, average, std dev) for selected measurements
 * and another that shows only a single statistic. The latter table is meant for easy entry into a spreadsheet for test
 * result tracking but both tables support copying CSV formatted text to the clipboard.
 */
public final class SummaryTablePanel extends JPanel
        implements IntervalListener, AnalysisSetListener, PropertyChangeListener {
    private static final long serialVersionUID = 5581753452007550696L;

    private static final Icon TRANSPOSE_ICON = Styles.buildIcon("arrow_rotate_clockwise.png");

    private final NMONVisualizerGui gui;
    private final JFrame parent;

    private final GUITable dataSetTable;
    private final GUITable statisticsTable;

    private final JPanel statsPanel;

    private final JScrollPane scrollPane;
    private final JMenu menu;

    private final AnalysisSet analysisSet = new AnalysisSet();

    private final AnalysisSetFileChooser fileChooser;

    @SuppressWarnings("unchecked")
    public SummaryTablePanel(NMONVisualizerGui gui, JFrame parent) {
        super();

        this.gui = gui;
        this.parent = parent;

        fileChooser = new AnalysisSetFileChooser(gui, analysisSet);
        menu = new JMenu("Table");
        setupMenu(parent);

        setLayout(new BorderLayout());

        // combo box with various data statistics for use with the results table
        statsPanel = new JPanel();
        setupStatsPanel();

        JPanel top = new JPanel(new BorderLayout());
        setupTopPanel(top);
        add(top, BorderLayout.PAGE_START);

        // each table has its own data model
        dataSetTable = new ByDataSetTable(gui);
        statisticsTable = new GUITable(gui);

        ByDataSetTableModel dataSetTableModel = new ByDataSetTableModel(gui, analysisSet);
        ByStatisticTableModel statTableModel = new ByStatisticTableModel(gui, analysisSet);

        dataSetTable.setModel(dataSetTableModel);
        statisticsTable.setModel(statTableModel);

        setupTable(dataSetTable, parent);
        setupTable(statisticsTable, parent);

        scrollPane = new JScrollPane(statisticsTable);
        scrollPane.getViewport().setBackground(java.awt.Color.WHITE);
        // could be inefficient to have both tables updating when only 1 is visible...
        scrollPane.addComponentListener(new ScrollingTableFix(statisticsTable, scrollPane));
        scrollPane.addComponentListener(new ScrollingTableFix(dataSetTable, scrollPane));
        scrollPane.setBorder(Styles.createBottomLineBorder(this));

        add(scrollPane, BorderLayout.CENTER);

        // alert users they can drag onto the tables
        JLabel label = new JLabel("Click and drag measurements from the tree onto this table");
        label.setFont(Styles.BOLD);
        label.setHorizontalAlignment(SwingConstants.CENTER);

        add(label, BorderLayout.PAGE_END);

        analysisSet.addListener(this);

        // check the count column, if it is 0, do not display
        ((TableRowSorter<ByStatisticTableModel>) statisticsTable.getRowSorter())
                .setRowFilter(new RowFilter<ByStatisticTableModel, Integer>() {
                    @Override
                    public boolean include(RowFilter.Entry<? extends ByStatisticTableModel, ? extends Integer> entry) {
                        ByStatisticTableModel model = ((ByStatisticTableModel) entry.getModel());
                        int idx = model.getColumnIndex(Statistic.COUNT.toString());

                        if (idx == -1) {
                            throw new IllegalStateException(
                                    model + "has no column named " + Statistic.COUNT.toString());
                        }
                        else {
                            Object value = model.getEnabledValueAt(entry.getIdentifier(), idx);
                            int i = (Integer) value;
                            return i != 0;
                        }
                    }
                });
    }

    private void updateTable() {
        if (statsPanel.isVisible()) {
            ((AnalysisSetTableModel) dataSetTable.getModel()).fireTableDataChanged();
        }
        else {
            ByStatisticTableModel model = (ByStatisticTableModel) statisticsTable.getModel();

            model.updateGranularityMax();
            model.fireTableDataChanged();
        }
    }

    private void updateStatisticsComboBox() {
        String newName = Statistic.GRANULARITY_MAXIMUM.getName(gui.getGranularity());

        @SuppressWarnings("unchecked")
        DefaultComboBoxModel<Object> model = (DefaultComboBoxModel<Object>) ((JComboBox<Object>) statsPanel
                .getComponent(1)).getModel();

        boolean reselect = false;

        if (model.getSelectedItem() == model.getElementAt(4)) {
            reselect = true;
        }

        model.removeElementAt(4);
        model.insertElementAt(newName, 4);

        if (reselect) {
            model.setSelectedItem(newName);
        }
    }

    // when the analysis is not enabled, do not update the table
    @Override
    public void setEnabled(boolean enabled) {
        if (enabled) {
            gui.getIntervalManager().addListener(this);
            gui.addPropertyChangeListener("granularity", this);
            gui.addPropertyChangeListener("granularity", (ByStatisticTableModel) statisticsTable.getModel());

            // note update setSaveEnabled() if menu position changes
            gui.getMainFrame().getJMenuBar().add(menu, 3);
            gui.getMainFrame().getJMenuBar().revalidate();

            updateStatisticsComboBox();
            updateTable();
        }
        else {
            gui.getIntervalManager().removeListener(this);
            gui.removePropertyChangeListener("granularity", this);
            gui.removePropertyChangeListener("granularity", (ByStatisticTableModel) statisticsTable.getModel());

            gui.getMainFrame().getJMenuBar().remove(menu);
            gui.getMainFrame().getJMenuBar().revalidate();
        }

        // note there is no need to remove the 2 table models as DataSetListeners and
        // AnalysisSetListeners
        // the only thing that adds data to the analysis set is drag and drop, which is
        // impossible if this panel is not displayed
        // adding or removing a DataSet fires table changes, but the table is not refreshed
        // until it is displayed

        super.setEnabled(enabled);

    }

    // this class is an IntervalListener rather than each of the table models so that only 1 table
    // model can be updated on interval events
    @Override
    public void intervalAdded(Interval interval) {}

    @Override
    public void intervalRemoved(Interval interval) {}

    @Override
    public void intervalsCleared() {}

    @Override
    public void currentIntervalChanged(Interval interval) {
        updateTable();
    }

    @Override
    public void intervalRenamed(Interval interval) {}

    @Override
    public void analysisAdded(DataType type) {
        setSaveEnabled();
    }

    @Override
    public void analysisAdded(DataType type, String field) {
        setSaveEnabled();
    }

    @Override
    public void analysisRemoved(DataType type) {
        setSaveEnabled();
    }

    @Override
    public void analysisRemoved(DataType type, String field) {
        setSaveEnabled();
    }

    @Override
    public void analysisCleared() {
        setSaveEnabled();
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("granularity".equals(evt.getPropertyName())) {
            updateTable();

            updateStatisticsComboBox();
        }
    }

    private void setSaveEnabled() {
        boolean enabled = analysisSet.size() > 0;

        gui.getMainFrame().getJMenuBar().getMenu(3).getItem(1).setEnabled(enabled);
    }

    private void setupTable(final JTable table, JFrame parent) {
        table.setDragEnabled(true);
        table.setTransferHandler(new TableTransferHandler(table, analysisSet));

        table.setDefaultRenderer(Double.class, new DoubleCellRenderer());
        table.setDefaultRenderer(Integer.class, new IntegerCellRenderer());
        table.setDefaultRenderer(String.class, new StringCellRenderer());

        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                mouseReleased(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                // right click only if any rows are selected
                if (e.isPopupTrigger()) {
                    JPopupMenu menu = new JPopupMenu();

                    if ((table.rowAtPoint(e.getPoint()) != -1) && table.getSelectedRowCount() > 0) {
                        JMenuItem item = new JMenuItem("Copy");

                        item.addActionListener(new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent e) {
                                JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;

                                table.getTransferHandler().exportToClipboard(table,
                                        SummaryTablePanel.this.gui.getMainFrame().getToolkit().getSystemClipboard(),
                                        TransferHandler.COPY);
                            }
                        });
                        menu.add(item);

                        item = new JMenuItem("Copy All");
                        item.addActionListener(copyTable);
                        menu.add(item);

                        menu.addSeparator();

                        item = new JMenuItem("Remove");

                        item.addActionListener(new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent e) {
                                Set<String> keys = new java.util.HashSet<String>();

                                for (int row : table.getSelectedRows()) {
                                    keys.add(((AnalysisSetTableModel) table.getModel())
                                            .getKey(table.convertRowIndexToModel(row)));
                                }

                                for (String key : keys) {
                                    analysisSet.removeData(key);
                                }
                            }
                        });

                        menu.add(item);

                        menu.addSeparator();
                    }

                    JMenuItem item = new JMenuItem("Select Columns...");

                    item.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;
                            ChoosableColumnTableModel model = (ChoosableColumnTableModel) table.getModel();

                            new TableColumnChooser(gui, SummaryTablePanel.this.parent, model);
                        }
                    });

                    menu.add(item);

                    menu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });
    }

    private void setupStatsPanel() {
        statsPanel.setVisible(false);

        JLabel label = new JLabel("Statistic:");
        label.setHorizontalAlignment(SwingConstants.TRAILING);
        label.setFont(Styles.LABEL);

        statsPanel.add(label);

        // Object and not Statistic to support displaying the granularity value
        JComboBox<Object> statistic = new JComboBox<Object>();

        DefaultComboBoxModel<Object> model = (DefaultComboBoxModel<Object>) statistic.getModel();
        model.addElement(Statistic.AVERAGE);
        model.addElement(Statistic.WEIGHTED_AVERAGE);
        model.addElement(Statistic.MINIMUM);
        model.addElement(Statistic.MAXIMUM);
        model.addElement(Statistic.GRANULARITY_MAXIMUM.getName(gui.getGranularity()));
        model.addElement(Statistic.STD_DEV);
        model.addElement(Statistic.MEDIAN);
        model.addElement(Statistic.SUM);
        model.addElement(Statistic.COUNT);

        statistic.setSelectedItem(Statistic.AVERAGE);

        statistic.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                @SuppressWarnings("unchecked")
                Object o = ((JComboBox<Object>) e.getSource()).getModel().getSelectedItem();

                if (o.getClass().equals(String.class)) {
                    ((ByDataSetTableModel) dataSetTable.getModel()).setStatistic(Statistic.GRANULARITY_MAXIMUM);
                }
                else {
                    ((ByDataSetTableModel) dataSetTable.getModel()).setStatistic((Statistic) o);
                }
            }
        });

        statsPanel.add(statistic);
    }

    private void setupTopPanel(JPanel top) {
        JButton copy = new JButton("Copy");
        copy.setIcon(Styles.COPY_ICON);
        copy.addActionListener(copyTable);

        JButton clear = new JButton("Clear");
        clear.setIcon(Styles.CLEAR_ICON);
        clear.addActionListener(clearTable);

        // holder so both buttons are grouped on the edge of the panel
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(copy);
        buttonPanel.add(clear);

        // switches between the two tables
        JButton transpose = new JButton("Transpose");
        transpose.setIcon(TRANSPOSE_ICON);

        transpose.addActionListener(transposeTable);

        // use temp panel to keep button from filling the space
        JPanel temp = new JPanel();
        temp.add(transpose);

        // check box, stats combo box and buttons all at the top
        top.add(temp, BorderLayout.LINE_START);
        top.add(statsPanel, BorderLayout.CENTER);
        top.add(buttonPanel, BorderLayout.LINE_END);
    }

    private void setupMenu(JFrame parent) {
        menu.setMnemonic('t');

        JMenuItem item = new JMenuItem("Load Definition...");
        item.setMnemonic('n');
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
        item.addActionListener(loadAnalysis);
        menu.add(item);

        item = new JMenuItem("Save Definition...");
        item.setMnemonic('s');
        item.setIcon(Styles.SAVE_ICON);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
        item.addActionListener(saveAnalysis);
        item.setEnabled(analysisSet.size() > 0);
        menu.add(item);

        menu.addSeparator();

        item = new JMenuItem("Copy All");
        item.setMnemonic('a');
        item.setIcon(Styles.COPY_ICON);
        item.setAccelerator(
                KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
        item.addActionListener(copyTable);
        menu.add(item);

        item = new JMenuItem("Clear");
        item.setMnemonic('c');
        item.setIcon(Styles.CLEAR_ICON);
        item.setAccelerator(
                KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
        item.addActionListener(clearTable);
        menu.add(item);

        menu.addSeparator();

        item = new JMenuItem("Select Columns...");
        item.setMnemonic('m');
        item.setAccelerator(
                KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;

                new TableColumnChooser(gui, SummaryTablePanel.this.parent, ((AnalysisSetTableModel) table.getModel()));
            }
        });
        menu.add(item);

        item = new JMenuItem("Transpose");
        item.setMnemonic('t');
        item.setIcon(TRANSPOSE_ICON);
        item.setAccelerator(
                KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
        item.addActionListener(transposeTable);
        menu.add(item);
    }

    private final ActionListener copyTable = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            JTable table = statsPanel.isVisible() ? dataSetTable : statisticsTable;

            table.selectAll();

            SummaryTablePanel.this.gui.getMainFrame().getToolkit().getSystemClipboard()
                    .setContents(((TableTransferHandler) table.getTransferHandler()).copyAll(), null);
        }
    };

    private final ActionListener clearTable = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            analysisSet.clearData();
        }
    };

    private final ActionListener saveAnalysis = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (analysisSet.size() > 0) {
                fileChooser.save();
            }
        }
    };

    private final ActionListener loadAnalysis = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            fileChooser.load();
        }
    };

    private final Action transposeTable = new AbstractAction() {
        private static final long serialVersionUID = 8993427044737174594L;

        @Override
        public void actionPerformed(ActionEvent e) {
            if (statsPanel.isVisible()) {
                statsPanel.setVisible(false);
                scrollPane.setViewportView(statisticsTable);
                ((AnalysisSetTableModel) statisticsTable.getModel()).fireTableDataChanged();
            }
            else {
                statsPanel.setVisible(true);
                scrollPane.setViewportView(dataSetTable);
                ((AnalysisSetTableModel) dataSetTable.getModel()).fireTableDataChanged();
            }

            SummaryTablePanel.this.requestFocus();
        }
    };
}