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

import net.azib.ipscan.config.Platform;
import net.azib.ipscan.feeders.Feeder;
import net.azib.ipscan.feeders.FeederException;
import net.azib.ipscan.feeders.RangeFeeder;
import net.azib.ipscan.gui.actions.FeederActions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

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

import static net.azib.ipscan.config.Labels.getLabel;
import static net.azib.ipscan.util.InetAddressUtils.*;

/**
 * GUI for initialization of RangeFeeder.
 * 
 * @author Anton Keks
 */
public class RangeFeederGUI extends AbstractFeederGUI {
	private Text startIPText;
	private Text endIPText;
	private Text hostnameText;
	private Combo netmaskCombo;

	private boolean isEndIPUnedited = true;
	private boolean modifyListenersDisabled = false;

	public RangeFeederGUI(FeederArea parent) {
		super(parent);
		feeder = new RangeFeeder();
	}

	public void initialize() {
		setLayout(new GridLayout(5, false));

		var ipRangeLabel = new Label(this, SWT.NONE);
        startIPText = new Text(this, SWT.BORDER);
		var toLabel = new Label(this, SWT.NONE);
        endIPText = new Text(this, SWT.BORDER);
		var hostnameLabel = new Label(this, SWT.NONE);
        hostnameText = new Text(this, SWT.BORDER);
		var ipUpButton = new Button(this, SWT.NONE);
        netmaskCombo = new Combo(this, SWT.NONE);

		// the longest possible IP
        startIPText.setText("255.255.255.255xx");
		var textWidth = startIPText.computeSize(-1, -1).x;
        startIPText.setText("");
		startIPText.setLayoutData(new GridData(textWidth, -1));
		endIPText.setLayoutData(new GridData(textWidth, -1));
		hostnameText.setLayoutData(new GridData(textWidth, -1));
		netmaskCombo.setLayoutData(new GridData(textWidth, -1));

		((GridData)endIPText.getLayoutData()).horizontalSpan = 2;
		var ipUpData = new GridData(); ipUpData.horizontalSpan = 2;
		ipUpButton.setLayoutData(ipUpData);

        ipRangeLabel.setText(getLabel("feeder.range") + ":");
		ipRangeLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
		hostnameLabel.setText(getLabel("feeder.range.hostname") + ":");
		hostnameLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));

        toLabel.setText(getLabel("feeder.range.to"));

		startIPText.addModifyListener(new StartIPModifyListener());
        endIPText.addModifyListener(new EndIPModifyListener());

		var hostnameListener = new FeederActions.HostnameButton(hostnameText, startIPText, netmaskCombo) {
			public void widgetSelected(SelectionEvent event) {
				isEndIPUnedited = true;
				netmaskCombo.setText(getLabel("feeder.range.netmask"));
				super.widgetSelected(event);
			}

			protected void setInterfaceAddress(InterfaceAddress ifAddr) {
				afterLocalHostInfoFilled(ifAddr);
			}
        };
        
        hostnameText.addTraverseListener(hostnameListener);
		hostnameText.setToolTipText(getLabel("feeder.range.hostname.tooltip"));
		
		Listener netmaskResetListener = new NetmaskResetListener();
		startIPText.addListener(SWT.Modify, netmaskResetListener);
		endIPText.addListener(SWT.Modify, netmaskResetListener);

		ipUpButton.setText(getLabel("button.ipUp"));
		ipUpButton.addSelectionListener(hostnameListener);

        netmaskCombo.setText(getLabel("feeder.range.netmask"));
		netmaskCombo.setVisibleItemCount(10);
		netmaskCombo.add("/26");
		netmaskCombo.add("/24");
		netmaskCombo.add("/16");
		// Warning: IPv4 specific netmasks
		netmaskCombo.add("255...192");
		netmaskCombo.add("255...128");
		netmaskCombo.add("255...0");
		netmaskCombo.add("255..0.0");
		netmaskCombo.add("255.0.0.0");
		var netmaskSelectionListener = new NetmaskListener();
		netmaskCombo.addListener(SWT.Selection, netmaskSelectionListener);
		netmaskCombo.addListener(SWT.Traverse, netmaskSelectionListener);
		netmaskCombo.setToolTipText(getLabel("feeder.range.netmask.tooltip"));

		pack();
		var comboBounds = netmaskCombo.getBounds();
		var endIPBounds = endIPText.getBounds();
		var width = endIPBounds.x + endIPBounds.width - comboBounds.x - 5;
		if (Platform.WINDOWS) width -= 22; // TODO: remove width of down arrow, this number may change with updated SWT version
		if (Platform.MAC_OS) width += 10;
		((GridData) netmaskCombo.getLayoutData()).widthHint = width;
		pack();

		// do this stuff asynchronously (to show GUI faster)
		asyncFillLocalHostInfo(hostnameText, startIPText);
	}

	public Feeder createFeeder() {
		return feeder = new RangeFeeder(startIPText.getText().trim(), endIPText.getText().trim());
	}
	
	public String[] serialize() {
		return new String[] {startIPText.getText().trim(), endIPText.getText().trim()};
	}

	public void unserialize(String[] parts) {
		// TODO: netmask support from the command-line
		startIPText.setText(parts[0]);
		endIPText.setText(parts[1]);
		netmaskCombo.setText(getLabel("feeder.range.netmask"));
	}

	public String[] serializePartsLabels() {
		return new String[] {"feeder.range.startIP", "feeder.range.endIP"};
	}

	final class EndIPModifyListener implements ModifyListener {
		@Override public void modifyText(ModifyEvent modifyEvent) {
			isEndIPUnedited = false;
		}
	}
	
	final class NetmaskResetListener implements Listener {
		public void handleEvent(Event event) {
			// reset the netmask combo
			if (!modifyListenersDisabled)
				netmaskCombo.setText(getLabel("feeder.range.netmask"));
		}
	}

	final class StartIPModifyListener implements ModifyListener {
		public void modifyText(ModifyEvent e) {
			if (isEndIPUnedited) endIPText.setText(startIPText.getText());
		}
	}

	final class NetmaskListener implements Listener {
		public void handleEvent(Event event) {
			if (event.type == SWT.Traverse) {
				// skip any other traversal besides RETURN
				if (event.detail != SWT.TRAVERSE_RETURN) 
					return;
				event.doit = false;
			}
			if (event.type == SWT.Selection) {
				// workaround for GTK: this event is fired after keypresses, but we want it to be fired
				// only if something is selected from the drop down
				if (netmaskCombo.indexOf(netmaskCombo.getText()) < 0) return;

				// workaround for Windows: selection event is fired when the dropdown is opened
				if (Platform.WINDOWS && netmaskCombo.getListVisible()) return;
			}
			
			try {
				var netmaskString = netmaskCombo.getText();
				var startIP = InetAddress.getByName(startIPText.getText());
				updateStartEndWithNetmask(startIP, netmaskString);
			}
			catch (UnknownHostException e) {
				throw new FeederException("invalidNetmask");
			}
			
			if (event.type == SWT.Traverse) {
				// try to focus the start button
				getParent().forceFocus();
			} 
			else {
				netmaskCombo.forceFocus();
			}
		}
	}

	private void updateStartEndWithNetmask(InetAddress ip, String netmaskString) {
		try {
			var netmask = parseNetmask(netmaskString);
			modifyListenersDisabled = true;
			startIPText.setText(startRangeByNetmask(ip, netmask).getHostAddress());
			endIPText.setText(endRangeByNetmask(ip, netmask).getHostAddress());
			modifyListenersDisabled = false;
			isEndIPUnedited = false;
		}
		catch (UnknownHostException e) {
			LOG.fine(e.toString());
		}
	}

	@Override protected void afterLocalHostInfoFilled(InterfaceAddress ifAddr) {
		var address = ifAddr.getAddress();
		if (!address.isLoopbackAddress()) {
			updateStartEndWithNetmask(address, "/" + ifAddr.getNetworkPrefixLength());
			isEndIPUnedited = true;
		}
	}
}
