package org.esa.snap.framework.ui.product.spectrum;

import com.bc.ceres.swing.TableLayout;
import com.jidesoft.swing.TristateCheckBox;
import org.esa.snap.framework.datamodel.Band;
import org.esa.snap.framework.ui.DecimalTableCellRenderer;
import org.esa.snap.framework.ui.ModalDialog;
import org.esa.snap.framework.ui.UIUtils;
import org.esa.snap.framework.ui.product.LoadSaveRasterDataNodesConfigurationsComponent;
import org.esa.snap.framework.ui.product.LoadSaveRasterDataNodesConfigurationsProvider;
import org.esa.snap.framework.ui.tool.ToolButtonFactory;
import org.esa.snap.util.ArrayUtils;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SpectrumChooser extends ModalDialog implements LoadSaveRasterDataNodesConfigurationsComponent {

    private static final int band_selected_index = 0;
    private static final int band_name_index = 1;
    private static final int band_description_index = 2;
    private static final int band_wavelength_index = 3;
    private static final int band_bandwidth_ndex = 4;
    private static final int band_unit_index = 5;
    private final String[] band_columns =
            new String[]{"", "Band name", "Band description", "Spectral wavelength (nm)", "Spectral bandwidth (nm)", "Unit"};
    private final static ImageIcon[] icons = new ImageIcon[]{
            UIUtils.loadImageIcon("icons/PanelDown12.png"),
            UIUtils.loadImageIcon("icons/PanelUp12.png"),
    };
    private final static ImageIcon[] roll_over_icons = new ImageIcon[]{
            ToolButtonFactory.createRolloverIcon(icons[0]),
            ToolButtonFactory.createRolloverIcon(icons[1]),
    };
    private final DisplayableSpectrum[] originalSpectra;

    private DisplayableSpectrum[] spectra;
    private static SpectrumSelectionAdmin selectionAdmin;
    private static boolean selectionChangeLock;

    private JPanel spectraPanel;
    private final JPanel[] bandTablePanels;
    private final JTable[] bandTables;
    private final TristateCheckBox[] tristateCheckBoxes;
    private boolean[] collapsed;
    private TableLayout spectraPanelLayout;

    public SpectrumChooser(Window parent, DisplayableSpectrum[] originalSpectra) {
        super(parent, "Available Spectra", ModalDialog.ID_OK_CANCEL_HELP, "spectrumChooser");
        if (originalSpectra != null) {
            this.originalSpectra = originalSpectra;
            List<DisplayableSpectrum> spectraWithBands = new ArrayList<>();
            for (DisplayableSpectrum spectrum : originalSpectra) {
                if (spectrum.hasBands()) {
                    spectraWithBands.add(spectrum);
                }
            }
            this.spectra = spectraWithBands.toArray(new DisplayableSpectrum[spectraWithBands.size()]);
        } else {
            this.originalSpectra = new DisplayableSpectrum[0];
            this.spectra = new DisplayableSpectrum[0];
        }
        selectionAdmin = new SpectrumSelectionAdmin();
        selectionChangeLock = false;
        bandTables = new JTable[spectra.length];
        collapsed = new boolean[spectra.length];
        Arrays.fill(collapsed, true);
        bandTablePanels = new JPanel[spectra.length];
        tristateCheckBoxes = new TristateCheckBox[spectra.length];
        initUI();
    }

    private void initUI() {
        final JPanel content = new JPanel(new BorderLayout());
        initSpectraPanel();
        final JScrollPane spectraScrollPane = new JScrollPane(spectraPanel);
        spectraScrollPane.getVerticalScrollBar().setUnitIncrement(20);
        spectraScrollPane.getHorizontalScrollBar().setUnitIncrement(20);
        content.add(spectraScrollPane, BorderLayout.CENTER);

        LoadSaveRasterDataNodesConfigurationsProvider provider = new LoadSaveRasterDataNodesConfigurationsProvider(this);
        AbstractButton loadButton = provider.getLoadButton();
        AbstractButton saveButton = provider.getSaveButton();
        TableLayout layout = new TableLayout(1);
        layout.setTablePadding(4, 4);
        JPanel buttonPanel = new JPanel(layout);
        buttonPanel.add(loadButton);
        buttonPanel.add(saveButton);
        buttonPanel.add(layout.createVerticalSpacer());
        content.add(buttonPanel, BorderLayout.EAST);

        setContent(content);
    }

    private void initSpectraPanel() {
        spectraPanelLayout = new TableLayout(7);
        spectraPanelLayout.setTableFill(TableLayout.Fill.HORIZONTAL);
        spectraPanelLayout.setTableWeightY(0.0);
        spectraPanelLayout.setTableWeightX(1.0);
        spectraPanelLayout.setColumnWeightX(0, 0.0);
        spectraPanelLayout.setColumnWeightX(1, 0.0);
        spectraPanelLayout.setTablePadding(3, 3);

        spectraPanel = new JPanel(spectraPanelLayout);
        spectraPanel.add(new JLabel(""));
        spectraPanel.add(new JLabel(""));
        spectraPanel.add(new JLabel("Spectrum Name"));
        spectraPanel.add(new JLabel("Unit"));
        spectraPanel.add(new JLabel("Line Style"));
        spectraPanel.add(new JLabel("Symbol"));
        spectraPanel.add(new JLabel("Symbol Size"));

        for (int i = 0; i < spectra.length; i++) {
            selectionAdmin.evaluateSpectrumSelections(spectra[i]);
            addSpectrumComponentsToSpectraPanel(i);
            spectraPanelLayout.setCellColspan((i * 2) + 2, 1, 6);
            spectraPanel.add(new JLabel());
            bandTablePanels[i] = new JPanel(new BorderLayout());
            bandTablePanels[i].setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
            spectraPanel.add(bandTablePanels[i]);
            bandTables[i] = createBandsTable(i);
        }
        spectraPanel.add(spectraPanelLayout.createVerticalSpacer());
        spectraPanel.updateUI();
    }

    private void addSpectrumComponentsToSpectraPanel(int index) {
        DisplayableSpectrum spectrum = spectra[index];
        ImageIcon strokeIcon;
        if (spectrum.isDefaultOrRemainingBandsSpectrum()) {
            strokeIcon = new ImageIcon();
        } else {
            strokeIcon = SpectrumStrokeProvider.getStrokeIcon(spectrum.getLineStyle());
        }
        AbstractButton collapseButton = ToolButtonFactory.createButton(icons[0], false);
        collapseButton.addActionListener(new CollapseListener(index));
        final ImageIcon shapeIcon = SpectrumShapeProvider.getShapeIcon(spectrum.getSymbolIndex());
        spectraPanel.add(collapseButton);
        final TristateCheckBox tristateCheckBox = new TristateCheckBox();
        tristateCheckBox.setState(selectionAdmin.getState(index));
        tristateCheckBox.addActionListener(new TristateCheckboxListener(index));
        tristateCheckBoxes[index] = tristateCheckBox;
        spectraPanel.add(tristateCheckBox);
        final JLabel spectrumNameLabel = new JLabel(spectrum.getName());
        Font font = spectrumNameLabel.getFont();
        font = new Font(font.getName(), Font.BOLD, font.getSize());
        spectrumNameLabel.setFont(font);
        spectraPanel.add(spectrumNameLabel);
        spectraPanel.add(new JLabel(spectrum.getUnit()));
        JComboBox<ImageIcon> strokeComboBox;
        if (spectrum.isDefaultOrRemainingBandsSpectrum()) {
            strokeComboBox = new JComboBox<>(new ImageIcon[]{strokeIcon});
            strokeComboBox.setEnabled(false);
        } else {
            strokeComboBox = new JComboBox<>(SpectrumStrokeProvider.getStrokeIcons());
            strokeComboBox.addActionListener(
                    e -> spectrum.setLineStyle(
                            SpectrumStrokeProvider.getStroke((ImageIcon) strokeComboBox.getSelectedItem())));
        }
        strokeComboBox.setPreferredSize(new Dimension(100, 20));
        strokeComboBox.setSelectedItem(strokeIcon);
        spectraPanel.add(strokeComboBox);
        JComboBox<ImageIcon> shapeComboBox = new JComboBox<>(SpectrumShapeProvider.getShapeIcons());
        JComboBox<Integer> shapeSizeComboBox = new JComboBox<>(SpectrumShapeProvider.getScaleGrades());
        shapeComboBox.setPreferredSize(new Dimension(100, 20));
        shapeSizeComboBox.setPreferredSize(new Dimension(100, 20));
        shapeComboBox.setSelectedItem(shapeIcon);
        shapeComboBox.addActionListener(e -> {
            final int shapeIndex = SpectrumShapeProvider.getShape((ImageIcon) shapeComboBox.getSelectedItem());
            spectrum.setSymbolIndex(shapeIndex);
            if (shapeIndex == SpectrumShapeProvider.EMPTY_SHAPE_INDEX) {
                shapeSizeComboBox.setSelectedItem("");
            } else {
                shapeSizeComboBox.setSelectedItem(spectrum.getSymbolSize());
            }
        });
        spectraPanel.add(shapeComboBox);
        shapeSizeComboBox.setSelectedItem(spectrum.getSymbolSize());
        shapeSizeComboBox.addActionListener(e -> {
            final String selectedItem = shapeSizeComboBox.getSelectedItem().toString();
            if (!selectedItem.equals("")) {
                spectrum.setSymbolSize(Integer.parseInt(selectedItem));
            }
        });
        spectraPanel.add(shapeSizeComboBox);
    }

    private void toggleCollapsed(int index) {
        final boolean isCollapsed = !collapsed[index];
        collapsed[index] = isCollapsed;
        int rowIndex = (index * 2) + 2;
        if (isCollapsed) {
            spectraPanelLayout.setRowFill(rowIndex, TableLayout.Fill.HORIZONTAL);
            spectraPanelLayout.setRowWeightY(rowIndex, 0.0);
            bandTablePanels[index].removeAll();
        } else {
            spectraPanelLayout.setRowFill(rowIndex, TableLayout.Fill.BOTH);
            spectraPanelLayout.setRowWeightY(rowIndex, 1.0);
            bandTablePanels[index].add(bandTables[index].getTableHeader(), BorderLayout.NORTH);
            bandTablePanels[index].add(bandTables[index], BorderLayout.CENTER);
        }
        bandTablePanels[index].updateUI();
        spectraPanel.updateUI();
    }

    private JTable createBandsTable(int index) {
        DisplayableSpectrum spectrum = spectra[index];
        final Band[] spectralBands = spectrum.getSpectralBands();
        Object[][] spectrumData = new Object[spectralBands.length][band_columns.length];
        for (int i = 0; i < spectralBands.length; i++) {
            Band spectralBand = spectralBands[i];
            final boolean selected = spectrum.isBandSelected(i) && spectrum.isSelected();
            spectrumData[i][band_selected_index] = selected;
            spectrumData[i][band_name_index] = spectralBand.getName();
            spectrumData[i][band_description_index] = spectralBand.getDescription();
            spectrumData[i][band_wavelength_index] = spectralBand.getSpectralWavelength();
            spectrumData[i][band_bandwidth_ndex] = spectralBand.getSpectralBandwidth();
            spectrumData[i][band_unit_index] = spectralBand.getUnit();
        }
        final BandTableModel bandTableModel = new BandTableModel(spectrumData, band_columns);
        bandTableModel.addTableModelListener(e -> {
            e.getSource();
            if (e.getColumn() == band_selected_index) {
                final DisplayableSpectrum spectrum1 = spectra[index];
                final int bandRow = e.getFirstRow();
                final Boolean selected = (Boolean) bandTableModel.getValueAt(bandRow, e.getColumn());
                spectrum1.setBandSelected(bandRow, selected);
                if (!selectionChangeLock) {
                    selectionChangeLock = true;
                    selectionAdmin.setBandSelected(index, bandRow, selected);
                    selectionAdmin.updateSpectrumSelectionState(index, selectionAdmin.getState(index));
                    tristateCheckBoxes[index].setState(selectionAdmin.getState(index));
                    spectrum1.setSelected(selectionAdmin.isSpectrumSelected(index));
                    selectionChangeLock = false;
                }
            }
        });
        JTable bandsTable = new JTable(bandTableModel);
        bandsTable.setRowSorter(new TableRowSorter<>(bandTableModel));

        final TableColumn selectionColumn = bandsTable.getColumnModel().getColumn(band_selected_index);
        final JCheckBox selectionCheckBox = new JCheckBox();
        selectionColumn.setCellEditor(new DefaultCellEditor(selectionCheckBox));
        selectionColumn.setMinWidth(20);
        selectionColumn.setMaxWidth(20);
        BooleanRenderer booleanRenderer = new BooleanRenderer();
        selectionColumn.setCellRenderer(booleanRenderer);

        final TableColumn wavelengthColumn = bandsTable.getColumnModel().getColumn(band_wavelength_index);
        wavelengthColumn.setCellRenderer(new DecimalTableCellRenderer(new DecimalFormat("###0.0##")));

        final TableColumn bandwidthColumn = bandsTable.getColumnModel().getColumn(band_bandwidth_ndex);
        bandwidthColumn.setCellRenderer(new DecimalTableCellRenderer(new DecimalFormat("###0.0##")));

        return bandsTable;
    }

    private void updateBandsTable(int index) {
        final TableModel tableModel = bandTables[index].getModel();
        for (int i = 0; i < tableModel.getRowCount(); i++) {
            tableModel.setValueAt(selectionAdmin.isBandSelected(index, i), i, band_selected_index);
        }
    }

    public DisplayableSpectrum[] getSpectra() {
        return originalSpectra;
    }

    @Override
    public void setReadRasterDataNodeNames(String[] readRasterDataNodeNames) {
        for (JTable bandTable : bandTables) {
            BandTableModel bandTableModel = (BandTableModel) bandTable.getModel();
            for (int j = 0; j < bandTableModel.getRowCount(); j++) {
                String bandName = bandTableModel.getValueAt(j, band_name_index).toString();
                boolean selected = ArrayUtils.isMemberOf(bandName, readRasterDataNodeNames);
                bandTableModel.setValueAt(selected, j, band_selected_index);
            }
        }

    }

    @Override
    public String[] getRasterDataNodeNamesToWrite() {
        List<String> bandNames = new ArrayList<>();
        for (JTable bandTable : bandTables) {
            BandTableModel bandTableModel = (BandTableModel) bandTable.getModel();
            for (int j = 0; j < bandTableModel.getRowCount(); j++) {
                if ((boolean) bandTableModel.getValueAt(j, band_selected_index)) {
                    bandNames.add(bandTableModel.getValueAt(j, band_name_index).toString());
                }
            }
        }
        return bandNames.toArray(new String[bandNames.size()]);
    }

    private static class BandTableModel extends DefaultTableModel {

        private BandTableModel(Object[][] spectrumData, String[] bandColumns) {
            super(spectrumData, bandColumns);
        }

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

    }

    private class BooleanRenderer extends JCheckBox implements TableCellRenderer {

        private BooleanRenderer() {
            this.setOpaque(true);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            if (isSelected) {
                setBackground(table.getSelectionBackground());
                setForeground(table.getSelectionForeground());
            } else {
                setBackground(table.getBackground());
                setForeground(table.getForeground());
            }
            boolean selected = (Boolean) value;
            setSelected(selected);
            return this;
        }
    }

    private class CollapseListener implements ActionListener {

        private final int index;

        CollapseListener(int index) {
            this.index = index;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            final Object source = e.getSource();
            if (source instanceof AbstractButton) {
                AbstractButton collapseButton = (AbstractButton) source;
                toggleCollapsed(index);
                if (collapsed[index]) {
                    collapseButton.setIcon(icons[0]);
                    collapseButton.setRolloverIcon(roll_over_icons[0]);
                } else {
                    collapseButton.setIcon(icons[1]);
                    collapseButton.setRolloverIcon(roll_over_icons[1]);
                }
            }
        }

    }

    private class TristateCheckboxListener implements ActionListener {

        private final int index;

        TristateCheckboxListener(int index) {
            this.index = index;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (!selectionChangeLock) {
                selectionChangeLock = true;
                selectionAdmin.updateSpectrumSelectionState(index, tristateCheckBoxes[index].getState());
                tristateCheckBoxes[index].setState(selectionAdmin.getState(index));
                updateBandsTable(index);
                spectra[index].setSelected(selectionAdmin.isSpectrumSelected(index));
                selectionChangeLock = false;
            }
        }
    }

}
