package de.renew.gui.configure;

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.Vector;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;

import de.renew.application.SimulatorPlugin;
import de.renew.net.NetInstance;
import de.renew.net.NetInstanceList;
import de.renew.plugin.PluginManager;
import de.renew.remote.NetInstanceAccessor;
import de.renew.remote.RemotePlugin;
import de.renew.remote.RemoteServerRegistry;
import de.renew.simulator.api.SimulationManager;


/**
 * @version 1.0
 * @author Timo Carl {@literal <6carl@informatik.uni-hamburg.de>}
 */
class RemoteServerWindow extends JFrame {
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(RemoteServerWindow.class);
    /**
     * Used to store instance of String or {@link RemoteServerRegistry.ServerDescriptor},
     * so we cannot narrow down the type from Object.
     */
    private JComboBox<Object> _servers;
    /**
     * Used to store {@link NetInstanceAccessor} or {@link WeakNetInstanceWrapper} for some reason,
     * so we cant narrow down the generic type currently.
     */
    private JList<Object> _instancesList;
    protected JButton _openButton;
    protected JButton _disconnectButton;
    private final ConnectDialog _dialog;

    RemoteServerWindow() {
        super("Remote Renew Servers");
        initGui();
        _dialog = new ConnectDialog(this);
    }

    private void initGui() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        Container root = getContentPane();
        root.setLayout(new GridBagLayout());
        GridBagConstraints gbc;

        // list of all connected Servers
        _servers = new JComboBox<>();
        _servers.setEditable(false);
        gbc = new GridBagConstraints(
            0, 0, 2, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
            new Insets(5, 5, 0, 5), 1, 1);
        root.add(_servers, gbc);

        // connect button
        JButton connect = new JButton("Connect...");
        connect.addActionListener(e -> _dialog.setVisible(true));
        gbc = new GridBagConstraints(
            0, 1, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
            new Insets(5, 5, 0, 5), 1, 1);
        root.add(connect, gbc);

        // disconnect button
        _disconnectButton = new JButton("Disconnect");
        gbc = new GridBagConstraints(
            1, 1, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
            new Insets(5, 5, 0, 5), 1, 1);
        root.add(_disconnectButton, gbc);

        // list of net instances
        _instancesList = new JList<>();
        _instancesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        _instancesList.addListSelectionListener(
            e -> _openButton.setEnabled(!_instancesList.isSelectionEmpty()));
        _instancesList.setCellRenderer(new MyCellRenderer());
        JScrollPane scroll = new JScrollPane(_instancesList);
        scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        gbc = new GridBagConstraints(
            0, 2, 2, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
            new Insets(5, 5, 0, 5), 1, 1);
        root.add(scroll, gbc);

        // open net button
        _openButton = new JButton("Open net");
        _openButton.setEnabled(false);
        gbc = new GridBagConstraints(
            0, 3, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
            new Insets(5, 5, 5, 5), 1, 1);
        root.add(_openButton, gbc);

        // close Button
        JButton close = new JButton("Close");
        close.addActionListener(e -> dispose());
        gbc = new GridBagConstraints(
            1, 3, 1, 1, 1, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
            new Insets(5, 5, 5, 5), 1, 1);
        root.add(close, gbc);

        pack();
    }

    /**
     * Registers a listener that is triggered when a server selection occurs.
     *
     * @param listener the {@link ActionListener} to be added to listen for server selection events
     */
    void addServerSelectedListener(ActionListener listener) {
        _servers.addActionListener(listener);
    }

    /**
     * Registers a listener that is triggered when the disconnect button is clicked.
     *
     * @param listener the {@link ActionListener} to be added to the disconnect button
     */
    void addDisconnectListener(ActionListener listener) {
        _disconnectButton.addActionListener(listener);
    }

    /**
     * Registers a listener to the connect button of the dialog.
     *
     * @param listener the {@link ActionListener} to be added for the connect button
     */
    void addConnectListener(ActionListener listener) {
        _dialog._connectButton.addActionListener(listener);
    }

    /**
     * Registers a listener that is executed when a net instance is opened.
     * The listener is triggered either by a double-click event on the instances list
     * or by clicking the open button.
     *
     * @param listener a {@code Runnable} to execute when a net instance is opened.
     */
    void addOpenNetInstanceListener(Runnable listener) {
        _instancesList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    listener.run();
                }
            }
        });
        _openButton.addActionListener(e -> listener.run());
    }

    public void updateView() throws RemoteException {
        int index = _servers.getSelectedIndex();

        Object[] servers = RemoteServerRegistry.instance().allServers();
        Vector<Object> v = new Vector<>(servers.length + 1);
        v.add("local nets");
        v.addAll(Arrays.asList(servers));
        _servers.setModel(new DefaultComboBoxModel<>(v));
        if ((index < 0) && (!v.isEmpty())) {
            index = 0;
        } else if (index >= v.size()) {
            index = v.size() - 1;
        }
        _servers.setSelectedIndex(index);

        _disconnectButton.setEnabled(servers.length != 0 && _servers.getSelectedIndex() > 0);

        SimulatorPlugin simulatorPlugin =
            (SimulatorPlugin) PluginManager.getInstance().getPluginByName("Renew Simulator");
        if (index > 0) {
            NetInstanceAccessor[] nets = RemoteServerRegistry.instance().allNetInstances(index - 1);
            Arrays.sort(nets, (b1, b2) -> {
                try {
                    int number1 = Integer.parseInt(b1.getID());
                    int number2 = Integer.parseInt(b2.getID());
                    return Integer.compare(number1, number2);
                } catch (RemoteException e) {
                    logger.error("Exception while sorting nets", e);
                    return 0;
                }
            });
            _instancesList.setListData(nets);
        } else if (index == 0 && SimulationManager.isSimulationActive()) {
            NetInstance[] nets = NetInstanceList.getAll();
            WeakNetInstanceWrapper[] weakNets = new WeakNetInstanceWrapper[nets.length];
            RemotePlugin remote = RemotePlugin.getInstance();
            for (int i = 0; i < nets.length; ++i) {
                weakNets[i] = new WeakNetInstanceWrapper(remote.wrapInstance(nets[i]));
            }
            Arrays.sort(weakNets, (b1, b2) -> {
                try {
                    int number1 = Integer.parseInt(b1.getInstance().getID());
                    int number2 = Integer.parseInt(b2.getInstance().getID());
                    return Integer.compare(number1, number2);
                } catch (RemoteException e) {
                    logger.error("Exception while sorting nets", e);
                    return 0;
                }
            });
            _instancesList.setListData(weakNets);
        } else {
            _instancesList.setListData(new Object[0]);
        }
    }

    String getDialogName() {
        return _dialog._name.getText();
    }

    String getDialogServer() {
        return _dialog._server.getText();
    }

    void disposeDialog() {
        _dialog.dispose();
    }

    void selectServer(RemoteServerRegistry.ServerDescriptor server) {
        _servers.setSelectedItem(server);
    }

    int getSelectedServerIndex() {
        return _servers.getSelectedIndex();
    }

    int getSelectedNetInstanceIndex() {
        return _instancesList.getSelectedIndex();
    }

    Object getNetInstanceModelByIndex(int index) {
        return _instancesList.getModel().getElementAt(index);
    }

    class ConnectDialog extends JDialog {
        protected JTextField _server;
        protected JTextField _name;
        private JButton _connectButton;

        ConnectDialog(JFrame parent) {
            super(parent, "Connect to remote Renew server...", true);
            initConnectDialog();
        }

        private void initConnectDialog() {
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            Container root = getContentPane();
            root.setLayout(new GridBagLayout());

            JLabel l = new JLabel("Server ");
            GridBagConstraints gbc = new GridBagConstraints(
                0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
                new Insets(10, 10, 0, 5), 1, 1);
            root.add(l, gbc);
            _server = new JTextField("localhost", 20);
            gbc = new GridBagConstraints(
                1, 0, 2, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
                new Insets(5, 5, 0, 10), 1, 1);
            root.add(_server, gbc);

            l = new JLabel("Name ");
            gbc = new GridBagConstraints(
                0, 1, 1, 1, 0, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
                new Insets(5, 10, 0, 5), 1, 1);
            root.add(l, gbc);
            _name = new JTextField("default", 20);
            gbc = new GridBagConstraints(
                1, 1, 2, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
                new Insets(5, 5, 0, 10), 1, 1);
            root.add(_name, gbc);

            // connect button
            _connectButton = new JButton("Connect");
            gbc = new GridBagConstraints(
                0, 2, 2, 1, 1, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
                new Insets(5, 5, 10, 5), 1, 1);
            root.add(_connectButton, gbc);
            getRootPane().setDefaultButton(_connectButton);

            // cancel button
            JButton cancel = new JButton("Cancel");
            cancel.addActionListener(e -> dispose());
            gbc = new GridBagConstraints(
                2, 2, 1, 1, 0, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE,
                new Insets(5, 5, 10, 10), 1, 1);
            root.add(cancel, gbc);
            pack();
        }
    }

    static class MyCellRenderer extends DefaultListCellRenderer {
        @Override
        public Component getListCellRendererComponent(
            JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
        {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value instanceof NetInstanceAccessor accessor) {
                try {
                    String str = accessor.asString();
                    setText(str);
                } catch (RemoteException e) {
                    // FIXME: add a comment why this can be ignored
                    //  or add some handling/logging
                }
            }
            return this;
        }
    }

    static class WeakNetInstanceWrapper {
        final WeakReference<NetInstanceAccessor> instance;
        String description;

        WeakNetInstanceWrapper(NetInstanceAccessor net) {
            this.instance = new WeakReference<>(net);
            try {
                this.description = net.asString();
            } catch (RemoteException e) {
                this.description = e.toString();
            }
        }

        NetInstanceAccessor getInstance() {
            return instance.get();
        }

        @Override
        public String toString() {
            return description;
        }
    }
}