/*
 * Zed Attack Proxy (ZAP) and its related class files.
 * 
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 * 
 * Copyright 2010 psiinon@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.zaproxy.zap.extension.httpsessions;

import java.awt.CardLayout;
import java.awt.Event;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;

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.JToolBar;
import javax.swing.KeyStroke;

import org.jdesktop.swingx.JXTable;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.AbstractPanel;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.utils.SortedComboBoxModel;
import org.zaproxy.zap.view.ScanPanel;

/**
 * The HttpSessionsPanel used as a display panel for the {@link ExtensionHttpSessions}, allowing the
 * user to view and control the http sessions.
 */
public class HttpSessionsPanel extends AbstractPanel {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The Constant PANEL_NAME. */
	public static final String PANEL_NAME = "httpsessions";

	/** The extension. */
	private ExtensionHttpSessions extension = null;

	private JPanel panelCommand = null;
	private JToolBar panelToolbar = null;
	private JScrollPane jScrollPane = null;
	private JComboBox<String> siteSelect = null;
	private JButton newSessionButton = null;
	private JXTable sessionsTable = null;
	private JButton optionsButton = null;

	/** The current site. */
	private String currentSite = null;

	/** The site model. */
	private SortedComboBoxModel<String> siteModel = new SortedComboBoxModel<>();

	/** The sessions model. */
	private HttpSessionsTableModel sessionsModel = new HttpSessionsTableModel(null);

	/**
	 * Instantiates a new http session panel.
	 * 
	 * @param extensionHttpSession the extension http session
	 */
	public HttpSessionsPanel(ExtensionHttpSessions extensionHttpSession) {
		super();
		this.extension = extensionHttpSession;
		initialize();
	}

	/**
	 * This method initializes this panel.
	 */
	private void initialize() {
		this.setLayout(new CardLayout());
		this.setSize(474, 251);
		this.setName(Constant.messages.getString("httpsessions.panel.title"));
		this.setIcon(new ImageIcon(HttpSessionsPanel.class.getResource("/resource/icon/16/session.png")));
		this.setDefaultAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_H, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | Event.ALT_MASK | Event.SHIFT_MASK, false));
		this.setMnemonic(Constant.messages.getChar("httpsessions.panel.mnemonic"));
		this.add(getPanelCommand(), getPanelCommand().getName());
	}

	/**
	 * This method initializes the main panel.
	 * 
	 * @return javax.swing.JPanel
	 */
	private javax.swing.JPanel getPanelCommand() {
		if (panelCommand == null) {
			panelCommand = new javax.swing.JPanel();
			panelCommand.setLayout(new java.awt.GridBagLayout());
			panelCommand.setName(Constant.messages.getString("httpsessions.panel.title"));

			// Add the two components: toolbar and work pane
			GridBagConstraints toolbarGridBag = new GridBagConstraints();
			GridBagConstraints workPaneGridBag = new GridBagConstraints();

			toolbarGridBag.gridx = 0;
			toolbarGridBag.gridy = 0;
			toolbarGridBag.weightx = 1.0d;
			toolbarGridBag.insets = new java.awt.Insets(2, 2, 2, 2);
			toolbarGridBag.anchor = java.awt.GridBagConstraints.NORTHWEST;
			toolbarGridBag.fill = java.awt.GridBagConstraints.HORIZONTAL;

			workPaneGridBag.gridx = 0;
			workPaneGridBag.gridy = 1;
			workPaneGridBag.weightx = 1.0;
			workPaneGridBag.weighty = 1.0;
			workPaneGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			workPaneGridBag.anchor = java.awt.GridBagConstraints.NORTHWEST;
			workPaneGridBag.fill = java.awt.GridBagConstraints.BOTH;

			panelCommand.add(this.getPanelToolbar(), toolbarGridBag);
			panelCommand.add(getWorkPane(), workPaneGridBag);
		}
		return panelCommand;
	}

	/**
	 * Gets the options button.
	 * 
	 * @return the options button
	 */
	private JButton getOptionsButton() {
		if (optionsButton == null) {
			optionsButton = new JButton();
			optionsButton.setToolTipText(Constant.messages.getString("httpsessions.toolbar.options.button"));
			optionsButton.setIcon(new ImageIcon(ScanPanel.class.getResource("/resource/icon/16/041.png")));
			optionsButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					Control.getSingleton().getMenuToolsControl()
							.options(Constant.messages.getString("httpsessions.options.title"));
				}
			});
		}
		return optionsButton;
	}

	/**
	 * Gets the new session button.
	 * 
	 * @return the new session button
	 */
	private JButton getNewSessionButton() {
		if (newSessionButton == null) {
			newSessionButton = new JButton();
			newSessionButton.setText(Constant.messages.getString("httpsessions.toolbar.newsession.label"));
			newSessionButton.setIcon(new ImageIcon(HttpSessionsPanel.class.getResource("/resource/icon/16/103.png")));
			newSessionButton.setToolTipText(Constant.messages.getString("httpsessions.toolbar.newsession.tooltip"));

			newSessionButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					HttpSessionsSite site = getCurrentHttpSessionSite();
					if (site != null) {
						site.createEmptySession();
					}
				}
			});

		}
		return newSessionButton;
	}

	/**
	 * Gets the panel's toolbar.
	 * 
	 * @return the panel toolbar
	 */
	private javax.swing.JToolBar getPanelToolbar() {
		if (panelToolbar == null) {

			// Initialize the toolbar
			panelToolbar = new javax.swing.JToolBar();
			panelToolbar.setLayout(new java.awt.GridBagLayout());
			panelToolbar.setEnabled(true);
			panelToolbar.setFloatable(false);
			panelToolbar.setRollover(true);
			panelToolbar.setPreferredSize(new java.awt.Dimension(800, 30));
			panelToolbar.setName("HttpSessionToolbar");

			// Add elements
			GridBagConstraints labelGridBag = new GridBagConstraints();
			GridBagConstraints siteSelectGridBag = new GridBagConstraints();
			GridBagConstraints newSessionGridBag = new GridBagConstraints();
			GridBagConstraints emptyGridBag = new GridBagConstraints();
			GridBagConstraints optionsGridBag = new GridBagConstraints();

			labelGridBag.gridx = 0;
			labelGridBag.gridy = 0;
			labelGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			labelGridBag.anchor = java.awt.GridBagConstraints.WEST;

			siteSelectGridBag.gridx = 1;
			siteSelectGridBag.gridy = 0;
			siteSelectGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			siteSelectGridBag.anchor = java.awt.GridBagConstraints.WEST;

			newSessionGridBag.gridx = 2;
			newSessionGridBag.gridy = 0;
			newSessionGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			newSessionGridBag.anchor = java.awt.GridBagConstraints.WEST;

			emptyGridBag.gridx = 3;
			emptyGridBag.gridy = 0;
			emptyGridBag.weightx = 1.0;
			emptyGridBag.weighty = 1.0;
			emptyGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			emptyGridBag.anchor = java.awt.GridBagConstraints.WEST;
			emptyGridBag.fill = java.awt.GridBagConstraints.HORIZONTAL;

			optionsGridBag.gridx = 4;
			optionsGridBag.gridy = 0;
			optionsGridBag.insets = new java.awt.Insets(0, 0, 0, 0);
			optionsGridBag.anchor = java.awt.GridBagConstraints.EAST;

			JLabel label = new JLabel(Constant.messages.getString("httpsessions.toolbar.site.label"));

			panelToolbar.add(label, labelGridBag);
			panelToolbar.add(getSiteSelect(), siteSelectGridBag);
			panelToolbar.add(getNewSessionButton(), newSessionGridBag);
			panelToolbar.add(getOptionsButton(), optionsGridBag);

			// Add an empty JLabel to fill the space
			panelToolbar.add(new JLabel(), emptyGridBag);
		}
		return panelToolbar;
	}

	/**
	 * Gets the work pane where data is shown.
	 * 
	 * @return the work pane
	 */
	private JScrollPane getWorkPane() {
		if (jScrollPane == null) {
			jScrollPane = new JScrollPane();
			jScrollPane.setViewportView(getHttpSessionsTable());
		}
		return jScrollPane;
	}

	/**
	 * Sets the sessions table column sizes.
	 */
	private void setSessionsTableColumnSizes() {

		sessionsTable.getColumnModel().getColumn(0).setMinWidth(60);
		sessionsTable.getColumnModel().getColumn(0).setPreferredWidth(60); // active

		sessionsTable.getColumnModel().getColumn(1).setMinWidth(120);
		sessionsTable.getColumnModel().getColumn(1).setPreferredWidth(200); // name

		sessionsTable.getColumnModel().getColumn(3).setMinWidth(100);
		sessionsTable.getColumnModel().getColumn(3).setPreferredWidth(150); // matched
	}

	/**
	 * Gets the http sessions table.
	 * 
	 * @return the http sessions table
	 */
	private JXTable getHttpSessionsTable() {
		if (sessionsTable == null) {
			sessionsTable = new JXTable(sessionsModel);

			sessionsTable.setColumnSelectionAllowed(false);
			sessionsTable.setCellSelectionEnabled(false);
			sessionsTable.setRowSelectionAllowed(true);
			sessionsTable.setAutoCreateRowSorter(true);
			sessionsTable.setColumnControlVisible(true);

			this.setSessionsTableColumnSizes();

			sessionsTable.setName(PANEL_NAME);
			sessionsTable.setDoubleBuffered(true);
			sessionsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
			sessionsTable.addMouseListener(new java.awt.event.MouseAdapter() {
				@Override
				public void mousePressed(java.awt.event.MouseEvent e) {

					showPopupMenuIfTriggered(e);
				}
				@Override
				public void mouseReleased(java.awt.event.MouseEvent e) {
					showPopupMenuIfTriggered(e);
				}
				private void showPopupMenuIfTriggered(java.awt.event.MouseEvent e) {
					if (e.isPopupTrigger()) {

						// Select table item
						int row = sessionsTable.rowAtPoint(e.getPoint());
						if (row < 0 || !sessionsTable.getSelectionModel().isSelectedIndex(row)) {
							sessionsTable.getSelectionModel().clearSelection();
							if (row >= 0) {
								sessionsTable.getSelectionModel().setSelectionInterval(row, row);
							}
						}

						View.getSingleton().getPopupMenu().show(e.getComponent(), e.getX(), e.getY());
					}
				}
			});
		}
		return sessionsTable;
	}

	/**
	 * Gets the site select ComboBox.
	 * 
	 * @return the site select
	 */
	private JComboBox<String> getSiteSelect() {
		if (siteSelect == null) {
			siteSelect = new JComboBox<>(siteModel);
			siteSelect.addItem(Constant.messages.getString("httpsessions.toolbar.site.select"));
			siteSelect.setSelectedIndex(0);

			// Add the item listener for when the site is selected
			siteSelect.addItemListener(new ItemListener() {
				@Override
				public void itemStateChanged(ItemEvent e) {
					if (ItemEvent.SELECTED == e.getStateChange()) {
						if (siteSelect.getSelectedIndex() > 0) {
							siteSelected((String) e.getItem());

						} // If the user selects the first option (empty one), force the selection to
							// the first valid site
						else if (siteModel.getSize() > 1) {
							siteModel.setSelectedItem(siteModel.getElementAt(1));
						}
					}
				}
			});
		}
		return siteSelect;
	}

	/**
	 * Adds a new site to the "Http Sessions" tab.
	 * <p>
	 * The method must be called in the EDT, failing to do so might result in thread interference or memory consistency errors.
	 * </p>
	 * 
	 * @param site the site
	 * @see #addSiteAsynchronously(String)
	 * @see EventQueue
	 */
	public void addSite(String site) {
		if (siteModel.getIndexOf(site) < 0) {
			siteModel.addElement(site);
			if (currentSite == null) {
				// First site added, automatically select it
				siteModel.setSelectedItem(site);
			}
		}
	}

	/**
	 * Adds a new site, asynchronously, to the "Http Sessions" tab.
	 * <p>
	 * The call to this method will return immediately and the site will be added in the EDT (by calling the method
	 * {@code EventQueue#invokeLater(Runnable)}) after all pending events have been processed.
	 * </p>
	 *
	 * @param site the site
	 * @see #addSite(String)
	 * @see EventQueue#invokeLater(Runnable)
	 */
	public void addSiteAsynchronously(final String site) {
		EventQueue.invokeLater(new Runnable() {

			@Override
			public void run() {
				addSite(site);
			}
		});
	}

	/**
	 * A new Site was selected.
	 * 
	 * @param site the site
	 */
	private void siteSelected(String site) {
		if (!site.equals(currentSite)) {
			this.sessionsModel = extension.getHttpSessionsSite(site).getModel();
			this.getHttpSessionsTable().setModel(this.sessionsModel);

			this.setSessionsTableColumnSizes();

			currentSite = site;
		}
	}

	/**
	 * Node selected.
	 * 
	 * @param node the node
	 */
	public void nodeSelected(SiteNode node) {
		if (node != null) {
			siteModel.setSelectedItem(ScanPanel.cleanSiteName(node, true));
		}
	}

	/**
	 * Reset the panel.
	 */
	public void reset() {
		currentSite = null;

		siteModel.removeAllElements();
		siteModel.addElement(Constant.messages.getString("httpsessions.toolbar.site.select"));

		sessionsModel = new HttpSessionsTableModel(null);
		getHttpSessionsTable().setModel(sessionsModel);
	}

	/**
	 * Gets the current http session site.
	 * 
	 * @return the current http session site, or null if no HttpSessionSite is selected
	 */
	public HttpSessionsSite getCurrentHttpSessionSite() {
		if (currentSite == null) {
			return null;
		}
		return extension.getHttpSessionsSite(currentSite);
	}

	/**
	 * Gets the currently selected site.
	 *
	 * @return the current site
	 */
	public String getCurrentSite(){
		return currentSite;
	}
	
	/**
	 * Gets the selected http session.
	 * 
	 * @return the selected session, or null if nothing is selected
	 */
	public HttpSession getSelectedSession() {
		final int selectedRow = this.sessionsTable.getSelectedRow();
		if (selectedRow == -1) {
			// No row selected
			return null;
		}
		final int rowIndex = sessionsTable.convertRowIndexToModel(selectedRow);
		return this.sessionsModel.getHttpSessionAt(rowIndex);
	}
}
