/*
 * #%L
 * org.gitools.ui.app
 * %%
 * Copyright (C) 2013 - 2014 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.analysis.groupcomparison.wizard;

import org.apache.commons.lang.ArrayUtils;
import org.gitools.analysis.clustering.ClusteringData;
import org.gitools.analysis.clustering.annotations.AnnPatClusteringData;
import org.gitools.analysis.clustering.annotations.AnnPatClusteringMethod;
import org.gitools.analysis.groupcomparison.dimensiongroups.*;
import org.gitools.analysis.groupcomparison.filters.GroupByLabelPredicate;
import org.gitools.analysis.groupcomparison.filters.GroupByValuePredicate;
import org.gitools.api.analysis.Clusters;
import org.gitools.api.matrix.IMatrixLayer;
import org.gitools.api.matrix.IMatrixLayers;
import org.gitools.api.matrix.IMatrixPredicate;
import org.gitools.heatmap.Heatmap;
import org.gitools.heatmap.HeatmapDimension;
import org.gitools.matrix.filter.DataIntegrationCriteria;
import org.gitools.matrix.filter.MatrixPredicates;
import org.gitools.matrix.filter.PatternFunction;
import org.gitools.ui.app.wizard.add.data.DataIntegrationCriteriaDialog;
import org.gitools.ui.core.Application;
import org.gitools.ui.core.pages.common.PatternSourcePage;
import org.gitools.ui.platform.IconUtils;
import org.gitools.ui.platform.dialog.MessageStatus;
import org.gitools.ui.platform.icons.IconNames;
import org.gitools.ui.platform.wizard.AbstractWizardPage;
import org.gitools.ui.platform.wizard.PageDialog;
import org.gitools.utils.cutoffcmp.CutoffCmp;
import org.gitools.utils.operators.Operator;
import org.gitools.utils.progressmonitor.DefaultProgressMonitor;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.TableColumnModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;


public class GroupComparisonGroupingPage extends AbstractWizardPage {


    private JPanel panel1;
    private JComboBox layerCb;
    private JComboBox dimensionCb;
    private JButton addButton;
    private JButton removeButton;
    private JTable groupsTable;
    private JLabel dataLabel;
    private JButton mergeButton;
    private JButton splitButton;
    private JRadioButton annotationRadioButton;
    private JRadioButton valueRadioButton;
    private JRadioButton noConstraintRadioButton;
    private JRadioButton nullConversionRadioButton;
    private JRadioButton nullDiscardRadioButton;
    private JTextField nullConversionTextArea;
    private JCheckBox copyHeadersCheckBox;


    private DimensionGroupTableModel tableModel = new DimensionGroupTableModel();
    private List<DimensionGroup> removedItems = new ArrayList<DimensionGroup>();
    private Heatmap heatmap;
    private DimensionGroupEnum groupingType;
    private String groupingPattern;

    private static int MIN_GROUP_SIZE = 3;


    public GroupComparisonGroupingPage(Heatmap heatmap, DimensionGroupEnum groupingType) {
        super();

        this.heatmap = heatmap;
        this.groupingType = groupingType;

        setLogo(IconUtils.getImageIconResourceScaledByHeight(IconNames.LOGO_METHOD, 96));

        layerCb.setModel(new DefaultComboBoxModel(heatmap.getLayers().getIds()));
        layerCb.setSelectedItem(heatmap.getLayers().getTopLayer().getId());

        groupsTable.setModel(tableModel);

        setTitle("Group selection");
        TableColumnModel columnModel = groupsTable.getColumnModel();
        columnModel.getColumn(2).setPreferredWidth(50);
        columnModel.getColumn(2).setCellEditor(new SpinnerCellEditor(new SpinnerNumberModel()));
        columnModel.getColumn(2).getCellEditor().addCellEditorListener(new CellEditorListener() {
            @Override
            public void editingStopped(ChangeEvent e) {
                tableModel.fireTableDataChanged();
            }

            @Override
            public void editingCanceled(ChangeEvent e) {
                tableModel.fireTableDataChanged();
            }
        });
        groupsTable.setRowHeight(25);

        groupsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                updateControls();
            }
        });


        removeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                removeSelected();
            }
        });

        addButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (getSelectedGroupingType().equals(DimensionGroupEnum.Annotation)) {
                    //TODO: create Dialog with removedItems
                } else if (getSelectedGroupingType().equals(DimensionGroupEnum.Free)) {
                    createFreeGroup();
                } else if (getSelectedGroupingType().equals(DimensionGroupEnum.Value)) {
                    createValueGroup();
                }
                updateControls();
            }
        });

        dimensionCb.setModel(new DefaultComboBoxModel(new String[]{"Columns", "Rows"}));


        mergeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                performMerge();
            }
        });
        splitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                performSplit();
            }
        });
        dimensionCb.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                initGroups();
            }
        });

        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                initGroups();
            }
        };
        annotationRadioButton.addActionListener(listener);
        valueRadioButton.addActionListener(listener);
        noConstraintRadioButton.addActionListener(listener);

        ActionListener nullConversionListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateControls();
            }
        };
        nullDiscardRadioButton.addActionListener(nullConversionListener);
        nullConversionRadioButton.addActionListener(nullConversionListener);

        nullConversionTextArea.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                updateControls();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                updateControls();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                updateControls();
            }
        });


        updateControls();

        layerCb.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateControls();
            }
        });
    }

    private void updateNullConversion() {
        nullConversionTextArea.setEnabled(nullConversionRadioButton.isSelected());
    }


    private void updateButtons() {
        if (getSelectedGroupingType().equals(DimensionGroupEnum.Annotation)) {
            mergeButton.setVisible(true);

            if (groupsTable.getSelectedRowCount() > 0) {
                //SPLIT button
                int groupNumber = -1;
                boolean enableSplit = true;

                // all selected rows are same group?
                for (int i : groupsTable.getSelectedRows()) {
                    if (groupNumber < 0) {
                        groupNumber = (int) tableModel.getValueAt(i, 2);
                    } else if (groupNumber != (int) tableModel.getValueAt(i, 2)) {
                        enableSplit = false;
                        break;
                    }
                }

                // unselected of same group?
                if (groupsTable.getSelectedRowCount() == 1) {
                    enableSplit = false;
                    for (int i = 0; i < groupsTable.getRowCount(); i++) {
                        if (groupsTable.isRowSelected(i)) {
                            continue;
                        }

                        if (groupNumber == (int) tableModel.getValueAt(i, 2)) {
                            enableSplit = true;
                            break;
                        }
                    }
                }
                //MERGE button
                mergeButton.setEnabled(!enableSplit && groupsTable.getSelectedRowCount() > 1);
            }
        } else {
            mergeButton.setVisible(false);
        }

        splitButton.setEnabled(false);
        splitButton.setVisible(false);


        //ADD button
        boolean enableAdd = true;
        if (getSelectedGroupingType().equals(DimensionGroupEnum.Annotation)) {
            enableAdd = !removedItems.isEmpty();
        }
        addButton.setEnabled(enableAdd);

        //Remove button
        removeButton.setEnabled(groupsTable.getSelectedRowCount() > 0);

    }

    @Override
    public void updateControls() {
        boolean isComplete = false;

        updateNullConversion();
        updateButtons();
        updateGroupingType();


        // Are there at least two groups?
        isComplete = tableModel.getRowCount() > 1 & layerCb.getSelectedItem() != null;

        if (!isComplete) {
            setMessage(MessageStatus.INFO, "Create at least 2 groups to compare and select data layer for group comparison");
            setComplete(false);
            return;
        } else {
            setMessage(MessageStatus.INFO, "Click next to proceed.");
        }

        // Do all groups have at least 3 columns (if applicable)
        if (annotationRadioButton.isSelected() | noConstraintRadioButton.isSelected()) {
            for (DimensionGroup g : tableModel.getGroupList()) {
                if (g.getGroupSize() < MIN_GROUP_SIZE) {
                    setMessage(MessageStatus.ERROR, "Groups must contain 3 or more items. Remove or merge groups" +
                            "that are too small.");
                    setComplete(false);
                    return;
                }
            }
        }

        // Valid Null Conversion?
        try {
            Double.valueOf(nullConversionTextArea.getText());
        } catch (Exception e) {
            setMessage(MessageStatus.ERROR, "\" " + nullConversionTextArea.getText() + "\" can is not a numeric value");
            setComplete(false);
            return;
        }
        setComplete(isComplete);
    }

    private void performSplit() {

        int currentGroupNumber = (int) tableModel.getValueAt(groupsTable.getSelectedRows()[0], 2);
        int occurrences = 0;

        for (int i = 0; i < groupsTable.getRowCount(); i++) {
            if (currentGroupNumber == (int) tableModel.getValueAt(i, 2)) {
                occurrences++;
            }
        }

        boolean totalSplit = occurrences == groupsTable.getSelectedRowCount();
        int newGroups = 1;

        for (int i = 0; i < groupsTable.getRowCount(); i++) {
            int g = (int) tableModel.getValueAt(i, 2);
            if (g > currentGroupNumber | groupsTable.isRowSelected(i)) {
                groupsTable.setValueAt(g + newGroups, i, 2);
                newGroups = totalSplit && groupsTable.isRowSelected(i) ? ++newGroups : newGroups;
            }
        }
        tableModel.fireTableDataChanged();
    }

    private void performMerge() {

        int[] selection = groupsTable.getSelectedRows();
        List<IMatrixPredicate> predicateList = new ArrayList<>();
        StringBuilder groupName = new StringBuilder("");
        StringBuilder groupProperty = new StringBuilder("");
        int size = 0;
        for (int i : selection) {
            DimensionGroup group = tableModel.getGroupAt(i);
            if (!groupName.toString().equals("")) {
                groupName.append(" + ");
                groupProperty.append(" + ");
            }
            groupName.append(group.getName());
            groupProperty.append(group.getProperty());
            predicateList.add(group.getPredicate());
            size += group.getGroupSize();
        }

        MatrixPredicates.OrPredicate newpredicate = new MatrixPredicates.OrPredicate(predicateList);
        tableModel.setGroup(new DimensionGroup(groupName.toString(),
                newpredicate,
                DimensionGroupEnum.Annotation,
                groupProperty.toString(), size),
                selection[0]);
        tableModel.removeGroups(ArrayUtils.removeElement(selection, selection[0]));
        tableModel.fireTableDataChanged();
        updateControls();
    }

    private void initGroups() {

        DimensionGroup[] newGroups = new DimensionGroup[0];

        if (getSelectedGroupingType().equals(DimensionGroupEnum.Annotation)) {
            newGroups = initAnnotationGroups();
            if (newGroups == null) {
                setSelectedGroupingType(groupingType);
                return;
            }
        }

        removedItems.clear();
        tableModel.setGroups(newGroups);
        updateControls();
    }

    public boolean isCopyHeatmapHeaders() {
        return copyHeadersCheckBox.isSelected();
    }

    private DimensionGroup[] initAnnotationGroups() {
        DimensionGroup[] newGroups;
        HeatmapDimension hdim = dimensionCb.getSelectedItem().equals("Columns") ?
                heatmap.getColumns() : heatmap.getRows();

        if (hdim.getAnnotations() == null) {
            setMessage(MessageStatus.WARN, "No annotations found");
            return null;
        }

        PatternSourcePage page = new PatternSourcePage(hdim, true);
        PageDialog dlg = new PageDialog(Application.get(), page);
        dlg.open();
        if (dlg.isCancelled()) {
            return null;
        }

        // get all clusters from $pattern

        ClusteringData data = new AnnPatClusteringData(hdim, page.getPattern());
        Clusters results = new AnnPatClusteringMethod().cluster(data, new DefaultProgressMonitor());

        List<DimensionGroup> annGroups = new ArrayList<>();
        for (String groupAnnotationPattern : results.getClusters()) {
            DimensionGroupAnnotation g = new DimensionGroupAnnotation(
                    groupAnnotationPattern,
                    new GroupByLabelPredicate(
                            hdim,
                            groupAnnotationPattern,
                            new PatternFunction(page.getPattern(), hdim.getAnnotations())));
            if (groupAnnotationPattern.equals("")) {
                g.setName("Not annotated");
            }
            annGroups.add(g);
        }
        newGroups = annGroups.toArray(new DimensionGroup[annGroups.size()]);
        this.groupingPattern = page.getPattern();
        return newGroups;
    }

    private void createValueGroup() {
        String[] ops = new String[]{Operator.AND.getAbbreviation(), Operator.OR.getAbbreviation()};
        DataIntegrationCriteriaDialog dlg =
                new DataIntegrationCriteriaDialog(
                        Application.get(),
                        heatmap.getLayers(),
                        CutoffCmp.comparators,
                        ops,
                        null,
                        "Group " + Integer.toString(tableModel.getRowCount() + 1));
        dlg.setVisible(true);
        if (dlg.isCancelled()) {
            return;
        }
        List<DataIntegrationCriteria> criteria = dlg.getCriteriaList();
        tableModel.addGroup(
                new DimensionGroupValue(dlg.getGroupName(), new GroupByValuePredicate(criteria))
        );
    }

    private void createFreeGroup() {
        HeatmapDimension hdim = dimensionCb.getSelectedItem().equals("Columns") ?
                heatmap.getColumns() : heatmap.getRows();
        DimensionGroupSelectPage page = new DimensionGroupSelectPage(hdim, "Group " + String.valueOf(groupsTable.getRowCount() + 1));
        PageDialog dlg = new PageDialog(Application.get(), page);
        dlg.open();

        if (dlg.isCancelled()) {
            return;
        }

        tableModel.addGroup(
                new DimensionGroupFree(
                        page.getGroupName(),
                        new GroupByLabelPredicate(
                                hdim,
                                page.getGroup())
                )
        );
    }


    private void removeSelected() {
        if (getSelectedGroupingType().equals(DimensionGroupEnum.Annotation)) {
            for (int cg : groupsTable.getSelectedRows()) {
                removedItems.add(tableModel.getGroupAt(cg));
            }

        }
        tableModel.removeGroups(groupsTable.getSelectedRows());
    }


    public void addGroups(DimensionGroupValue... groups) {
        tableModel.setGroups(groups);
    }

    public Double getNullConversion() {
        if (nullConversionRadioButton.isSelected()) {
            return Double.valueOf(nullConversionTextArea.getText());
        } else {
            return null;
        }
    }

    public List<DimensionGroup> getGroups() {
        return (tableModel.getGroupList());
    }

    public class AttrOption {
        private String name;
        private IMatrixLayer attr;

        /**
         * @noinspection UnusedDeclaration
         */
        public AttrOption(String name) {
            this.name = name;
        }

        public AttrOption(IMatrixLayer attr) {
            this.attr = attr;
        }

        public IMatrixLayer getAttr() {
            return attr;
        }

        @Override
        public String toString() {
            return attr != null ? attr.getName() : name;
        }
    }


    public void setSelectedGroupingType(DimensionGroupEnum groupingType) {
        if (groupingType.equals(DimensionGroupEnum.Annotation)) {
            annotationRadioButton.setSelected(true);
        } else if (groupingType.equals(DimensionGroupEnum.Value)) {
            valueRadioButton.setSelected(true);
        } else if (groupingType.equals(DimensionGroupEnum.Free)) {
            noConstraintRadioButton.setSelected(true);
        }
        this.groupingType = groupingType;
    }

    public DimensionGroupEnum getSelectedGroupingType() {
        if (annotationRadioButton.isSelected()) {
            return DimensionGroupEnum.Annotation;
        } else if (valueRadioButton.isSelected()) {
            return DimensionGroupEnum.Value;
        } else if (noConstraintRadioButton.isSelected()) {
            return DimensionGroupEnum.Free;
        }
        return null;
    }

    private void updateGroupingType() {
        if (annotationRadioButton.isSelected()) {
            this.groupingType = DimensionGroupEnum.Annotation;
        } else if (valueRadioButton.isSelected()) {
            this.groupingType = DimensionGroupEnum.Value;
        } else if (noConstraintRadioButton.isSelected()) {
            this.groupingType = DimensionGroupEnum.Free;
        }
    }

    @Override
    public JComponent createControls() {
        return panel1;
    }


    public void setAttributes(IMatrixLayers attrs) {

        if (attrs != null) {
            AttrOption[] attrOptions = new AttrOption[attrs.size()];

            for (int i = 0; i < attrs.size(); i++)
                attrOptions[i] = new AttrOption(attrs.get(i));

            layerCb.setModel(new DefaultComboBoxModel(attrOptions));
            layerCb.setSelectedIndex(0);
            layerCb.setEnabled(true);
            layerCb.setVisible(true);
            //attributeLabel.setVisible(true);
        } else {
            dissableAttrCb();
        }
    }

    private void dissableAttrCb() {
        layerCb.setModel(new DefaultComboBoxModel());
        layerCb.setEnabled(false);
        layerCb.setVisible(false);
        //attributeLabel.setVisible(false);
    }

    public String getLayer() {
        return layerCb.getSelectedItem().toString();
    }

    public DimensionGroupEnum getGroupingType() {
        return groupingType;
    }

    public String getGroupingPattern() {
        return groupingPattern;
    }
}
