/*
  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.fetchers;

import net.azib.ipscan.config.LoggerFactory;
import net.azib.ipscan.config.ScannerConfig;
import net.azib.ipscan.core.ScanningResult.ResultType;
import net.azib.ipscan.core.ScanningSubject;
import net.azib.ipscan.gui.fetchers.PortTextFetcherPrefs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.Thread.currentThread;
import static java.util.Collections.singleton;
import static net.azib.ipscan.fetchers.PortsFetcher.PARAMETER_OPEN_PORTS;

/**
 * PortTextFetcher - generic configurable fetcher to read some particular information from a port.
 *
 * @author Anton Keks
 */
public abstract class PortTextFetcher extends AbstractFetcher {
	private static final Logger LOG = LoggerFactory.getLogger();
	
	private ScannerConfig scannerConfig;

	private int defaultPort;
	protected boolean scanOpenPorts;
	protected String textToSend;
	protected Pattern matchingRegexp;
	protected int extractGroup;
	
	public PortTextFetcher(ScannerConfig scannerConfig, int defaultPort, String defaultTextToSend, String matchingRegexp) {
		this.scannerConfig = scannerConfig;
		this.defaultPort = defaultPort;
		this.textToSend = getPreferences().get("textToSend", defaultTextToSend);
		this.matchingRegexp = Pattern.compile(getPreferences().get("matchingRegexp", matchingRegexp));
		this.extractGroup = getPreferences().getInt("extractGroup", 1);
	}

	public Object scan(ScanningSubject subject) {
		var portIterator = getPortIterator(subject);

		while (portIterator.hasNext() && !currentThread().isInterrupted()) {
			try (var socket = new Socket()) {
				socket.connect(new InetSocketAddress(subject.getAddress(), portIterator.next()), subject.getAdaptedPortTimeout());
				socket.setTcpNoDelay(true);
				socket.setSoTimeout(scannerConfig.portTimeout * 2);
				socket.setSoLinger(true, 0);

				socket.getOutputStream().write(textToSend.getBytes());

				var in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String line;
				while ((line = in.readLine()) != null) {
					var matcher = matchingRegexp.matcher(line);
					if (matcher.find()) {
						// mark that additional info is available
						subject.setResultType(ResultType.WITH_PORTS);
						// return the required contents
						return getResult(matcher, socket.getPort());
					}
				}
			}
			catch (ConnectException e) {
				// no connection
			}
			catch (SocketTimeoutException e) {
				// no information
			}
			catch (SocketException e) {
				// connection reset
			}
			catch (IOException e) {
				LOG.log(Level.FINE, subject.getAddress().toString(), e);
			}
		}
		return null;
	}

	protected String getResult(Matcher matcher, int port) {
		var result = matcher.group(extractGroup);
		return result.isEmpty() ? String.valueOf(port) : result;
	}

	private Iterator<Integer> getPortIterator(ScanningSubject subject) {
		if (scanOpenPorts) {
			@SuppressWarnings("unchecked")
			var openPorts = (SortedSet<Integer>) subject.getParameter(PARAMETER_OPEN_PORTS);
			if (openPorts != null) {
				SortedSet<Integer> ports = new TreeSet<>(openPorts);
				ports.add(defaultPort);
				return ports.iterator();
			}
		}
		return subject.isAnyPortRequested() ? subject.requestedPortsIterator() : singleton(defaultPort).iterator();
	}

	@Override
	public Class<? extends FetcherPrefs> getPreferencesClass() {
		return PortTextFetcherPrefs.class;
	}
	
	public String getTextToSend() {
		return textToSend;
	}

	public void setTextToSend(String textToSend) {
		this.textToSend = textToSend;
	}

	public Pattern getMatchingRegexp() {
		return matchingRegexp;
	}

	public void setMatchingRegexp(Pattern matchingRegexp) {
		this.matchingRegexp = matchingRegexp;
	}

	public int getExtractGroup() {
		return extractGroup;
	}

	public void setExtractGroup(int extractGroup) {
		this.extractGroup = extractGroup;
	}
}
