// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software.  Please visit our Web site:
//
//                       http://www.tightvnc.com/
//
// 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 2 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.viewer.swing.gui;

import com.glavsoft.viewer.mvp.View;
import com.glavsoft.viewer.settings.ConnectionParams;
import com.glavsoft.viewer.settings.WrongParameterException;
import com.glavsoft.viewer.swing.ConnectionPresenter;
import com.glavsoft.viewer.swing.Utils;
import com.glavsoft.viewer.swing.ViewerEventsListener;
import com.glavsoft.viewer.swing.ssh.SshConnectionManager;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.LinkedList;

/**
 * Dialog window for connection parameters get from.
 */
@SuppressWarnings("serial")
public class ConnectionDialogView extends JPanel implements View, ConnectionView {
    private static final int PADDING = 4;
    private static final int COLUMNS_HOST_FIELD = 30;
    private static final int COLUMNS_PORT_USER_FIELD = 13;
    private static final String CLOSE = "Close";
    private static final String CANCEL = "Cancel";
    private ViewerEventsListener onCloseListener;
	private final boolean hasSshSupport;
    private final JTextField serverPortField;
	private JCheckBox useSshTunnelingCheckbox;
	private final JComboBox serverNameCombo;
    private JTextField sshUserField;
    private JTextField sshHostField;
    private JTextField sshPortField;
    private JLabel sshUserLabel;
    private JLabel sshHostLabel;
    private JLabel sshPortLabel;
    private JLabel ssUserWarningLabel;
    private JButton clearHistoryButton;
    private JButton connectButton;
    private final JFrame view;
    private final ConnectionPresenter presenter;
    private final StatusBar statusBar;
    private boolean connectionInProgress;
    private JButton closeCancelButton;

    public ConnectionDialogView(final ViewerEventsListener onCloseListener,
                                final ConnectionPresenter presenter) {
        this.onCloseListener = onCloseListener;
		this.hasSshSupport = SshConnectionManager.checkForSshSupport();
        this.presenter = presenter;

        setLayout(new BorderLayout(0, 0));
		JPanel optionsPane = new JPanel(new GridBagLayout());
		add(optionsPane, BorderLayout.CENTER);
		optionsPane.setBorder(new EmptyBorder(PADDING, PADDING, PADDING, PADDING));

		setLayout(new GridBagLayout());

		int gridRow = 0;

        serverNameCombo = new JComboBox();
        initConnectionsHistoryCombo();
        serverNameCombo.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                Object item = serverNameCombo.getSelectedItem();
                if (item instanceof ConnectionParams) {
                    presenter.populateFromHistoryItem((ConnectionParams) item);
                }
            }
        });

        addFormFieldRow(optionsPane, gridRow, new JLabel("Remote Host:"), serverNameCombo, true);
        ++gridRow;

        serverPortField = new JTextField(COLUMNS_PORT_USER_FIELD);

        addFormFieldRow(optionsPane, gridRow, new JLabel("Port:"), serverPortField, false);
        ++gridRow;

        if (this.hasSshSupport) {
			gridRow = createSshOptions(optionsPane, gridRow);
		}

        JPanel buttonPanel = createButtons();

		GridBagConstraints cButtons = new GridBagConstraints();
		cButtons.gridx = 0; cButtons.gridy = gridRow;
		cButtons.weightx = 100; cButtons.weighty = 100;
		cButtons.gridwidth = 2; cButtons.gridheight = 1;
		optionsPane.add(buttonPanel, cButtons);

        view = new JFrame("New TightVNC Connection");
        view.add(this, BorderLayout.CENTER);
        statusBar = new StatusBar();
        view.add(statusBar, BorderLayout.SOUTH);

        view.getRootPane().setDefaultButton(connectButton);
        view.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent windowEvent) {
                super.windowClosing(windowEvent);
                onCloseListener.onViewerComponentClosing();
            }
        });
//        view.setResizable(false);
        Utils.decorateDialog(view);
		Utils.centerWindow(view);
	}

    private void initConnectionsHistoryCombo() {
        serverNameCombo.setEditable(true);

        new AutoCompletionComboEditorDocument(serverNameCombo); // use autocompletion feature for ComboBox
        serverNameCombo.setRenderer(new HostnameComboboxRenderer());

        ConnectionParams prototypeDisplayValue = new ConnectionParams();
        prototypeDisplayValue.hostName = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
        serverNameCombo.setPrototypeDisplayValue(prototypeDisplayValue);
    }

    @Override
    public void showReconnectDialog(final String title, final String message) {
        int val = JOptionPane.showConfirmDialog(view,
                message + "\nTry another connection?",
                title,
                JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
        if (JOptionPane.NO_OPTION == val) {
            presenter.setNeedReconnection(false);
            closeView();
            view.dispose();
            closeApp();
        } else {
            // TODO return when allowInteractive, close window otherwise
//                forceConnectionDialog = allowInteractive;
        }
    }

    public void setConnectionInProgress(boolean enable) {
        if (enable) {
            connectionInProgress = true;
            closeCancelButton.setText(CANCEL);
            connectButton.setEnabled(false);
        } else {
            connectionInProgress = false;
            closeCancelButton.setText(CLOSE);
            connectButton.setEnabled(true);
        }
    }

    private JPanel createButtons() {
        JPanel buttonPanel = new JPanel();

        closeCancelButton = new JButton(CLOSE);
        closeCancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (connectionInProgress) {
                    presenter.cancelConnection();
                    setConnectionInProgress(false);
                } else {
                    closeView();
                    closeApp();
                }
            }
        });

        connectButton = new JButton("Connect");
        buttonPanel.add(connectButton);
        connectButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setMessage("");
                Object item = serverNameCombo.getSelectedItem();
                String hostName = item instanceof ConnectionParams ?
                        ((ConnectionParams) item).hostName :
                        (String) item;
                try {
                    setConnectionInProgress(true);
                    presenter.submitConnection(hostName);
                } catch (WrongParameterException wpe) {
                    if (ConnectionPresenter.PROPERTY_HOST_NAME.equals(wpe.getPropertyName())) {
                        serverNameCombo.requestFocusInWindow();
                    }
                    if (ConnectionPresenter.PROPERTY_RFB_PORT_NUMBER.equals(wpe.getPropertyName())) {
                        serverPortField.requestFocusInWindow();
                    }
                    showConnectionErrorDialog(wpe.getMessage());
                    setConnectionInProgress(false);
                }
            }
        });

        JButton optionsButton = new JButton("Options...");
        buttonPanel.add(optionsButton);
        optionsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                OptionsDialog od = new OptionsDialog(view);
                od.initControlsFromSettings(presenter.getRfbSettings(), presenter.getUiSettings(), true);
                od.setVisible(true);
                view.toFront();
            }
        });

        clearHistoryButton = new JButton("Clear history");
        clearHistoryButton.setToolTipText("Clear connections history");
        buttonPanel.add(clearHistoryButton);
        clearHistoryButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                presenter.clearHistory();
                clearHistoryButton.setEnabled(false);
                view.toFront();
            }
        });
        buttonPanel.add(closeCancelButton);
        return buttonPanel;
    }

	private int createSshOptions(JPanel pane, int gridRow) {
		GridBagConstraints cUseSshTunnelLabel = new GridBagConstraints();
		cUseSshTunnelLabel.gridx = 0; cUseSshTunnelLabel.gridy = gridRow;
		cUseSshTunnelLabel.weightx = 100; cUseSshTunnelLabel.weighty = 100;
		cUseSshTunnelLabel.gridwidth = 2; cUseSshTunnelLabel.gridheight = 1;
		cUseSshTunnelLabel.anchor = GridBagConstraints.LINE_START;
		cUseSshTunnelLabel.ipadx = PADDING;
		cUseSshTunnelLabel.ipady = 10;
		useSshTunnelingCheckbox = new JCheckBox("Use SSH tunneling");
		pane.add(useSshTunnelingCheckbox, cUseSshTunnelLabel);
		++gridRow;

        sshHostLabel = new JLabel("SSH Server:");
        sshHostField = new JTextField(COLUMNS_HOST_FIELD);
        addFormFieldRow(pane, gridRow, sshHostLabel, sshHostField, true);
        ++gridRow;

        sshPortLabel = new JLabel("SSH Port:");
        sshPortField = new JTextField(COLUMNS_PORT_USER_FIELD);
        addFormFieldRow(pane, gridRow, sshPortLabel, sshPortField, false);
        ++gridRow;

        sshUserLabel = new JLabel("SSH User:");
        sshUserField = new JTextField(COLUMNS_PORT_USER_FIELD);
        JPanel sshUserFieldPane = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
        sshUserFieldPane.add(sshUserField);
        ssUserWarningLabel = new JLabel(" (will be asked if not specified)");
        sshUserFieldPane.add(ssUserWarningLabel);
        addFormFieldRow(pane, gridRow, sshUserLabel, sshUserFieldPane, false);
        ++gridRow;

        useSshTunnelingCheckbox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                final boolean useSsh = e.getStateChange() == ItemEvent.SELECTED;
                setUseSsh(useSsh);
                presenter.setUseSsh(useSsh);
            }
        });

        return gridRow;
	}

    private void addFormFieldRow(JPanel pane, int gridRow, JLabel label, JComponent field, boolean fill) {
        GridBagConstraints cLabel = new GridBagConstraints();
        cLabel.gridx = 0; cLabel.gridy = gridRow;
        cLabel.weightx = 0;
        cLabel.weighty = 100;
        cLabel.gridwidth = cLabel.gridheight = 1;
        cLabel.anchor = GridBagConstraints.LINE_END;
        cLabel.ipadx = PADDING;
        cLabel.ipady = 10;
        pane.add(label, cLabel);

        GridBagConstraints cField = new GridBagConstraints();
        cField.gridx = 1; cField.gridy = gridRow;
        cField.weightx = 0; cField.weighty = 100;
        cField.gridwidth = cField.gridheight = 1;
        cField.anchor = GridBagConstraints.LINE_START;
        if (fill) cField.fill = GridBagConstraints.HORIZONTAL;
        pane.add(field, cField);
    }

    /*
     * Implicit View interface
     */
    public void setMessage(String message) {
        statusBar.setMessage(message);
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setPortNumber(int portNumber) {
        serverPortField.setText(String.valueOf(portNumber));
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getPortNumber() {
        return serverPortField.getText();
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setSshHostName(String sshHostName) {
        if (hasSshSupport) {
            sshHostField.setText(sshHostName);
        }
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getSshHostName() {
        if (hasSshSupport) {
            return sshHostField.getText();
        } else { return ""; }
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setSshPortNumber(int sshPortNumber) {
        if (hasSshSupport) {
            sshPortField.setText(String.valueOf(sshPortNumber));
        }
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getSshPortNumber() {
        if (hasSshSupport) {
            return sshPortField.getText();
        } else { return ""; }
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setSshUserName(String sshUserName) {
        if (hasSshSupport) {
            sshUserField.setText(sshUserName);
        }
    }

    @SuppressWarnings("UnusedDeclaration")
    public String getSshUserName() {
        if (hasSshSupport) {
            return sshUserField.getText();
        } else { return ""; }
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setUseSsh(boolean useSsh) {
        if (hasSshSupport) {
            useSshTunnelingCheckbox.setSelected(useSsh);
            sshUserLabel.setEnabled(useSsh);
            sshUserField.setEnabled(useSsh);
            ssUserWarningLabel.setEnabled(useSsh);
            sshHostLabel.setEnabled(useSsh);
            sshHostField.setEnabled(useSsh);
            sshPortLabel.setEnabled(useSsh);
            sshPortField.setEnabled(useSsh);
        }
    }

    @SuppressWarnings("UnusedDeclaration")
    public boolean getUseSsh() {
        return useSshTunnelingCheckbox.isSelected();
    }

    @SuppressWarnings("UnusedDeclaration")
    public void setConnectionsList(LinkedList<ConnectionParams> connections) {
        serverNameCombo.removeAllItems();
        for (ConnectionParams cp : connections) {
            serverNameCombo.addItem(new ConnectionParams(cp));
        }
        serverNameCombo.setPopupVisible(false);
        clearHistoryButton.setEnabled(serverNameCombo.getItemCount() > 0);
    }
    /*
     * /Implicit View interface
     */

    @Override
    public void showView() {
        view.setVisible(true);
        view.toFront();
        view.repaint();
    }

    @Override
    public void closeView() {
        view.setVisible(false);
    }

    @Override
    public void showConnectionErrorDialog(final String message) {
        JOptionPane.showMessageDialog(view, message, "Connection error", JOptionPane.ERROR_MESSAGE);
    }

    @Override
    public void closeApp() {
        if (onCloseListener != null) {
            onCloseListener.onViewerComponentClosing();
        }
    }

    @Override
    public JFrame getFrame() {
        return view;
    }

}

class StatusBar extends JPanel {

    private JLabel messageLabel;

    StatusBar() {
        setLayout(new BorderLayout());
        setPreferredSize(new Dimension(10, 23));

        messageLabel = new JLabel("");
        final Font f = messageLabel.getFont();
        messageLabel.setFont(f.deriveFont(f.getStyle() & ~Font.BOLD));
        add(messageLabel, BorderLayout.CENTER);

        JPanel rightPanel = new JPanel(new BorderLayout());
        rightPanel.setOpaque(false);


        add(rightPanel, BorderLayout.EAST);
        setBorder(new Border() {
            @Override
            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
                Color oldColor = g.getColor();
                g.translate(x, y);
                g.setColor(c.getBackground().darker());
                g.drawLine(0, 0, width -1, 0);
                g.setColor(c.getBackground().brighter());
                g.drawLine(0, 1, width -1, 1);
                g.translate(-x, -y);
                g.setColor(oldColor);
            }
            @Override
            public Insets getBorderInsets(Component c) {
                return new Insets(2, 2, 2, 2);
            }
            @Override
            public boolean isBorderOpaque() {
                return false;
            }
        });
    }

    public void setMessage(String message) {
        messageLabel.setText(message);
    }
}
