/*
 * #%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.core.pages.common;

import com.alee.utils.SwingUtils;
import org.gitools.api.ApplicationContext;
import org.gitools.api.matrix.IMatrixLayer;
import org.gitools.api.resource.ResourceReference;
import org.gitools.heatmap.HeatmapDimension;
import org.gitools.heatmap.header.HeatmapHeader;
import org.gitools.heatmap.header.HeatmapTextLabelsHeader;
import org.gitools.matrix.format.AnnotationMatrixFormat;
import org.gitools.matrix.model.matrix.AnnotationMatrix;
import org.gitools.persistence.locators.UrlResourceLocator;
import org.gitools.ui.core.utils.DocumentChangeListener;
import org.gitools.ui.core.utils.FileChooserUtils;
import org.gitools.ui.core.utils.LogUtils;
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.settings.Settings;
import org.gitools.ui.platform.wizard.AbstractWizardPage;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class AnnotationEditPage extends AbstractWizardPage {

    private final AnnotationMatrix annotationMatrix;
    private JPanel root;
    private JList annList;
    private JTextField annotationSearchField;
    private JScrollPane jScrollPane1;
    private JButton loadAnnotations;
    private JButton removeSelected;
    private JButton fromOtherHeatmapButton;
    private JTextField annTitleField;
    private JButton applyTitleChange;
    private JTable previewTable;
    private JTextField descriptionField;
    private JButton applyDescriptionButton;
    private JScrollPane previewScrollPane;

    private HeatmapDimension hdim;

    private List<AnnotationOption> annotation;
    private DefaultTableModel tableModel;


    public AnnotationEditPage(HeatmapDimension hdim) {
        this.hdim = hdim;
        annotationMatrix = hdim.getAnnotations();

        setLogo(IconUtils.getImageIconResourceScaledByHeight(IconNames.annotation512, 96));
        tableModel = new DefaultTableModel() {


            @Override
            public String getColumnName(int column) {
                if (column == 0) {
                    return "Id";
                } else if (column > 0 && column < this.getColumnCount()) {
                    return ((AnnotationOption) annList.getSelectedValuesList().get(column - 1)).getKey();
                }
                return super.getColumnName(column);
            }

            @Override
            public int getRowCount() {
                return annotationMatrix.getIdentifiers().size();
            }

            @Override
            public int getColumnCount() {
                return annList.getSelectedValuesList().size() + 1;
            }

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                if (rowIndex < 0 || columnIndex < 0) {
                    return null;
                }
                String id = annotationMatrix.getIdentifiers().getLabel(rowIndex);
                if (columnIndex == 0) {
                    return id;
                } else {
                    String key;
                    try {
                        key = ((AnnotationOption) annList.getSelectedValuesList().get(columnIndex - 1)).getKey();
                    } catch (IndexOutOfBoundsException e) {
                        return null;
                    }
                    return annotationMatrix.getAnnotation(id, key);
                }
            }
        };
        previewTable.setModel(tableModel);

        initComponents();


        fromOtherHeatmapButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //todo: addFromOtherHeatmap();

            }
        });
        fromOtherHeatmapButton.setEnabled(false);
        fromOtherHeatmapButton.setVisible(false);


        annList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {

                if (e.getValueIsAdjusting()) {
                    return;
                }
                annListSelectionChanged();
                updateControls();
            }
        });
        setTitle("Annotations selection");
        setComplete(true);
    }


    private void annListSelectionChanged() {
        tableModel.setColumnCount(annList.getSelectedValuesList().size() + 1);

        SwingUtils.invokeLater(new Runnable() {
            @Override
            public void run() {
                previewTable.updateUI();
                AnnotationOption selectedValue = (AnnotationOption) annList.getSelectedValue();
                if (selectedValue == null) {
                    return;
                }
                annTitleField.setText(selectedValue.getKey());
                descriptionField.setText(selectedValue.getDescription());
            }
        });

    }

    class FilterCellRenderer extends DefaultListCellRenderer {

        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            AnnotationOption annOpt = (AnnotationOption) value;
            String needle = annotationSearchField.getText().toLowerCase();
            boolean match;
            match = annotationSearchField.getText().isEmpty();

            if (!match) {
                match = annOpt.getKey().toLowerCase().contains(needle) |
                        annOpt.getDescription().toLowerCase().contains(needle);
            }

            if (match) {// <= put your logic here
                c.setFont(c.getFont().deriveFont(Font.ITALIC, 14.0f));
                c.setForeground(Color.BLACK);
            } else {
                c.setFont(c.getFont().deriveFont(Font.ITALIC, 9.0f));
                c.setForeground(Color.LIGHT_GRAY);
            }
            return c;
        }
    }


    @Override
    public void updateControls() {

        SwingUtils.invokeLater(new Runnable() {
            @Override
            public void run() {
                AnnotationOption ann = (AnnotationOption) annList.getSelectedValue();
                if (ann != null && !annTitleField.getText().equals(ann.getKey()) && isNewKey() &&
                        annList.getSelectedIndices().length == 1) {
                    annTitleField.setFont(annTitleField.getFont().deriveFont(Font.BOLD));
                    applyTitleChange.setEnabled(true);
                } else {
                    annTitleField.setFont(annTitleField.getFont().deriveFont(Font.PLAIN));
                    applyTitleChange.setEnabled(false);
                }

                if (ann != null && !descriptionField.getText().equals(ann.getDescription()) &&
                        annList.getSelectedIndices().length == 1) {
                    descriptionField.setFont(descriptionField.getFont().deriveFont(Font.BOLD));
                    applyDescriptionButton.setEnabled(true);
                } else {
                    descriptionField.setFont(descriptionField.getFont().deriveFont(Font.PLAIN));
                    applyDescriptionButton.setEnabled(false);
                }
            }
        });


    }

    private boolean isNewKey() {
        String newKey = annTitleField.getText();
        int selectedIdx = annList.getSelectedIndex();
        int elements = annList.getModel().getSize();
        for (int i = 0; i < elements; i++) {
            if (i == selectedIdx) {
                continue;
            }

            AnnotationOption ao = (AnnotationOption) annList.getModel().getElementAt(i);
            if (newKey.toLowerCase().equals(ao.getKey().toLowerCase())) {
                return false;
            }
        }
        return true;
    }

    private void removeSelectedAnnotations() {
        String inUse = "id";
        for (HeatmapHeader hh : hdim.getHeaders()) {
            inUse = inUse + ", " + hh.getAnnotationPattern();
        }

        StringBuilder cannotRemove = new StringBuilder();
        boolean stuffHasBeenRemoved = false;

        for (Object option : annList.getSelectedValuesList()) {
            String label = ((AnnotationOption) option).getKey();
            if (!inUse.contains(label)) {
                hdim.getAnnotations().removeAnnotations(label);
                stuffHasBeenRemoved = true;
            } else {
                cannotRemove.append(label);
                cannotRemove.append(", ");
            }
        }

        if (stuffHasBeenRemoved) {
            initComponents();
        }
        annList.setSelectedIndices(new int[0]);
        if (cannotRemove.length() > 0) {
            setMessage(MessageStatus.WARN, "<html>Could not remove some annotations because they are being used in a header: " +
                    "<b>" + cannotRemove.toString() + "</b></html>");
        }
    }


    private void updateComplete() {
        setComplete(annList.getSelectedIndices().length > 0);
    }


    public String[] getSelectedValues() {
        int[] indices = annList.getSelectedIndices();
        String[] values = new String[indices.length];

        for (int i = 0; i < indices.length; i++) {
            int index = indices[i];
            if (index == 0) {
                values[i] = "id";
            } else {
                values[i] = annotation.get(index - 1).getKey();
            }
        }

        return values;
    }

    public void setHeatmapDimension(HeatmapDimension heatmapDimension) {
        this.hdim = heatmapDimension;
        updateControls();
    }


    private void filterAnnotationsBox(KeyEvent evt) {
        annList.repaint();
    }

    public HeatmapTextLabelsHeader.LabelSource getLabelSource() {
        if (annList.getSelectedIndex() == 0) {
            return HeatmapTextLabelsHeader.LabelSource.ID;
        } else {
            return HeatmapTextLabelsHeader.LabelSource.PATTERN;
        }
    }

    public String getAnnotationName() {
        if (annList.getSelectedIndex() > 0) {
            return annotation.get(annList.getSelectedIndex() - 1).getKey();
        } else {
            return "";
        }
    }


    private void initComponents() {

        jScrollPane1.setViewportView(annList);
        previewScrollPane.setViewportView(previewTable);

        loadAnnotations.setText("Load from file");
        loadAnnotations.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                loadAnnotationsActionPerformed(evt);
            }
        });

        annotationSearchField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                annotationSearchFieldKeyReleased(evt);
            }
        });

        removeSelected.setText("remove Selected");
        removeSelected.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeSelectedAnnotations();
            }
        });

        DocumentChangeListener docListener = new DocumentChangeListener() {
            @Override
            protected void update(DocumentEvent e) {
                updateControls();
            }
        };
        annTitleField.getDocument().addDocumentListener(docListener);
        descriptionField.getDocument().addDocumentListener(docListener);

        applyTitleChange.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changeAnnotationKey();
            }
        });
        applyDescriptionButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changeDescription();
            }
        });

        if (hdim != null && hdim.getAnnotations() != null && !hdim.getAnnotations().getLabels().isEmpty()) {

            DefaultListModel<AnnotationOption> model = new DefaultListModel<>();
            FilterCellRenderer cellRenderer = new FilterCellRenderer();


            annotation = new ArrayList<>();
            for (String key : hdim.getAnnotations().getLabels()) {
                if (key.toLowerCase().startsWith("hierarchical-cols") || key.toLowerCase().startsWith("hierarchical-rows")) {
                    continue;
                }
                String description = hdim.getAnnotations().getAnnotationMetadata(AnnotationMatrix.METADATA_DESCRIPTION, key);
                annotation.add(new AnnotationOption(key, description));
            }

            Collections.sort(annotation, new Comparator<AnnotationOption>() {
                @Override
                public int compare(AnnotationOption o1, AnnotationOption o2) {
                    return o1.toString().toUpperCase().compareTo(o2.toString().toUpperCase());
                }
            });

            for (AnnotationOption annotationOption : annotation) {
                model.addElement(annotationOption);
            }

            annList.setModel(model);
            annList.setSelectedIndex(0);
            annList.setCellRenderer(cellRenderer);
            setMessage(MessageStatus.INFO, "View, Load and Remove annotations");
        }

    }

    private void changeDescription() {
        int idx = annList.getSelectedIndex();
        AnnotationOption anno = (AnnotationOption) annList.getSelectedValue();
        annotationMatrix.setAnnotationMetadata(AnnotationMatrix.METADATA_DESCRIPTION, anno.getKey(), descriptionField.getText());
        initComponents();
        annList.setSelectedIndex(idx);
    }

    private void changeAnnotationKey() {
        AnnotationOption anno = (AnnotationOption) annList.getSelectedValue();
        IMatrixLayer<String> oldLayer = annotationMatrix.getLayers().get(anno.getKey());

        String newKey = annTitleField.getText();
        for (String id : annotationMatrix.getIdentifiers()) {
            annotationMatrix.setAnnotation(id, newKey, annotationMatrix.get(oldLayer, id));
        }
        initComponents();

    }

    private void loadAnnotationsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loadAnnotationsActionPerformed
        try {
            File file = FileChooserUtils.selectFile("Open annotationMatrix file", Settings.get().getLastAnnotationPath(), FileChooserUtils.MODE_OPEN).getFile();

            if (file != null) {
                hdim.addAnnotations(new ResourceReference<>(new UrlResourceLocator(file), ApplicationContext.getPersistenceManager().getFormat(AnnotationMatrixFormat.EXTENSION, AnnotationMatrix.class)).get());
                Settings.get().setLastAnnotationPath(file.getParent());
                updateControls();
            }
        } catch (Exception ex) {
            LogUtils.logException(ex, LoggerFactory.getLogger(getClass()));
        }
    }

    private void annotationSearchFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_annotationSearchFieldKeyReleased
        filterAnnotationsBox(evt);
    }


    public JPanel getPanel() {
        return root;
    }

}
