package net.azib.ipscan.util;

import org.junit.Test;

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

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@SuppressWarnings("ConstantConditions")
public class InetAddressUtilsTest {

	@Test
	public void hostnameMatching() throws Exception {
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("127.0.0.1").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("192.168.245.345").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("255.255.255.255").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("0.0.0.0").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("8.8.8.8").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("::").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("::1").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("fe80::1234").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("fe80:1234::").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("::fe80:1234").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("::fe80").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("fe80::").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("::fe80").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("2001:db8::").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("2001:db8::1").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("8.8.8.8").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("a.bc").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("angryip.org").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("www.example.com").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("example.com.").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("example.dn42.").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("example.dn42").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("beta.wiki.dn42").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("A.B.CD").matches());
		assertTrue(InetAddressUtils.HOSTNAME_REGEX.matcher("me.local").matches());

		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("3.5").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("3.5.1").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("127.0.0.256").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("256.256.255.1").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("a.b").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("abc").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("123").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("Hello world.").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("fe80:").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher(":fe80").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("2001:db8").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("2001:db8:").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher(":2001:db8:").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher(":2001:db8").matches());
		assertFalse(InetAddressUtils.HOSTNAME_REGEX.matcher("fe80::g").matches());
	}

	@Test
	public void findIPs() throws Exception {
		var matcher = InetAddressUtils.HOSTNAME_REGEX.matcher("Hello, my IP is 10.10.10.123, not 128.92.34.56. Isn't it cool?");
		assertTrue(matcher.find());
		assertEquals("10.10.10.123", matcher.group());
		assertTrue(matcher.find());
		assertEquals("128.92.34.56", matcher.group());
		assertFalse(matcher.find());
	}

	@Test
	public void findHostnames() throws Exception {
		var matcher = InetAddressUtils.HOSTNAME_REGEX.matcher("Angry IP Scanner's official site is https://angryip.org, not http://www.angryziber.com. Isn't it cool?");
		assertTrue(matcher.find());
		assertEquals("angryip.org", matcher.group());
		assertTrue(matcher.find());
		assertEquals("www.angryziber.com.", matcher.group());
		assertFalse(matcher.find());
	}

	@Test
	public void testStartRangeByNetmask() throws UnknownHostException {
		assertEquals("127.0.1.64", InetAddressUtils.startRangeByNetmask(
				InetAddress.getByName("127.0.1.92"), 
				InetAddress.getByName("255.255.255.192")).getHostAddress());
		assertEquals("127.0.0.15", InetAddressUtils.startRangeByNetmask(
				InetAddress.getByName("127.0.0.15"), 
				InetAddress.getByName("255.255.255.255")).getHostAddress());
		assertEquals("192.10.0.0", InetAddressUtils.startRangeByNetmask(
				InetAddress.getByName("192.10.11.13"), 
				InetAddress.getByName("255.255.0.0")).getHostAddress());
	}

	@Test
	public void testEndRangeByNetmask() throws UnknownHostException {
		assertEquals("127.0.1.127", InetAddressUtils.endRangeByNetmask(
				InetAddress.getByName("127.0.1.92"), 
				InetAddress.getByName("255.255.255.192")).getHostAddress());
		assertEquals("127.0.0.15", InetAddressUtils.endRangeByNetmask(
				InetAddress.getByName("127.0.0.15"), 
				InetAddress.getByName("255.255.255.255")).getHostAddress());
		assertEquals("192.10.255.255", InetAddressUtils.endRangeByNetmask(
				InetAddress.getByName("192.10.11.13"), 
				InetAddress.getByName("255.255.0.0")).getHostAddress());
	}
	
	@Test
	public void testIncrement() throws UnknownHostException {
		assertEquals("127.0.0.2", InetAddressUtils.increment(InetAddress.getByName("127.0.0.1")).getHostAddress());
		assertEquals("128.0.0.0", InetAddressUtils.increment(InetAddress.getByName("127.255.255.255")).getHostAddress());
		assertEquals("0.0.0.0", InetAddressUtils.increment(InetAddress.getByName("255.255.255.255")).getHostAddress());
	}

	@Test
	public void testGreaterThan() throws UnknownHostException {
		assertTrue(InetAddressUtils.greaterThan(InetAddress.getByName("127.0.0.1"), InetAddress.getByName("127.0.0.0")));
		assertTrue(InetAddressUtils.greaterThan(InetAddress.getByName("129.0.0.1"), InetAddress.getByName("128.0.0.0")));
		assertTrue(InetAddressUtils.greaterThan(InetAddress.getByName("255.0.0.0"), InetAddress.getByName("254.255.255.255")));
		assertFalse(InetAddressUtils.greaterThan(InetAddress.getByName("0.0.0.0"), InetAddress.getByName("255.255.255.255")));
		assertFalse(InetAddressUtils.greaterThan(InetAddress.getByName("0.0.0.0"), InetAddress.getByName("0.0.0.0")));
		assertFalse(InetAddressUtils.greaterThan(InetAddress.getByName("127.0.0.1"), InetAddress.getByName("127.0.5.0")));
	}	
	
	@Test
	public void testParseNetmask() throws UnknownHostException {
		assertEquals("255.255.255.255", InetAddressUtils.parseNetmask("255.255.255.255").getHostAddress());
		assertEquals("255.255.255.255", InetAddressUtils.parseNetmask("255...255").getHostAddress());
		assertEquals("255.0.255.255", InetAddressUtils.parseNetmask("255.0..255").getHostAddress());
		assertEquals("255.255.255.192", InetAddressUtils.parseNetmask("255...192").getHostAddress());
		assertEquals("255.0.255.0", InetAddressUtils.parseNetmask("255.0..0").getHostAddress());
		assertEquals("0.0.0.0", InetAddressUtils.parseNetmask("0.0.0.0").getHostAddress());

		assertEquals("0.0.0.0", InetAddressUtils.parseNetmask("/0").getHostAddress());
		assertEquals("128.0.0.0", InetAddressUtils.parseNetmask("/1").getHostAddress());
		assertEquals("255.255.0.0", InetAddressUtils.parseNetmask("/16").getHostAddress());
		assertEquals("255.255.255.0", InetAddressUtils.parseNetmask("/24").getHostAddress());
		assertEquals("255.255.255.128", InetAddressUtils.parseNetmask("/25").getHostAddress());
		assertEquals("255.255.255.248", InetAddressUtils.parseNetmask("/29").getHostAddress());
		assertEquals("255.255.255.255", InetAddressUtils.parseNetmask("/32").getHostAddress());

		// IPv6
		assertEquals("ffff:ffff:ffff:ffff:0:0:0:0", InetAddressUtils.parseNetmask("/64").getHostAddress());
	}
	
	@Test
	public void testMaskPrototypeBytes() throws UnknownHostException {
		var bytes = InetAddress.getByName("32.23.34.254").getAddress();
		InetAddressUtils.maskPrototypeAddressBytes(bytes, InetAddress.getByName("255.0.0.255").getAddress(), InetAddress.getByName("29.1.2.255").getAddress());
		assertEquals("29.23.34.255", InetAddress.getByAddress(bytes).getHostAddress());
		
		bytes = InetAddress.getByName("250.250.250.250").getAddress();
		InetAddressUtils.maskPrototypeAddressBytes(bytes, InetAddress.getByName("0.0.0.0").getAddress(), InetAddress.getByName("29.1.2.255").getAddress());
		assertEquals("250.250.250.250", InetAddress.getByAddress(bytes).getHostAddress());

		bytes = InetAddress.getByName("250.250.250.250").getAddress();
		InetAddressUtils.maskPrototypeAddressBytes(bytes, InetAddress.getByName("255.255.255.255").getAddress(), InetAddress.getByName("29.128.127.73").getAddress());
		assertEquals("29.128.127.73", InetAddress.getByAddress(bytes).getHostAddress());
	}
	
	@Test
	public void testIsLikelyBroadcast() throws UnknownHostException {
		assertTrue(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("127.0.2.0"), null));
		assertTrue(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("127.6.32.255"), null));
		assertFalse(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("127.4.5.6"), null));

		var ifAddr = mock(InterfaceAddress.class);
		when(ifAddr.getAddress()).thenReturn(InetAddress.getByName("192.168.0.1"));
		when(ifAddr.getBroadcast()).thenReturn(InetAddress.getByName("192.168.0.127"));
		assertTrue(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("192.168.0.127"), ifAddr));
		assertTrue(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("192.168.0.0"), ifAddr));
		assertFalse(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("192.168.0.1"), ifAddr));
		assertFalse(InetAddressUtils.isLikelyBroadcast(InetAddress.getByName("192.168.0.126"), ifAddr));
	}
}
