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

import net.azib.ipscan.config.LoggerFactory;
import net.azib.ipscan.config.Version;
import net.azib.ipscan.core.ScanningSubject;
import net.azib.ipscan.util.InetAddressUtils;

import java.io.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static java.util.logging.Level.WARNING;
import static net.azib.ipscan.util.InetAddressUtils.HOSTNAME_REGEX;

/**
 * Feeder, taking IP addresses from text files in any format.
 * It uses regular expressions for matching of IP addresses.
 *
 * @author Anton Keks
 */
public class FileFeeder extends AbstractFeeder {
	
	private static final Pattern PORT_REGEX = Pattern.compile("\\d{1,5}\\b");
	
	static final Logger LOG = LoggerFactory.getLogger();
	
	/** Found IP address Strings are put here */
	private Map<String, ScanningSubject> foundHosts;
	private Iterator<ScanningSubject> foundIPAddressesIterator;
	private Stream<NetworkInterface> networkInterfaces;
	
	private int currentIndex;

	public String getId() {
		return "feeder.file";
	}
	
	public FileFeeder() {
		try {
			networkInterfaces = NetworkInterface.networkInterfaces();
		}
		catch (SocketException e) {
			LOG.log(WARNING, "", e);
		}
	}
	
	public FileFeeder(String fileName) {
		this();
		try {
			findHosts(new FileReader(fileName));
		}
		catch (FileNotFoundException e) {
			throw new FeederException("file.notExists");
		}
	}

	public FileFeeder(Reader reader) {
		this();
		findHosts(reader);
	}

	private String readLines(BufferedReader fileReader, int num) throws IOException {
		var index = 1;
		var sb = new StringBuilder();
		String fileLine;
		while ((fileLine = fileReader.readLine()) != null) {
			sb.append(fileLine).append("\n");
			index++;
			if (index > num) break;
		}
		return sb.toString();
	}

	private void findHosts(Reader reader) {
		currentIndex = 0;
		foundHosts = new LinkedHashMap<>();
		var startTime = System.currentTimeMillis();

		try (var fileReader = new BufferedReader(reader)) {
			String fileLine;
			while (!(fileLine = readLines(fileReader, 20)).isEmpty()) {
				var lineTime = System.currentTimeMillis();
				var matcher = HOSTNAME_REGEX.matcher(fileLine);
				while (matcher.find()) {
					try {
						var host = matcher.group();
						if (host.equals(Version.OWN_HOST)) continue;
						var subject = foundHosts.get(host);
						if (subject == null) {
							var address = InetAddress.getByName(host);
							subject = new ScanningSubject(address, InetAddressUtils.getInterface(address, networkInterfaces));
						}
						
						if (!matcher.hitEnd() && fileLine.charAt(matcher.end()) == ':') {
							// see if any valid port is requested
							var portMatcher = PORT_REGEX.matcher(fileLine.substring(matcher.end()+1));
							if (portMatcher.lookingAt()) {
								subject.addRequestedPort(Integer.valueOf(portMatcher.group()));
							}
						}
						
						foundHosts.put(host, subject);
					}
					catch (UnknownHostException e) {
						LOG.log(WARNING, e.toString());
					}
				}
			}
			LOG.fine("File processed in " + (System.currentTimeMillis() - startTime) + " ms");
			if (foundHosts.isEmpty()) {
				throw new FeederException("file.nothingFound");
			}
		}
		catch (IOException e) {
			throw new FeederException("file.errorWhileReading");
		}

		foundIPAddressesIterator = foundHosts.values().iterator();
	}
	
	public int percentageComplete() {
		return Math.round((float) currentIndex * 100 / foundHosts.size());
	}

	public boolean hasNext() {
		return foundIPAddressesIterator.hasNext();
	}

	public ScanningSubject next() {
		currentIndex++;
		return foundIPAddressesIterator.next();
	}

	public String getInfo() {
		// let's return the number of found addresses
		return Integer.toString(foundHosts.size());
	}
}
