/**
 * This file is a part of Angry IP Scanner source code,
 * see http://www.azib.net/ for more information.
 * Licensed under GPLv2.
 */
package net.azib.ipscan.gui.actions;

import java.net.InetAddress;
import java.net.UnknownHostException;

import net.azib.ipscan.config.CommentsConfig;
import net.azib.ipscan.config.Labels;
import net.azib.ipscan.config.OpenersConfig;
import net.azib.ipscan.config.OpenersConfig.Opener;
import net.azib.ipscan.core.ScanningResultList;
import net.azib.ipscan.core.UserErrorException;
import net.azib.ipscan.core.state.ScanningState;
import net.azib.ipscan.core.state.StateMachine;
import net.azib.ipscan.fetchers.CommentFetcher;
import net.azib.ipscan.fetchers.FetcherRegistry;
import net.azib.ipscan.gui.DetailsWindow;
import net.azib.ipscan.gui.EditOpenersDialog;
import net.azib.ipscan.gui.InputDialog;
import net.azib.ipscan.gui.ResultTable;
import net.azib.ipscan.gui.StatusBar;
import net.azib.ipscan.gui.actions.ToolsActions.TableSelection;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;

/**
 * Commands and Context menu Actions.
 * All these operate on the items, selected in the results list.
 *
 * @author Anton Keks
 */
public class CommandsActions {
	
	/**
	 * Checks that there is at least one item selected in the results list.
	 * @param mainWindow
	 */
	static void checkSelection(ResultTable resultTable) {
		if (resultTable.getItemCount() <= 0) {
			throw new UserErrorException("commands.noResults");
		}
		else
		if (resultTable.getSelectionIndex() < 0) {
			throw new UserErrorException("commands.noSelection");
		}
	}

	public static class Details implements Listener {
		private final ResultTable resultTable;
		private final DetailsWindow detailsWindow;
		
		public Details(ResultTable resultTable, DetailsWindow detailsWindow) {
			this.resultTable = resultTable;
			this.detailsWindow = detailsWindow;
			resultTable.addListener(SWT.Traverse, this);
			resultTable.addListener(SWT.MouseDoubleClick, this);
		}

		public void handleEvent(Event event) {
			// activate only if something is selected
			if (event.type == SWT.Selection || (resultTable.getSelectionIndex() >= 0 && (event.type == SWT.MouseDoubleClick || event.detail == SWT.TRAVERSE_RETURN))) {
				event.doit = false;
				checkSelection(resultTable);
				detailsWindow.open(); 
			}
		}
	}
	
	public static final class Delete implements Listener {
		private final ResultTable resultTable;
		private final StateMachine stateMachine;
		private final TableSelection selectionListener;
		
		public Delete(ResultTable resultTable, StateMachine stateMachine, TableSelection selectionListener) {
			this.resultTable = resultTable;
			this.stateMachine = stateMachine;
			this.selectionListener = selectionListener;
		}

		public void handleEvent(Event event) {
			// ignore other keys if this is a KeyDown event - 
			// the same listener is used for several events
			if (event.type == SWT.KeyDown && event.keyCode != SWT.DEL)
				return;
			// deletion not allowed when scanning
			if (!stateMachine.inState(ScanningState.IDLE))
				return;
			
			checkSelection(resultTable);
			int firstSelection = resultTable.getSelectionIndex();
			resultTable.remove(resultTable.getSelectionIndices());
			resultTable.setSelection(firstSelection);
			// reset status text about multiple selection
			event.widget = resultTable;
			selectionListener.handleEvent(event);
		}
	}

	/** 
	 * This cannot be accessed from the menu, but it provides the Ctrl+A
	 * Select All functionality for Windows (other platforms implement this themselves)
	 */
	public static class SelectAll implements Listener {
		private final ResultTable resultTable;

		public SelectAll(ResultTable resultTable) {
			this.resultTable = resultTable;
		}

		public void handleEvent(Event event) {
			// Ctrl+A handler
			if (event.type == SWT.KeyDown && event.keyCode == 'a' && event.stateMask == SWT.MOD1) {
				resultTable.selectAll();
				event.doit = false;
			}
		}
	}

	public static final class Rescan implements Listener {
		private final ResultTable resultTable;
		private final StateMachine stateMachine;
		
		public Rescan(ResultTable resultTable, StateMachine stateMachine) {
			this.resultTable = resultTable;
			this.stateMachine = stateMachine;
		}

		public void handleEvent(Event event) {
			checkSelection(resultTable);
			stateMachine.rescan();
		}
	}
	
	/**
	 * Copies currently selected IP to the clipboard.
	 * Used as both menu item listener and key down listener.
	 */
	public static final class CopyIP implements Listener {
		private final ResultTable resultTable;
		
		public CopyIP(ResultTable resultTable) {
			this.resultTable = resultTable;
		}

		public void handleEvent(Event event) {
			if (event.type == SWT.KeyDown) {
				// if this is not Ctrl+C or nothing is selected, then simply do nothing
				if ((event.keyCode != 'c' && event.stateMask != SWT.MOD1) || resultTable.getSelectionIndex() < 0)
					return;
			}
			else {
				// if selected from the menu, check selection
				checkSelection(resultTable);
			}
			Clipboard clipboard = new Clipboard(event.display);
			clipboard.setContents(new Object[] {resultTable.getItem(resultTable.getSelectionIndex()).getText()}, new Transfer[] {TextTransfer.getInstance()});
			clipboard.dispose();
		}
	}
	
	public static final class CopyIPDetails implements Listener {
		private final ResultTable resultTable;
		
		public CopyIPDetails(ResultTable resultTable) {
			this.resultTable = resultTable;
		}

		public void handleEvent(Event event) {
			checkSelection(resultTable);
			Clipboard clipboard = new Clipboard(event.display);
			clipboard.setContents(new Object[] {resultTable.getIPDetails()}, new Transfer[] {TextTransfer.getInstance()});
			clipboard.dispose();
		}
	}
	
	public static final class ShowOpenersMenu implements Listener {
		
		private final Listener openersSelectListener;
		private final OpenersConfig openersConfig;

		public ShowOpenersMenu(OpenersConfig openersConfig, SelectOpener selectOpener) {
			this.openersConfig = openersConfig;
			this.openersSelectListener = selectOpener;
		}

		public void handleEvent(Event event) {
			Menu openersMenu = (Menu)event.widget;
			MenuItem[] menuItems = openersMenu.getItems();
			for (int i = 2; i < menuItems.length; i++) {
				menuItems[i].dispose();
			}
			
			// update menu items
			int index = 0;
			for (String name : openersConfig) {
				MenuItem menuItem = new MenuItem(openersMenu, SWT.CASCADE);
				
				index++;
				if (index <= 9) {
					name += "\tCtrl+" + index;
					menuItem.setAccelerator(SWT.MOD1 | ('0' + index));
				}
				
				menuItem.setText(name);
				menuItem.setData(new Integer(index));
				menuItem.addListener(SWT.Selection, openersSelectListener);
			}

		}
	}
	
	public static final class EditComment implements Listener {
		private final ResultTable resultTable;
		private final ScanningResultList results;
		private final CommentsConfig commentsConfig;
		private final FetcherRegistry fetcherRegistry;
		
		public EditComment(ResultTable resultTable, ScanningResultList results, CommentsConfig commentsConfig, FetcherRegistry fetcherRegistry) {
			this.resultTable = resultTable;
			this.results = results;
			this.commentsConfig = commentsConfig;
			this.fetcherRegistry = fetcherRegistry;
		}

		public void handleEvent(Event event) {
			checkSelection(resultTable);
			try {
				int index = resultTable.getSelectionIndex();
				InetAddress address = InetAddress.getByName(resultTable.getItem(index).getText());
				String comment = commentsConfig.getComment(address);
				String newComment = new InputDialog(Labels.getLabel("title.editComment"), Labels.getLabel("text.editComment")).open(comment);
				if (newComment != null) {
					commentsConfig.setComment(address, newComment);
					// now update the result table for user to immediately see the change
					int fetcherIndex = fetcherRegistry.getSelectedFetcherIndex(CommentFetcher.LABEL);
					if (fetcherIndex >= 0) {
						// update the value in the results
						results.getResult(index).setValue(fetcherIndex, newComment);
						// update visual representation
						resultTable.clear(index);
					}
				}
			}
			catch (UnknownHostException e) {
				// should not happen
			}
		}
	}
	
	public static final class EditOpeners implements Listener {
		
		private final FetcherRegistry fetcherRegistry;
		private final OpenersConfig openersConfig;

		public EditOpeners(FetcherRegistry fetcherRegistry, OpenersConfig openersConfig) {
			this.fetcherRegistry = fetcherRegistry;
			this.openersConfig = openersConfig;
		}

		public void handleEvent(Event event) {
			new EditOpenersDialog(fetcherRegistry, openersConfig).open(); 
		}
	}
	
	public static final class SelectOpener implements Listener {
		
		private final StatusBar statusBar;
		private final ResultTable resultTable;
		private final OpenerLauncher openerLauncher;
		private final OpenersConfig openersConfig;
		
		public SelectOpener(OpenersConfig openersConfig, StatusBar statusBar, ResultTable resultTable, OpenerLauncher openerLauncher) {
			this.openersConfig = openersConfig;
			this.statusBar = statusBar;
			this.resultTable = resultTable;
			this.openerLauncher = openerLauncher;
		}
		
		public void handleEvent(Event event) {
			MenuItem menuItem = (MenuItem) event.widget;
			String name = menuItem.getText();
			int indexOf = name.lastIndexOf('\t');
			if (indexOf >= 0) {
				name = name.substring(0, indexOf);
			}
			Opener opener = openersConfig.getOpener(name);
			
			int selectedItem = resultTable.getSelectionIndex();
			if (selectedItem < 0) {
				throw new UserErrorException("commands.noSelection");
			}				
					
			try {
				statusBar.setStatusText(Labels.getLabel("state.opening") + name);
				openerLauncher.launch(opener, selectedItem);
				// wait a bit to make status visible
				// TODO: somehow wait until the process is started
				Thread.sleep(500);
			}
			catch (InterruptedException e) {}
			finally {
				statusBar.setStatusText(null);
			}
		}
	}

}
