/*
 * #%L
 * gitools-ui-app
 * %%
 * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
 * %%
 * 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 3 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, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package org.gitools.ui.app.dialog.filter;

import org.gitools.api.matrix.IMatrixLayer;
import org.gitools.api.matrix.IMatrixLayers;
import org.gitools.api.matrix.MatrixDimensionKey;
import org.gitools.matrix.filter.ValueFilterFunction;
import org.gitools.ui.platform.dialog.MessageStatus;
import org.gitools.ui.platform.wizard.AbstractWizardPage;
import org.gitools.utils.cutoffcmp.CutoffCmp;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.util.List;

/**
 * @noinspection ALL
 */
public class ValueFilterPage extends AbstractWizardPage {
    /**
     * A return status code - returned if Cancel button has been pressed
     */
    private static final int RET_CANCEL = 0;
    /**
     * A return status code - returned if OK button has been pressed
     */
    public static final int RET_OK = 1;
    private final IMatrixLayer visibleLayer;

    private static class ComboBoxCellRenderer extends JComboBox implements TableCellRenderer {

        public ComboBoxCellRenderer(Object[] values) {
            super(values);
        }


        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

            if (isSelected) {
                setForeground(table.getSelectionForeground());
                super.setBackground(table.getSelectionBackground());
            } else {
                setForeground(table.getForeground());
                setBackground(table.getBackground());
            }

            // Select the current value
            setSelectedItem(value);
            return this;
        }
    }

    private static class ComboBoxCellEditor extends DefaultCellEditor {
        public ComboBoxCellEditor(Object[] values) {
            super(new JComboBox(values));
        }
    }

    private final IMatrixLayers layers;
    private final CutoffCmp[] comparators;

    private final ValueFilterCriteriaTableModel criteriaModel;

    /**
     * Creates new form FilterDialog
     */
    public ValueFilterPage(Frame parent,
                           IMatrixLayers layers,
                           MatrixDimensionKey dimension, CutoffCmp[] comparators,
                           List<ValueFilterFunction> initialCriteriaList,
                           IMatrixLayer visibleLayer) {

        this.layers = layers;
        this.comparators = comparators;
        this.visibleLayer = visibleLayer;

        this.criteriaModel = new ValueFilterCriteriaTableModel(layers);

        initComponents();

        if (dimension.equals(MatrixDimensionKey.COLUMNS)) {
            applyToColumnsRb.setSelected(true);
        } else if (dimension.equals(MatrixDimensionKey.ROWS)) {
            applyToRowsRb.setSelected(true);
        }

        table.setModel(criteriaModel);

        criteriaModel.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                tableRemoveBtn.setEnabled(criteriaModel.getList().size() > 0);
                updateFilterDescription();
            }
        });

        if (initialCriteriaList != null) {
            criteriaModel.addAllCriteria(initialCriteriaList);
        }


        CellEditorListener cellEditorListener = new CellEditorListener() {
            /**
             * This tells the listeners the editor has ended editing
             */
            @Override
            public void editingStopped(ChangeEvent e) {
                updateFilterDescription();
            }

            /**
             * This tells the listeners the editor has canceled editing
             */
            @Override
            public void editingCanceled(ChangeEvent e) {
                updateFilterDescription();
            }
        };
        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(0).setCellEditor(new ComboBoxCellEditor(layers.getIds()));
        columnModel.getColumn(0).getCellEditor().addCellEditorListener(cellEditorListener);
        columnModel.getColumn(1).setCellEditor(new ComboBoxCellEditor(comparators));
        columnModel.getColumn(1).getCellEditor().addCellEditorListener(cellEditorListener);
        columnModel.getColumn(2).setCellEditor(new DefaultCellEditor(new JTextField()));
        columnModel.getColumn(2).getCellEditor().addCellEditorListener(cellEditorListener);

        ChangeListener changeListener = new ChangeListener() {
            /**
             * Invoked when the target of the listener has changed its state.
             *
             * @param e a ChangeEvent object
             */
            @Override
            public void stateChanged(ChangeEvent e) {
                updateFilterDescription();
            }
        };
        allCriteriaCheck.addChangeListener(changeListener);
        allElementsCheck.addChangeListener(changeListener);
        hideMatchingCheck.addChangeListener(changeListener);
        applyToRowsAndColumnsRb.addChangeListener(changeListener);
        applyToColumnsRb.addChangeListener(changeListener);
        applyToRowsRb.addChangeListener(changeListener);


        setTitle("Filter by values");
        updateFilterDescription();
    }

    /**
     * @return the return status of this dialog - one of RET_OK or RET_CANCEL
     */
    public int getReturnStatus() {
        return returnStatus;
    }

    private void updateFilterDescription() {

        StringBuilder sb = new StringBuilder();

        sb.append(isHideMatching() ? "<html><b>Hide</b> all " : "<html><b>Keep</b> all ");


        if (!isApplyToRows()) {
            sb.append("columns ");
            allElementsCheck.setText("All values in a column must match");
        } else if (!isApplyToColumns()) {
            sb.append("rows ");
            allElementsCheck.setText("All values in a row must match");
        } else {
            sb.append("rows and columns ");
            allElementsCheck.setText("All values in a row/columns must match");
        }

        sb.append("where ");
        sb.append(isAllElementsMatch() ? "all elements have " : "at least 1 element has ");

        //list all criterias
        sb.append("<ul>");
        for (ValueFilterFunction c : getCriteriaList()) {
            sb.append("<li>");
            sb.append(c.getLayerId());
            sb.append(" ");
            sb.append(c.getComparator().getLongName());
            sb.append(" ");
            sb.append(c.getCutoffValue());
            sb.append(" ");
            if (getCriteriaList().size() - 1 > getCriteriaList().indexOf(c)) {
                sb.append(isAllCriteriaMatch() ? "and " : "or ");
            }
            sb.append("</li>");
        }

        sb.append("</ul></html>");
        setMessage(MessageStatus.WARN, sb.toString());


        updateComplete();
    }

    private void updateComplete() {
        setComplete(getCriteriaList().size() > 0);
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        applyToGroup = new javax.swing.ButtonGroup();
        jScrollPane1 = new javax.swing.JScrollPane();
        table = new javax.swing.JTable();
        tableAddBtn = new javax.swing.JButton();
        tableRemoveBtn = new javax.swing.JButton();
        allCriteriaCheck = new javax.swing.JCheckBox();
        allElementsCheck = new javax.swing.JCheckBox();
        hideMatchingCheck = new javax.swing.JCheckBox();
        loadBtn = new javax.swing.JButton();
        saveBtn = new javax.swing.JButton();
        applyToRowsRb = new javax.swing.JRadioButton();
        jLabel2 = new javax.swing.JLabel();
        applyToRowsAndColumnsRb = new javax.swing.JRadioButton();
        applyToColumnsRb = new javax.swing.JRadioButton();

        table.setModel(new javax.swing.table.DefaultTableModel(
                new Object[][]{

                },
                new String[]{
                        "Value Dimension", "Condition", "Cell Value"
                }
        ) {
            Class[] types = new Class[]{
                    java.lang.String.class, java.lang.Object.class, java.lang.Double.class
            };

            public Class getColumnClass(int columnIndex) {
                return types[columnIndex];
            }
        });
        table.setColumnSelectionAllowed(true);
        jScrollPane1.setViewportView(table);
        table.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION);

        tableAddBtn.setText("Add");
        tableAddBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                tableAddBtnActionPerformed(evt);
            }
        });

        tableRemoveBtn.setText("Remove");
        tableRemoveBtn.setEnabled(false);
        tableRemoveBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                tableRemoveBtnActionPerformed(evt);
            }
        });

        allCriteriaCheck.setText("All of the above criteria should match");

        allElementsCheck.setText("All values should match");

        hideMatchingCheck.setText("Hide matching (Hide unmatching otherwise)");

        loadBtn.setText("Load...");
        loadBtn.setEnabled(false);

        saveBtn.setText("Save...");
        saveBtn.setEnabled(false);

        applyToGroup.add(applyToRowsRb);
        applyToRowsRb.setSelected(true);
        applyToRowsRb.setText("rows");

        jLabel2.setText("Apply to:");

        applyToGroup.add(applyToRowsAndColumnsRb);
        applyToRowsAndColumnsRb.setText("rows and columns");

        applyToGroup.add(applyToColumnsRb);
        applyToColumnsRb.setText("columns");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(layout.createSequentialGroup()
                                .addContainerGap()
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 481, Short.MAX_VALUE)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                                        .addComponent(saveBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                                        .addComponent(loadBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                                        .addComponent(tableAddBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                                        .addComponent(tableRemoveBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                                        .addGroup(layout.createSequentialGroup()
                                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                                        .addComponent(applyToRowsRb)
                                                        .addComponent(applyToColumnsRb)
                                                        .addComponent(applyToRowsAndColumnsRb)
                                                        .addComponent(jLabel2)
                                                        .addComponent(allCriteriaCheck)
                                                        .addComponent(allElementsCheck)
                                                        .addComponent(hideMatchingCheck))
                                                .addGap(0, 0, Short.MAX_VALUE)))
                                .addContainerGap())
        );
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                .addContainerGap()
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                        .addGroup(layout.createSequentialGroup()
                                                .addComponent(tableAddBtn)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(tableRemoveBtn)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(loadBtn)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(saveBtn))
                                        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 177, Short.MAX_VALUE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(allCriteriaCheck)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(allElementsCheck)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(hideMatchingCheck)
                                .addGap(18, 18, 18)
                                .addComponent(jLabel2)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(applyToRowsRb)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(applyToColumnsRb)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(applyToRowsAndColumnsRb)
                                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents


    private void tableAddBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tableAddBtnActionPerformed
        criteriaModel.addCriteria(new ValueFilterFunction(visibleLayer, comparators[0], 0.0, null));
        updateFilterDescription();
    }//GEN-LAST:event_tableAddBtnActionPerformed

    private void tableRemoveBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tableRemoveBtnActionPerformed
        criteriaModel.removeCriteria(table.getSelectedRows());
        updateFilterDescription();
    }//GEN-LAST:event_tableRemoveBtnActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox allCriteriaCheck;
    private javax.swing.JCheckBox allElementsCheck;
    private javax.swing.JRadioButton applyToColumnsRb;
    private javax.swing.ButtonGroup applyToGroup;
    private javax.swing.JRadioButton applyToRowsAndColumnsRb;
    private javax.swing.JRadioButton applyToRowsRb;
    private javax.swing.JCheckBox hideMatchingCheck;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton loadBtn;
    private javax.swing.JButton saveBtn;
    private javax.swing.JTable table;
    private javax.swing.JButton tableAddBtn;
    private javax.swing.JButton tableRemoveBtn;
    // End of variables declaration//GEN-END:variables

    private int returnStatus = RET_CANCEL;

    public boolean isApplyToRows() {
        return applyToRowsRb.isSelected() || applyToRowsAndColumnsRb.isSelected();
    }

    public boolean isApplyToColumns() {
        return applyToColumnsRb.isSelected() || applyToRowsAndColumnsRb.isSelected();
    }

    public boolean isAllCriteriaMatch() {
        return allCriteriaCheck.isSelected();
    }

    public boolean isAllElementsMatch() {
        return allElementsCheck.isSelected();
    }

    public boolean isHideMatching() {
        return hideMatchingCheck.isSelected();
    }

    public List<ValueFilterFunction> getCriteriaList() {
        return criteriaModel.getList();
    }
}
