package com.subgraph.orchid.circuits.path;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.subgraph.orchid.Router;
import com.subgraph.orchid.data.HexDigest;
import com.subgraph.orchid.data.IPv4Address;

/**
 * Implements configuration options:
 * 
 *   ExcludeNodes,ExcludeExitNodes,ExitNodes,EntryNodes
 *
 */
public class ConfigNodeFilter implements RouterFilter {

	private final static Pattern NETMASK_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)/(\\d+)$");
	private final static Pattern ADDRESS_BITS_PATTERN = Pattern.compile("^(\\d+\\.\\d+\\.\\d+\\.\\d+)/(\\d+)$");
	
	private final static Pattern IDENTITY_PATTERN = Pattern.compile("^[A-Fa-f0-9]{40}$");
	private final static Pattern COUNTRYCODE_PATTERN = Pattern.compile("^\\{([A-Za-z]{2})\\}$");
	private final static Pattern ROUTERNAME_PATTERN = Pattern.compile("^\\w{1,19}$");

	static class MaskFilter implements RouterFilter {

		private final int network;
		private final int bits;
		private final int mask;
		

		static int createMask(final int maskBitCount) {
			return maskBitCount == 0 ? 0 : (1 << 31) >> (maskBitCount - 1);
		}

		MaskFilter(IPv4Address network, int bits) {
			this.bits = bits;
			this.mask = createMask(bits);
			this.network = network.getAddressData() & mask;
		}
		
		public boolean filter(Router router) {
			final int routerAddress = router.getAddress().getAddressData();
			return (routerAddress & mask) == network;
		}
		
		public String toString() {
			IPv4Address a = new IPv4Address(network);
			return a.toString() + "/" + bits;
			
		}
	}
	
	static class IdentityFilter implements RouterFilter {
		private final HexDigest identity;
		IdentityFilter(HexDigest identity) {
			this.identity = identity;
		}
		public boolean filter(Router router) {
			return router.getIdentityHash().equals(identity);
		}
	}

	static class NameFilter implements RouterFilter {
		private final String name;
		NameFilter(String name) {
			this.name = name;
		}
		public boolean filter(Router router) {
			return name.equals(router.getNickname());
		}
	}
	
	static class CountryCodeFilter implements RouterFilter {
		private final String countryCode;
		public CountryCodeFilter(String countryCode) {
			this.countryCode = countryCode;
		}
		public boolean filter(Router router) {
			return countryCode.equalsIgnoreCase(router.getCountryCode());
		}
	}
	
	static boolean isAddressString(String s) {
		Matcher matcher = NETMASK_PATTERN.matcher(s);
		if(!matcher.matches()) {
			return false;
		}
		try {
			for(int i = 1; i < 5; i++) {
				if(!isValidOctetString(matcher.group(i))) {
					return false;
				}
			}
			return isValidMaskValue(matcher.group(5));
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	private static boolean isValidOctetString(String s) {
		int n = Integer.parseInt(s);
		return n >= 0 && n <= 255;
	}
	
	private static boolean isValidMaskValue(String s) {
		int n = Integer.parseInt(s);
		return n > 0 && n <= 32;
	}
	
	static boolean isIdentityString(String s) {
		return IDENTITY_PATTERN.matcher(s).matches();
	}
	
	static boolean isCountryCodeString(String s) {
		return COUNTRYCODE_PATTERN.matcher(s).matches();
	}
	
	static boolean isNameString(String s) {
		return ROUTERNAME_PATTERN.matcher(s).matches();
	}

	static RouterFilter createFilterFor(String s) {
		if(isAddressString(s)) {
			return createAddressFilter(s);
		} else if(isCountryCodeString(s)) {
			return createCountryCodeFilter(s);
		} else if(isIdentityString(s)) {
			return createIdentityFilter(s);
		} else if (isNameString(s)) {
			return createNameFilter(s);
		} else {
			return null;
		}
	}

	private static RouterFilter createAddressFilter(String s) {
		final Matcher matcher = ADDRESS_BITS_PATTERN.matcher(s);
		if(!matcher.matches()) {
			throw new IllegalArgumentException();
		}
		final IPv4Address network = IPv4Address.createFromString(matcher.group(1));
		final int bits = Integer.parseInt(matcher.group(2));
		return new MaskFilter(network, bits);
	}
	
	private static RouterFilter createIdentityFilter(String s) {
		if(isIdentityString(s)) {
			throw new IllegalArgumentException();
		}
		final HexDigest identity = HexDigest.createFromString(s);
		return new IdentityFilter(identity);
	}
	
	private static RouterFilter createCountryCodeFilter(String s) {
		final Matcher matcher = COUNTRYCODE_PATTERN.matcher(s);
		if(!matcher.matches()) {
			throw new IllegalArgumentException();
		}
		return new CountryCodeFilter(matcher.group(1));
	}
	
	private static RouterFilter createNameFilter(String s) {
		if(!isNameString(s)) {
			throw new IllegalArgumentException();
		}
		return new NameFilter(s);
	}

	static ConfigNodeFilter createFromStrings(List<String> stringList) {
		final List<RouterFilter> filters = new ArrayList<RouterFilter>();
		for(String s: stringList) {
			RouterFilter f = createFilterFor(s);
			if(f != null) {
				filters.add(f);
			}
		}
		return new ConfigNodeFilter(filters);
	}

	private final List<RouterFilter> filterList;
	
	private ConfigNodeFilter(List<RouterFilter> filterList) {
		this.filterList = filterList;
	}

	public boolean filter(Router router) {
		for(RouterFilter f: filterList) {
			if(f.filter(router)) {
				return true;
			}
		}
		return false;
	}
	
	boolean isEmpty() {
		return filterList.isEmpty();
	}
}

@@ -4113,16 +4113,17 @@
  %7B%0A%09%09if(
+!
 isIdenti

Fix for Identity String in ConfigNodeFilter