/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.gui.preferences;

import icy.gui.component.IcyTable;
import icy.gui.component.IcyTextField;
import icy.gui.component.IcyTextField.TextChangeListener;
import icy.gui.plugin.PluginDetailPanel;
import icy.gui.util.ComponentUtil;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.preferences.RepositoryPreferences;
import icy.preferences.RepositoryPreferences.RepositoryInfo;
import icy.resource.ResourceUtil;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/**
 * @author Stephane
 */
public abstract class PluginListPreferencePanel extends PreferencePanel implements TextChangeListener,
        ListSelectionListener
{
    /**
     * 
     */
    private static final long serialVersionUID = -2718763355377652489L;

    static final String[] columnNames = {"", "Name", "Version", "State", "Enabled"};
    static final String[] columnIds = {"Icon", "Name", "Version", "State", "Enabled"};

    List<PluginDescriptor> plugins;

    /**
     * gui
     */
    final AbstractTableModel tableModel;
    final JTable table;

    final JComboBox repository;
    final JPanel repositoryPanel;
    final IcyTextField filter;
    final JButton refreshButton;
    final JButton documentationButton;
    final JButton detailButton;
    final JButton action1Button;
    final JButton action2Button;

    private final Runnable buttonsStateUpdater;
    private final Runnable tableDataRefresher;
    private final Runnable pluginsListRefresher;
    private final Runnable repositoriesUpdater;

    final ActionListener repositoryActionListener;

    PluginListPreferencePanel(PreferenceFrame parent, String nodeName, String parentName)
    {
        super(parent, nodeName, parentName);

        plugins = new ArrayList<PluginDescriptor>();

        buttonsStateUpdater = new Runnable()
        {
            @Override
            public void run()
            {
                // need to be done on EDT
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        updateButtonsStateInternal();
                    }
                });
            }
        };
        tableDataRefresher = new Runnable()
        {
            @Override
            public void run()
            {
                // need to be done on EDT
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        refreshTableDataInternal();
                    }
                });
            }
        };
        pluginsListRefresher = new Runnable()
        {
            @Override
            public void run()
            {
                refreshPluginsInternal();
            }
        };
        repositoriesUpdater = new Runnable()
        {
            @Override
            public void run()
            {
                // need to be done on EDT
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        updateRepositoriesInternal();
                    }
                });
            }
        };
        repositoryActionListener = new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                repositoryChanged();
            }
        };

        repository = new JComboBox();
        repository.setToolTipText("Select a repository");
        repository.addActionListener(repositoryActionListener);

        repositoryPanel = new JPanel();
        repositoryPanel.setLayout(new BoxLayout(repositoryPanel, BoxLayout.PAGE_AXIS));
        repositoryPanel.setVisible(false);

        final JPanel internalRepPanel = new JPanel();
        internalRepPanel.setLayout(new BoxLayout(internalRepPanel, BoxLayout.LINE_AXIS));

        internalRepPanel.add(new JLabel("Repository :"));
        internalRepPanel.add(Box.createHorizontalStrut(8));
        internalRepPanel.add(repository);
        internalRepPanel.add(Box.createHorizontalGlue());

        repositoryPanel.add(internalRepPanel);
        repositoryPanel.add(Box.createVerticalStrut(8));

        // need filter before load()
        filter = new IcyTextField();
        filter.addTextChangeListener(this);

        // build buttons panel
        final Dimension buttonsDim = new Dimension(100, 24);

        refreshButton = new JButton("Reload list");
        refreshButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                reloadPlugins();
            }
        });
        ComponentUtil.setFixedSize(refreshButton, buttonsDim);

        documentationButton = new JButton("Online doc");
        documentationButton.setToolTipText("Open the online documentation");
        documentationButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();

                // open plugin web page
                if (selectedPlugins.size() == 1)
                    NetworkUtil.openBrowser(selectedPlugins.get(0).getWeb());
            }
        });
        ComponentUtil.setFixedSize(documentationButton, buttonsDim);

        detailButton = new JButton("Show detail");
        detailButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();

                // open the detail
                if (selectedPlugins.size() == 1)
                    new PluginDetailPanel(selectedPlugins.get(0));
            }
        });
        ComponentUtil.setFixedSize(detailButton, buttonsDim);

        action1Button = new JButton("null");
        action1Button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                doAction1();
            }
        });
        action1Button.setVisible(false);
        ComponentUtil.setFixedSize(action1Button, buttonsDim);

        action2Button = new JButton("null");
        action2Button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                doAction2();
            }
        });
        action2Button.setVisible(false);
        ComponentUtil.setFixedSize(action2Button, buttonsDim);

        final JPanel buttonsPanel = new JPanel();
        buttonsPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 8));
        buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.PAGE_AXIS));

        buttonsPanel.add(refreshButton);
        buttonsPanel.add(Box.createVerticalStrut(34));
        buttonsPanel.add(documentationButton);
        buttonsPanel.add(Box.createVerticalStrut(8));
        buttonsPanel.add(detailButton);
        buttonsPanel.add(Box.createVerticalStrut(8));
        buttonsPanel.add(action1Button);
        buttonsPanel.add(Box.createVerticalStrut(8));
        buttonsPanel.add(action2Button);
        buttonsPanel.add(Box.createVerticalStrut(8));
        buttonsPanel.add(Box.createVerticalGlue());

        // build table
        tableModel = new AbstractTableModel()
        {
            /**
             * 
             */
            private static final long serialVersionUID = -8573364273165723214L;

            @Override
            public int getColumnCount()
            {
                return columnNames.length;
            }

            @Override
            public String getColumnName(int column)
            {
                return columnNames[column];
            }

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

            @Override
            public Object getValueAt(int row, int column)
            {
                if (row < plugins.size())
                {
                    final PluginDescriptor plugin = plugins.get(row);

                    switch (column)
                    {
                        case 0:
                            if (plugin.isIconLoaded())
                                return ResourceUtil.scaleIcon(plugin.getIcon(), 32);

                            loadIconAsync(plugin);
                            return ResourceUtil.scaleIcon(PluginDescriptor.DEFAULT_ICON, 32);

                        case 1:
                            return plugin.getName();

                        case 2:
                            return plugin.getVersion().toString();

                        case 3:
                            return getStateValue(plugin);

                        case 4:
                            return Boolean.valueOf(isActive(plugin));
                    }
                }

                return "";
            }

            @Override
            public void setValueAt(Object aValue, int rowIndex, int columnIndex)
            {
                if (rowIndex < plugins.size())
                {
                    final PluginDescriptor plugin = plugins.get(rowIndex);

                    if (columnIndex == 4)
                    {
                        if (aValue instanceof Boolean)
                            setActive(plugin, ((Boolean) aValue).booleanValue());
                    }
                }
            }

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

            @Override
            public Class<?> getColumnClass(int columnIndex)
            {
                switch (columnIndex)
                {
                    case 0:
                        return ImageIcon.class;
                    case 4:
                        return Boolean.class;
                    default:
                        return String.class;
                }
            }
        };

        table = new IcyTable(tableModel);

        final TableColumnModel colModel = table.getColumnModel();
        TableColumn col;

        // columns setting
        col = colModel.getColumn(0);
        col.setIdentifier(columnIds[0]);
        col.setMinWidth(32);
        col.setPreferredWidth(32);
        col.setMaxWidth(32);

        col = colModel.getColumn(1);
        col.setIdentifier(columnIds[1]);
        col.setMinWidth(120);
        col.setPreferredWidth(200);
        col.setMaxWidth(500);

        col = colModel.getColumn(2);
        col.setIdentifier(columnIds[2]);
        col.setMinWidth(60);
        col.setPreferredWidth(60);
        col.setMaxWidth(60);

        col = colModel.getColumn(3);
        col.setIdentifier(columnIds[3]);
        col.setMinWidth(70);
        col.setPreferredWidth(90);
        col.setMaxWidth(120);

        col = colModel.getColumn(4);
        col.setIdentifier(columnIds[4]);
        col.setMinWidth(60);
        col.setPreferredWidth(60);
        col.setMaxWidth(60);

        table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        table.getSelectionModel().addListSelectionListener(this);
        table.setRowHeight(32);
        table.setColumnSelectionAllowed(false);
        table.setRowSelectionAllowed(true);
        table.setShowVerticalLines(false);
        table.setAutoCreateRowSorter(true);
        // sort on name by default
        table.getRowSorter().toggleSortOrder(1);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        table.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent me)
            {
                if (!me.isConsumed())
                {
                    if (me.getClickCount() == 2)
                    {
                        // show detail
                        detailButton.doClick();
                        me.consume();
                    }
                }
            }
        });

        final JPanel tableTopPanel = new JPanel();

        tableTopPanel.setLayout(new BoxLayout(tableTopPanel, BoxLayout.PAGE_AXIS));

        tableTopPanel.add(Box.createVerticalStrut(2));
        tableTopPanel.add(repositoryPanel);
        tableTopPanel.add(filter);
        tableTopPanel.add(Box.createVerticalStrut(8));
        tableTopPanel.add(table.getTableHeader());

        final JPanel tablePanel = new JPanel();

        tablePanel.setLayout(new BorderLayout());

        tablePanel.add(tableTopPanel, BorderLayout.NORTH);
        tablePanel.add(new JScrollPane(table), BorderLayout.CENTER);

        mainPanel.setLayout(new BorderLayout());

        mainPanel.add(tablePanel, BorderLayout.CENTER);
        mainPanel.add(buttonsPanel, BorderLayout.EAST);

        mainPanel.validate();
    }

    protected void loadIconAsync(final PluginDescriptor plugin)
    {
        ThreadUtil.bgRun(new Runnable()
        {
            @Override
            public void run()
            {
                // icon correctly loaded ?
                if (plugin.loadIcon())
                    refreshTableData();
            }
        });
    }

    @Override
    protected void closed()
    {
        super.closed();

        // do not retains plugins when frame is closed
        plugins.clear();
    }

    private List<PluginDescriptor> filterList(List<PluginDescriptor> list, String filter)
    {
        final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
        final boolean empty = StringUtil.isEmpty(filter, true);
        final String filterUp;

        if (!empty)
            filterUp = filter.toUpperCase();
        else
            filterUp = "";

        for (PluginDescriptor plugin : list)
        {
            final String classname = plugin.getClassName().toUpperCase();
            final String name = plugin.getName().toUpperCase();
            final String desc = plugin.getDescription().toUpperCase();

            // search in name and description
            if (empty || (classname.indexOf(filterUp) != -1) || (name.indexOf(filterUp) != -1)
                    || (desc.indexOf(filterUp) != -1))
                result.add(plugin);
        }

        return result;
    }

    protected boolean isActive(PluginDescriptor plugin)
    {
        return false;
    }

    protected void setActive(PluginDescriptor plugin, boolean value)
    {
    }

    protected abstract void doAction1();

    protected abstract void doAction2();

    protected abstract void repositoryChanged();

    protected abstract void reloadPlugins();

    protected abstract String getStateValue(PluginDescriptor plugin);

    protected abstract List<PluginDescriptor> getPlugins();

    protected int getPluginTableIndex(int rowIndex)
    {
        if (rowIndex == -1)
            return rowIndex;

        try
        {

            return table.convertRowIndexToView(rowIndex);
        }
        catch (IndexOutOfBoundsException e)
        {
            return -1;
        }
    }

    protected int getPluginIndex(PluginDescriptor plugin)
    {
        return plugins.indexOf(plugin);
    }

    protected int getPluginModelIndex(PluginDescriptor plugin)
    {
        return getPluginIndex(plugin);
    }

    protected int getPluginTableIndex(PluginDescriptor plugin)
    {
        return getPluginTableIndex(getPluginModelIndex(plugin));
    }

    protected int getPluginIndex(String pluginClassName)
    {
        for (int i = 0; i < plugins.size(); i++)
        {
            final PluginDescriptor plugin = plugins.get(i);

            if (plugin.getClassName().equals(pluginClassName))
                return i;
        }

        return -1;
    }

    protected int getPluginModelIndex(String pluginClassName)
    {
        return getPluginIndex(pluginClassName);
    }

    protected int getPluginTableIndex(String pluginClassName)
    {
        return getPluginTableIndex(getPluginModelIndex(pluginClassName));
    }

    List<PluginDescriptor> getSelectedPlugins()
    {
        final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();

        final int[] rows = table.getSelectedRows();
        if (rows.length == 0)
            return result;

        final List<PluginDescriptor> cachedPlugins = plugins;

        for (int i = 0; i < rows.length; i++)
        {
            try
            {
                final int index = table.convertRowIndexToModel(rows[i]);
                if (index < cachedPlugins.size())
                    result.add(cachedPlugins.get(index));
            }
            catch (IndexOutOfBoundsException e)
            {
                // ignore as async process can cause it
            }
        }

        return result;
    }

    /**
     * Select the specified list of ROI in the ROI Table
     */
    void setSelectedPlugins(HashSet<PluginDescriptor> newSelected)
    {
        final List<PluginDescriptor> modelPlugins = plugins;
        final ListSelectionModel selectionModel = table.getSelectionModel();

        // start selection change
        selectionModel.setValueIsAdjusting(true);
        try
        {
            // start by clearing selection
            selectionModel.clearSelection();

            for (int i = 0; i < modelPlugins.size(); i++)
            {
                final PluginDescriptor plugin = modelPlugins.get(i);

                // HashSet provide fast "contains"
                if (newSelected.contains(plugin))
                {
                    try
                    {
                        // convert model index to view index
                        final int ind = table.convertRowIndexToView(i);
                        if (ind != -1)
                            selectionModel.addSelectionInterval(ind, ind);
                    }
                    catch (IndexOutOfBoundsException e)
                    {
                        // ignore
                    }
                }
            }
        }
        finally
        {
            // end selection change
            selectionModel.setValueIsAdjusting(false);
        }
    }

    protected void refreshPluginsInternal()
    {
        plugins = filterList(getPlugins(), filter.getText());
        // refresh table data
        refreshTableData();
    }

    protected final void refreshPlugins()
    {
        ThreadUtil.runSingle(pluginsListRefresher);
    }

    protected void updateButtonsStateInternal()
    {
        final List<PluginDescriptor> selectedPlugins = getSelectedPlugins();
        final boolean singleSelection = (selectedPlugins.size() == 1);
        final PluginDescriptor singlePlugin = singleSelection ? selectedPlugins.get(0) : null;

        detailButton.setEnabled(singleSelection);
        documentationButton.setEnabled(singleSelection && !StringUtil.isEmpty(singlePlugin.getWeb()));
    }

    protected final void updateButtonsState()
    {
        ThreadUtil.runSingle(buttonsStateUpdater);
    }

    protected void updateRepositoriesInternal()
    {
        // final RepositoryPreferencePanel panel = (RepositoryPreferencePanel)
        // getPreferencePanel(RepositoryPreferencePanel.class);
        // // refresh repositories list (use list from GUI)
        // final ArrayList<RepositoryInfo> repositeries = panel.repositories;

        // refresh repositories list
        final List<RepositoryInfo> repositeries = RepositoryPreferences.getRepositeries();
        final RepositoryInfo savedRepository = (RepositoryInfo) repository.getSelectedItem();

        // needed to disable events during update time
        repository.removeActionListener(repositoryActionListener);

        repository.removeAllItems();
        for (RepositoryInfo repos : repositeries)
            if (repos.isEnabled())
                repository.addItem(repos);

        repository.addActionListener(repositoryActionListener);

        boolean selected = false;

        // try to set back the old selected repository
        if (savedRepository != null)
        {
            final String repositoryName = savedRepository.getName();

            for (int ind = 0; ind < repository.getItemCount(); ind++)
            {
                final RepositoryInfo repo = (RepositoryInfo) repository.getItemAt(ind);

                if ((repo != null) && (repo.getName().equals(repositoryName)))
                {
                    repository.setSelectedIndex(ind);
                    selected = true;
                    break;
                }
            }
        }

        // manually launch the action
        if (!selected)
            repository.setSelectedIndex((repository.getItemCount() > 0) ? 0 : -1);

        // avoid automatic minimum size here
        repository.setMinimumSize(new Dimension(48, 18));
    }

    protected final void updateRepositories()
    {
        ThreadUtil.runSingle(repositoriesUpdater);
    }

    protected void refreshTableDataInternal()
    {
        final List<PluginDescriptor> plugins = getSelectedPlugins();

        tableModel.fireTableDataChanged();
        // restore previous selected plugins if possible
        setSelectedPlugins(new HashSet<PluginDescriptor>(plugins));
        // update button state
        buttonsStateUpdater.run();
    }

    protected final void refreshTableData()
    {
        ThreadUtil.runSingle(tableDataRefresher);
    }

    protected void pluginsChanged()
    {
        refreshPlugins();
    }

    @Override
    protected void load()
    {

    }

    @Override
    protected void save()
    {
        // reload repositories as some parameter as beta flag can have changed
        updateRepositories();
    }

    @Override
    public void textChanged(IcyTextField source, boolean validate)
    {
        pluginsChanged();
    }

    @Override
    public void valueChanged(ListSelectionEvent e)
    {
        final int selected = table.getSelectedRow();

        if (!e.getValueIsAdjusting() && (selected != -1))
        {
            final int fi = e.getFirstIndex();
            final int li = e.getLastIndex();

            if ((fi == -1) || ((fi <= selected) && (li >= selected)))
                updateButtonsState();
        }
    }
}