/*******************************************************************************
 * PGPTool is a desktop application for pgp encryption/decryption
 * Copyright (C) 2019 Sergey Karpushin
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>
 ******************************************************************************/
package org.pgptool.gui.ui.historyquicksearch;

import static org.pgptool.gui.app.Messages.text;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.util.concurrent.ScheduledExecutorService;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.CompoundBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.jdesktop.swingx.imported.JXLabel;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.pgptool.gui.ui.decryptone.DecryptionDialogParameters;
import org.pgptool.gui.ui.tools.UiUtils;
import org.pgptool.gui.ui.tools.geometrymemory.TableColumnsGeometryPersister;
import org.pgptool.gui.ui.tools.geometrymemory.TableColumnsGeometryPersisterImpl;
import org.pgptool.gui.ui.tools.geometrymemory.WindowGeometryPersister;
import org.pgptool.gui.ui.tools.geometrymemory.WindowGeometryPersisterImpl;
import org.springframework.beans.factory.annotation.Autowired;

import com.google.common.base.Preconditions;

import ru.skarpushin.swingpm.EXPORT.base.DialogViewBase;
import ru.skarpushin.swingpm.base.ViewBase;
import ru.skarpushin.swingpm.bindings.TypedPropertyChangeListener;
import ru.skarpushin.swingpm.tools.sglayout.SgLayout;

public class HistoryQuickSearchView extends ViewBase<HistoryQuickSearchPm> {
	@Autowired
	private ScheduledExecutorService scheduledExecutorService;
	@Autowired
	private ConfigPairs uiGeom;

	private JFrame window;
	private WindowGeometryPersister windowGeometryPersister;

	private JPanel panelRoot;
	private JPanel panelTablePlaceholder;
	private JLabel lblNoDataToDisplay;

	private JScrollPane scrollPane;
	private JTable table;
	protected TableColumnsGeometryPersister tableColumnsGeometryPersister;
	private DefaultListSelectionModel selectionModel;

	private JPopupMenu ctxMenu;

	private JLabel tblLabel;
	private JTextField edQuickSearch;
	private JButton btnCancel;

	@Override
	protected void internalInitComponents() {
		SgLayout sgl = new SgLayout(2, 5, 0, 0);
		sgl.setColSize(0, 100, SgLayout.SIZE_TYPE_WEIGHTED);
		sgl.setColSize(1, UiUtils.getFontRelativeSize(text("action.cancel").length()), SgLayout.SIZE_TYPE_ASKCOMPONENT);
		sgl.setRowSize(3, UiUtils.getFontRelativeSize(26), SgLayout.SIZE_TYPE_WEIGHTED);
		panelRoot = new JPanel(sgl);

		CompoundBorder panelBorder = BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),
				BorderFactory.createEmptyBorder(5, 5, 5, 5));
		panelRoot.setBorder(panelBorder);

		panelRoot.add(new JLabel(text("phrase.typeFilenameToFilter")), sgl.cs(0, 0, 2, 1));
		edQuickSearch = new JTextField();
		edQuickSearch.addKeyListener(quickSearchKeyListener);
		JPanel pnlEditQuickSearch = new JPanel(new BorderLayout());
		pnlEditQuickSearch.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
		pnlEditQuickSearch.add(edQuickSearch, BorderLayout.CENTER);
		panelRoot.add(pnlEditQuickSearch, sgl.cs(0, 1, 2, 1));

		tblLabel = new JLabel("TBD");
		panelRoot.add(tblLabel, sgl.cs(0, 2, 2, 1));

		panelTablePlaceholder = new JPanel(new BorderLayout());
		panelRoot.add(panelTablePlaceholder, sgl.cs(0, 3, 2, 1));

		JXLabel hintLbl = new JXLabel(text("quicksearch.navigationHint"));
		hintLbl.setLineWrap(true);
		panelRoot.add(hintLbl, sgl.cs(0, 4));

		JPanel tmpPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		panelRoot.add(tmpPanel, sgl.cs(1, 4));
		tmpPanel.add(btnCancel = new JButton());

		ctxMenu = new JPopupMenu();
		initTableComponent();

		lblNoDataToDisplay = new JLabel(text("term.noDataToDisplay"));
		lblNoDataToDisplay.setHorizontalAlignment(JLabel.CENTER);
	}

	private KeyListener quickSearchKeyListener = new KeyAdapter() {
		@Override
		public void keyPressed(KeyEvent e) {
			int rowCount = table.getRowCount();
			if (rowCount == 0) {
				return;
			}

			if (e.getKeyCode() == KeyEvent.VK_DOWN) {
				int row = table.getSelectedRow();
				if (row < 0) {
					e.consume();
					table.setRowSelectionInterval(0, 0);
				} else if (row < rowCount - 1) {
					e.consume();
					table.setRowSelectionInterval(row + 1, row + 1);
				}
			} else if (e.getKeyCode() == KeyEvent.VK_UP) {
				int row = table.getSelectedRow();
				if (row < 0) {
					e.consume();
					table.setRowSelectionInterval(0, 0);
				} else if (row > 0) {
					e.consume();
					table.setRowSelectionInterval(row - 1, row - 1);
				}
			} else if (e.getKeyCode() == KeyEvent.VK_ENTER && table.getSelectedRow() >= 0) {
				e.consume();
				pm.actionOpen.actionPerformed(UiUtils.actionEvent(table, pm.actionOpen));
			} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
				pm.getActionCancel().actionPerformed(UiUtils.actionEvent(table, pm.getActionCancel()));
			}
		}
	};

	@SuppressWarnings("serial")
	private Action enterAction = new AbstractAction() {
		@Override
		public void actionPerformed(ActionEvent e) {
			pm.actionOpen.actionPerformed(e);
		}
	};

	private JScrollPane initTableComponent() {
		table = new JTable();

		// Adjust some visual appearance
		table.setRowHeight(22);
		table.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));

		// Add listeners
		selectionModel = new DefaultListSelectionModel();
		selectionModel.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
		selectionModel.addListSelectionListener((evt) -> pm.setSelected(getSelectedRow()));
		table.setSelectionModel(selectionModel);
		table.addMouseListener(listMouseListener);

		table.getActionMap().put("confirmRow", enterAction);
		table.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "confirmRow");

		// Envelope in scrollpane
		scrollPane = new JScrollPane();
		scrollPane.setViewportView(table);
		return scrollPane;
	}

	private MouseAdapter listMouseListener = new MouseAdapter() {
		@Override
		public void mouseClicked(MouseEvent e) {
			super.mouseClicked(e);
			if (!isAttached()) {
				return;
			}

			if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
				pm.actionOpen.actionPerformed(UiUtils.actionEvent(table, pm.actionOpen));
				return;
			}
		}

		private void myPopupEvent(MouseEvent e) {
			updateTableSelection(e);
			if (ctxMenu.getComponentCount() > 0) {
				ctxMenu.show(e.getComponent(), e.getX(), e.getY());
			}
		}

		private void updateTableSelection(MouseEvent e) {
			if (e.getComponent() == table) {
				int r = table.rowAtPoint(e.getPoint());
				if (r >= 0 && r < table.getRowCount()) {
					table.setRowSelectionInterval(r, r);
				} else {
					table.clearSelection();
				}
			} else {
				table.clearSelection();
			}
		}

		@Override
		public void mousePressed(MouseEvent e) {
			if (e.isPopupTrigger()) {
				myPopupEvent(e);
			}
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (e.isPopupTrigger()) {
				myPopupEvent(e);
			}
		}
	};

	private DecryptionDialogParameters getSelectedRow() {
		int row = table.getSelectedRow();
		if (row < 0) {
			return null;
		}

		// NOTE: This is a internal convention that by columnIndex -1 we mean
		// object itself
		TableModel tableModel = pm.getRowsTableModel().getValue();
		if (tableModel == null) {
			return null;
		}
		return (DecryptionDialogParameters) tableModel.getValueAt(row, -1);
	}

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

		tableModelChangeHandler.handlePropertyChanged(this, pm.getRowsTableModel().getPropertyName(), null,
				pm.getRowsTableModel().getValue());
		bindingContext.registerOnChangeHandler(pm.getRowsTableModel(), tableModelChangeHandler);

		bindingContext.setupBinding(pm.getTableLabel(), tblLabel);
		bindingContext.setupBinding(pm.getQuickSearch(), edQuickSearch);

		bindingContext.setupBinding(pm.getActionCancel(), btnCancel);

		bindContextMenu();
	}

	private void bindContextMenu() {
		if (pm.contextMenuActions != null) {
			for (Action action : pm.contextMenuActions) {
				if (action == null) {
					ctxMenu.addSeparator();
				} else {
					ctxMenu.add(action);
				}
			}
		}
	}

	private TypedPropertyChangeListener<TableModel> tableModelChangeHandler = new TypedPropertyChangeListener<TableModel>() {
		@Override
		public void handlePropertyChanged(Object source, String propertyName, TableModel oldValue,
				TableModel newValue) {
			if (newValue == null || newValue.getRowCount() == 0) {
				table.setModel(new DefaultTableModel());
				panelTablePlaceholder.removeAll();
				panelTablePlaceholder.add(lblNoDataToDisplay, BorderLayout.CENTER);
				panelTablePlaceholder.revalidate();
				panelTablePlaceholder.repaint();
				return;
			}

			if (tableColumnsGeometryPersister != null) {
				tableColumnsGeometryPersister.detach();
				tableColumnsGeometryPersister = null;
			}
			table.setModel(newValue);
			table.setRowSelectionInterval(0, 0);
			tableColumnsGeometryPersister = new TableColumnsGeometryPersisterImpl(table, "tblHstQSrch", uiGeom,
					scheduledExecutorService);
			tableColumnsGeometryPersister.restoreColumnsConfig();

			table.repaint();
			panelTablePlaceholder.removeAll();
			panelTablePlaceholder.add(scrollPane, BorderLayout.CENTER);
			panelTablePlaceholder.revalidate();
			panelTablePlaceholder.repaint();
		}
	};

	@Override
	protected void internalUnbindFromPm() {
		super.internalUnbindFromPm();
		table.setModel(new DefaultTableModel());
		table.repaint();
		ctxMenu.removeAll();
	}

	@Override
	protected void internalRenderTo(Container owner, Object constraints) {
		Preconditions.checkArgument(owner == null);
		// Preconditions.checkArgument(constraints != null && constraints instanceof
		// Rectangle);
		Preconditions.checkState(pm != null, "PM is required for this view");

		if (window == null) {
			window = new JFrame(text("term.quickSearch"));
			window.setLayout(new BorderLayout());
			window.add(panelRoot, BorderLayout.CENTER);
			window.setResizable(true);
			window.setMinimumSize(new Dimension(UiUtils.getFontRelativeSize(50), UiUtils.getFontRelativeSize(25)));

			windowGeometryPersister = new WindowGeometryPersisterImpl(window, "histQSrch", uiGeom,
					scheduledExecutorService);
			if (!windowGeometryPersister.restoreSize()) {
				window.pack();
			}

			window.addComponentListener(componentAdapter);
			window.addWindowListener(windowAdapter);
			DialogViewBase.installEscapeCloseOperation(window, window.getRootPane());
		}

		Point currentMousePosition = MouseInfo.getPointerInfo().getLocation();
		window.setLocation(findBestWindowLocation(currentMousePosition));

		window.setVisible(true);
		window.setAlwaysOnTop(true);
	}

	private WindowAdapter windowAdapter = new WindowAdapter() {
		@Override
		public void windowDeactivated(java.awt.event.WindowEvent e) {
			if (!isAttached()) {
				return;
			}

			pm.getActionCancel().actionPerformed(UiUtils.actionEvent(e.getSource(), "windowDeactivated"));
		};
	};

	private ComponentListener componentAdapter = new ComponentAdapter() {
		@Override
		public void componentHidden(ComponentEvent e) {
		}

		@Override
		public void componentShown(ComponentEvent e) {
			edQuickSearch.requestFocusInWindow();
		}
	};

	private Point findBestWindowLocation(Point pt) {
		Rectangle screenBounds = findGraphicsDeviceByPoint(pt);
		if (screenBounds == null) {
			return pt;
		}

		final int w = window.getWidth();
		final int h = window.getHeight();
		if (screenBounds.width < w || screenBounds.height < h) {
			return pt;
		}
		int x = pt.x;
		int y = pt.y;

		int xboundary = screenBounds.x + screenBounds.width;
		if (x + w > xboundary) {
			x = pt.x - (x + w - xboundary);
		}

		int yboundary = screenBounds.y + screenBounds.height;
		if (y + h > yboundary) {
			y = pt.y - (y + h - yboundary);
		}

		return new Point(x, y);
	}

	public static Rectangle findGraphicsDeviceByPoint(Point location) {
		// Check if the location is in the bounds of one of the graphics
		// devices.
		GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice[] graphicsDevices = graphicsEnvironment.getScreenDevices();
		Rectangle bounds = new Rectangle();

		// Iterate over the graphics devices.
		for (int j = 0; j < graphicsDevices.length; j++) {
			// Get the bounds of the device.
			GraphicsDevice graphicsDevice = graphicsDevices[j];
			bounds.setRect(graphicsDevice.getDefaultConfiguration().getBounds());

			// Is the location in this bounds?
			bounds.setRect(bounds.x, bounds.y, bounds.width, bounds.height);
			if (bounds.contains(location.x, location.y)) {
				// The location is in this screengraphics.
				return bounds;
			}
		}

		// We could not find a device that contains the given point.
		return null;
	}

	@Override
	protected void internalUnrender() {
		if (window != null) {
			window.setVisible(false);
		}
	}

}