/* 
 * Copyright 2015 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.tr064.net;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import android.annotation.SuppressLint;
import android.os.Build;

import de.avm.android.tr064.Tr064Log;

public class NetworkInterfaceAddressHelper
{
	private static final String TAG = "NetIfaceAddressHelper";
	private static final String ERROR_ARG_IP =
			"Argument has to be a numeric IPv4 address";
	
	/**
	 * Try to match network interface for remoteAddress by
	 * inspecting the subnet masks of all interfaces
	 * Needs SDK 9 at least (otherwise returns null)!
	 * 
	 * @param remoteAddress
	 * 		numeric IPv4 address of subnet or within subnet
	 * @return
	 * 		address of network interface with matching subnet or null
	 */
	@SuppressLint("NewApi")
	public static InetAddress getInterfaceAddress(String remoteAddress)
	{
//		Log.d(TAG, "getInterfaceAddress(" + remoteAddress + ")");
		InetAddress result = null;
		
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
		{
			try
			{
				String[] addressStrings = remoteAddress.split("\\.");
				if (addressStrings.length != 4)
					throw new IllegalArgumentException(ERROR_ARG_IP);
				byte[] addressBytes = new byte[4];
				for(int ii = 0; ii < 4; ii++)
				{
					int part = Integer.parseInt(addressStrings[ii]);
					if ((part < 0) || (part > 255))
						throw new IllegalArgumentException(ERROR_ARG_IP);
					addressBytes[ii] = (byte)part;
				}
				int address = bytesToInt(addressBytes);
				
				List<InterfaceAddress> addresses = getInterfaceAddresses();
				result = getInterfaceAddress(addresses, address, 0);
				/*
				 * Unfortunately calling address.getNetworkPrefixLength() always
				 * returns 32 on VPN interfaces with site local addresses.
				 * So we have no network mask to match with. Retry with networks
				 * from /31 to /8. 
				 */
				for (short prefixLen = 32; (result == null) && (prefixLen >= 8);
						prefixLen--)
				{
					result = getInterfaceAddress(addresses, address,
							createMask(prefixLen));
				}
			}
			catch(Exception e)
			{
				Tr064Log.w(TAG, "Error checking subnet for \"" + remoteAddress + "\"", e);
			}
		}
//		else Log.d(TAG, "OS older than GINGERBREAD");
		
//		Log.d(TAG, "getInterfaceAddressForNetwork: use interface " +
//				((result == null) ? "null" : result.getHostAddress()));
		return result;
	}

	@SuppressLint("NewApi")
	private static List<InterfaceAddress> getInterfaceAddresses() throws SocketException
	{
		ArrayList<InterfaceAddress> result = new ArrayList<InterfaceAddress>(); 
		for (Enumeration<NetworkInterface> e = NetworkInterface
				.getNetworkInterfaces(); e.hasMoreElements(); )
		{
			NetworkInterface iface = e.nextElement();
			if (iface.isLoopback()) continue;

			List<InterfaceAddress> addresses = iface.getInterfaceAddresses ();
			for (InterfaceAddress address : addresses)
				if (Inet4Address.class.isAssignableFrom(address.getAddress().getClass()))
					result.add(address);
		}
		return result;
	}
	
	@SuppressLint("NewApi")
	private static InetAddress getInterfaceAddress(List<InterfaceAddress> addresses,
			int remoteAddress, int netMask)
			throws Exception
	{
//		Log.d(TAG, "getInterfaceAddressForNetwork(" + Integer.toHexString(remoteAddress) +
//				", " + Integer.toHexString(netMask) + ")");
		
		for (InterfaceAddress address : addresses)
		{
			int mask = (netMask == 0) ? createMask(address.getNetworkPrefixLength()) : netMask;
//			Log.d(TAG, "  interface : " + address.getAddress().getHostAddress() + "/" +
//					Integer.bitCount(mask));
			int network = bytesToInt(address.getAddress().getAddress()) & mask;
//			Log.d(TAG, "    network == " + Integer.toHexString(network));
			if ((remoteAddress & mask) == network)
			{
//				Log.d(TAG, "    use interface " + address.getAddress().getHostAddress());
				return address.getAddress();
			}
		}
		
		return null;
	}
	
	private static int bytesToInt(byte[] bytes)
	{
		int result = 0;
		for (int ii = 0; ii < 4; ii++)
			result = result << 8 | ((int)bytes[ii] & 0x000000FF);
		return result;
	}
	
	private static int createMask(short prefixLen)
	{
		int mask = 0;
		int bit = 0x80000000;
		for (int ii = prefixLen; ii > 0; ii--)
		{
			mask = mask | bit;
			bit = bit >>> 1;
		}
		return mask;
	}
}
