package org.andengine.util;

import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;

import org.andengine.util.adt.array.ArrayUtils;
import org.andengine.util.exception.AndEngineException;
import org.andengine.util.exception.MethodNotFoundException;
import org.andengine.util.system.SystemUtils;

import android.annotation.TargetApi;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.Build;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 16:54:01 - 20.03.2011
 */
public final class WifiUtils {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final String IP_DEFAULT = "0.0.0.0";
	private static final String[] HOTSPOT_NETWORKINTERFACE_NAMES = { "wl0.1", "wlan0" };
	private static final String MULTICASTLOCK_NAME_DEFAULT = "AndEngineMulticastLock";

	// ===========================================================
	// Fields
	// ===========================================================

	// ===========================================================
	// Constructors
	// ===========================================================

	private WifiUtils() {

	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	// ===========================================================
	// Methods
	// ===========================================================

	public static WifiManager getWifiManager(final Context pContext) {
		return (WifiManager) pContext.getSystemService(Context.WIFI_SERVICE);
	}

	public static boolean isWifiEnabled(final Context pContext) {
		return WifiUtils.getWifiManager(pContext).isWifiEnabled();
	}

	private static void setWifiEnabled(final Context pContext, final boolean pEnabled) {
		WifiUtils.getWifiManager(pContext).setWifiEnabled(pEnabled);
	}

	public static String getWifiSSID(final Context pContext) {
		return WifiUtils.getWifiManager(pContext).getConnectionInfo().getSSID();
	}

	public static String getWifiSSID(final Context pContext, final boolean pStripQuotes) {
		final String wifiSSID = WifiUtils.getWifiSSID(pContext);

		if (pStripQuotes) {
			return WifiUtils.stripQuotes(wifiSSID);
		} else {
			return wifiSSID;
		}
	}

	public static byte[] getWifiIPv4AddressRaw(final Context pContext) {
		return IPUtils.ipv4AddressToIPAddress(WifiUtils.getWifiManager(pContext).getConnectionInfo().getIpAddress());
	}

	public static String getWifiIPv4Address(final Context pContext) throws WifiUtilsException {
		try {
			return IPUtils.ipAddressToString(WifiUtils.getWifiIPv4AddressRaw(pContext));
		} catch (final UnknownHostException e) {
			throw new WifiUtilsException("Unexpected error!", e);
		}
	}

	public static boolean isWifiIPAddressValid(final Context pContext) {
		return WifiUtils.getWifiManager(pContext).getConnectionInfo().getIpAddress() != 0;
	}


	/**
	 * The check currently performed is not sufficient, as some carriers disabled this feature manually!
	 */
	public static boolean isWifiHotspotSupported(final Context pContext) {
		if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.ECLAIR_MR1)) {
			return false;
		} else {
			final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
			try {
				final Method WifiManager_isWifiApEnabled = wifiManager.getClass().getMethod("isWifiApEnabled");
				return WifiManager_isWifiApEnabled != null;
			} catch (final Throwable t) {
				return false;
			}
		}
	}

	public static boolean isWifiHotspotEnabled(final Context pContext) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

		try {
			final Method WifiManager_isWifiApEnabled = wifiManager.getClass().getMethod("isWifiApEnabled");
			if (WifiManager_isWifiApEnabled == null) {
				throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".isWifiApEnabled()"));
			} else {
				final boolean result = (Boolean) WifiManager_isWifiApEnabled.invoke(wifiManager);
				return result;
			}
		} catch (final Throwable t) {
			throw new WifiUtilsException(t);
		}
	}

	public static boolean isWifiHotspotEnabled(final Context pContext, final boolean pDefault) {
		try {
			return WifiUtils.isWifiHotspotEnabled(pContext);
		} catch (final WifiUtilsException e) {
			return pDefault;
		}
	}

	public static boolean setWifiHostpotEnabled(final Context pContext, final boolean pEnabled) throws WifiUtilsException {
		return WifiUtils.setWifiHostpotEnabled(pContext, null, pEnabled);
	}

	public static boolean setWifiHostpotEnabled(final Context pContext, final WifiConfiguration pWifiConfiguration, final boolean pEnabled) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

		try {
			if (pEnabled) {
				WifiUtils.setWifiEnabled(pContext, false);
			}

			final Method WifiManager_setWifiApEnabled = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
			if (WifiManager_setWifiApEnabled == null) {
				throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".setWifiApEnabled(" + WifiConfiguration.class.getSimpleName() + ", " + boolean.class.getSimpleName() + ")"));
			} else {
				final boolean result = (Boolean) WifiManager_setWifiApEnabled.invoke(wifiManager, pWifiConfiguration, pEnabled);
				return result;
			}
		} catch (final Throwable t) {
			throw new WifiUtilsException(t);
		}
	}

	public static WifiConfiguration getWifiHotspotConfiguration(final Context pContext) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

		try {
			final Method WifiManager_getWifiApState = wifiManager.getClass().getMethod("getWifiApConfiguration");
			if (WifiManager_getWifiApState == null) {
				throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".getWifiApConfiguration()"));
			} else {
				return (WifiConfiguration)WifiManager_getWifiApState.invoke(wifiManager);
			}
		} catch (final Throwable t) {
			throw new WifiUtilsException(t);
		}
	}

	public static String getWifiHotspotSSID(final Context pContext) throws WifiUtilsException {
		return WifiUtils.getWifiHotspotConfiguration(pContext).SSID;
	}

	public static String getWifiHotspotSSID(final Context pContext, final boolean pStripQuotes) throws WifiUtilsException {
		final String wifiHotspotSSID = WifiUtils.getWifiHotspotSSID(pContext);

		if (pStripQuotes) {
			return WifiUtils.stripQuotes(wifiHotspotSSID);
		} else {
			return wifiHotspotSSID;
		}
	}

	public static WifiHotspotState getWifiHotspotState(final Context pContext) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

		try {
			final Method WifiManager_getWifiApState = wifiManager.getClass().getMethod("getWifiApState");
			if (WifiManager_getWifiApState == null) {
				throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".getWifiApState()"));
			} else {
				final int result = (Integer) WifiManager_getWifiApState.invoke(wifiManager);

				return WifiHotspotState.fromWifiApState(result);
			}
		} catch (final Throwable t) {
			throw new WifiUtilsException(t);
		}
	}

	public static boolean isWifiHotspotRunning(final Context pContext) throws WifiUtilsException {
		return WifiUtils.getWifiHotspotState(pContext) == WifiHotspotState.ENABLED;
	}

	/**
	 * @return prefers to return an IPv4 address if found, otherwise an IPv6 address.
	 * @throws WifiUtilsException
	 */
	@TargetApi(Build.VERSION_CODES.GINGERBREAD)
	public static byte[] getWifiHotspotIPAddressRaw() throws WifiUtilsException {
		try {
			byte[] ipv6Address = null;

			final Enumeration<NetworkInterface> networkInterfaceEnumeration = NetworkInterface.getNetworkInterfaces();
			while (networkInterfaceEnumeration.hasMoreElements()) {
				final NetworkInterface networkInterface = networkInterfaceEnumeration.nextElement();
				if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.FROYO) || !networkInterface.isLoopback()) {
					final String networkInterfaceName = networkInterface.getName();
	
					if (ArrayUtils.contains(WifiUtils.HOTSPOT_NETWORKINTERFACE_NAMES, networkInterfaceName)) {
						final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
						while (inetAddressEnumeration.hasMoreElements()) {
							final InetAddress inetAddress = inetAddressEnumeration.nextElement();
							if (!inetAddress.isLoopbackAddress()) {
								final byte[] ipAddress = inetAddress.getAddress();
								if (ipAddress.length == IPUtils.IPV4_LENGTH) {
									return ipAddress;
								} else {
									ipv6Address = ipAddress;
								}
							}
						}
					}
				}
			}

			if (ipv6Address != null) {
				return ipv6Address;
			} else {
				throw new WifiUtilsException("No IP bound to '" + Arrays.toString(WifiUtils.HOTSPOT_NETWORKINTERFACE_NAMES) + "'!");
			}
		} catch (final SocketException e) {
			throw new WifiUtilsException("Unexpected error!", e);
		}
	}

	public static String getWifiHotspotIPAddress() throws WifiUtilsException {
		try {
			return IPUtils.ipAddressToString(WifiUtils.getWifiHotspotIPAddressRaw());
		} catch (final UnknownHostException e) {
			throw new WifiUtilsException("Unexpected error!", e);
		}
	}

	public static boolean isWifiHotspotIPAddressValid() throws WifiUtilsException { // TODO!
		return !WifiUtils.IP_DEFAULT.equals(WifiUtils.getWifiHotspotIPAddress());
	}

	@TargetApi(Build.VERSION_CODES.GINGERBREAD)
	public static byte[] getEmulatorIPAddressRaw() throws WifiUtilsException {
		try {
			byte[] ipv6Address = null;

			final Enumeration<NetworkInterface> networkInterfaceEnumeration = NetworkInterface.getNetworkInterfaces();
			while (networkInterfaceEnumeration.hasMoreElements()) {
				final NetworkInterface networkInterface = networkInterfaceEnumeration.nextElement();
				if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.FROYO) || !networkInterface.isLoopback()) {
					final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
					while (inetAddressEnumeration.hasMoreElements()) {
						final InetAddress inetAddress = inetAddressEnumeration.nextElement();
						if (!inetAddress.isLoopbackAddress()) {
							final byte[] ipAddress = inetAddress.getAddress();
							if (ipAddress.length == IPUtils.IPV4_LENGTH) {
								return ipAddress;
							} else {
								ipv6Address = ipAddress;
							}
						}
					}
				}
			}

			if (ipv6Address != null) {
				return ipv6Address;
			} else {
				throw new WifiUtilsException("No IP found that is not bound to localhost!");
			}
		} catch (final SocketException e) {
			throw new WifiUtilsException("Unexpected error!", e);
		}
	}

	public static String getEmulatorIPAddress() throws WifiUtilsException {
		try {
			return IPUtils.ipAddressToString(WifiUtils.getEmulatorIPAddressRaw());
		} catch (final UnknownHostException e) {
			throw new WifiUtilsException("Unexpected error!", e);
		}
	}

	public static boolean getEmulatorIPAddressValid() throws WifiUtilsException { // TODO!
		return !WifiUtils.IP_DEFAULT.equals(WifiUtils.getEmulatorIPAddress());
	}


	public static boolean isMulticastEnabled(final Context pContext) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);

		try {
			final Method WifiManager_isMulticastEnabled = wifiManager.getClass().getMethod("isMulticastEnabled");
			if (WifiManager_isMulticastEnabled == null) {
				throw new WifiUtilsException(new MethodNotFoundException(WifiManager.class.getSimpleName() + ".isMulticastEnabled()"));
			} else {
				final boolean result = (Boolean) WifiManager_isMulticastEnabled.invoke(wifiManager);
				return result;
			}
		} catch (final Throwable t) {
			throw new WifiUtilsException(t);
		}
	}

	public static boolean isMulticastEnabled(final Context pContext, final boolean pDefault) {
		try {
			return WifiUtils.isMulticastEnabled(pContext);
		} catch (final WifiUtilsException e) {
			return pDefault;
		}
	}

	public static MulticastLock aquireMulticastLock(final Context pContext) {
		return WifiUtils.aquireMulticastLock(pContext, WifiUtils.MULTICASTLOCK_NAME_DEFAULT);
	}

	public static MulticastLock aquireMulticastLock(final Context pContext, final String pMulticastLockName) {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
		final MulticastLock multicastLock = wifiManager.createMulticastLock(pMulticastLockName);
		multicastLock.setReferenceCounted(true);
		multicastLock.acquire();
		return multicastLock;
	}

	public static void releaseMulticastLock(final MulticastLock pMulticastLock) {
		if (pMulticastLock.isHeld()) {
			pMulticastLock.release();
		}
	}

	public static byte[] getBroadcastIPAddressRaw(final Context pContext) throws WifiUtilsException {
		final WifiManager wifiManager = WifiUtils.getWifiManager(pContext);
		final DhcpInfo dhcp = wifiManager.getDhcpInfo();
		// TODO handle null somehow...

		final int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
		final byte[] broadcastIP = new byte[IPUtils.IPV4_LENGTH];
		for (int k = 0; k < IPUtils.IPV4_LENGTH; k++) {
			broadcastIP[k] = (byte) ((broadcast >> (k * 8)) & 0xFF);
		}
		return broadcastIP;
	}

	private static String stripQuotes(final String pString) {
		if (pString == null) {
			return pString;
		} else {
			final int stringLength = pString.length();
			if (stringLength >= 2) {
				if (pString.startsWith("\"") && pString.endsWith("\"") ) {
					return pString.substring(1, stringLength - 1);
				} else {
					return pString;
				}
			} else {
				return pString;
			}
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================

	public static enum WifiHotspotState {
		// ===========================================================
		// Elements
		// ===========================================================

		DISABLING,
		DISABLED,
		ENABLING,
		ENABLED,
		FAILED;

		// ===========================================================
		// Constants
		// ===========================================================

		// ===========================================================
		// Fields
		// ===========================================================

		// ===========================================================
		// Constructors
		// ===========================================================

		public static WifiHotspotState fromWifiApState(final int pWifiApState) throws WifiUtilsException {
			if (SystemUtils.isAndroidVersionOrHigher(Build.VERSION_CODES.ICE_CREAM_SANDWICH)) {
				switch (pWifiApState) {
					case 10:
						return DISABLING;
					case 11:
						return DISABLED;
					case 12:
						return ENABLING;
					case 13:
						return ENABLED;
					case 14:
						return FAILED;
					default:
						throw new WifiUtilsException("TODO...");
				}
			} else {
				switch (pWifiApState) {
					case 0:
						return DISABLING;
					case 1:
						return DISABLED;
					case 2:
						return ENABLING;
					case 3:
						return ENABLED;
					case 4:
						return FAILED;
					default:
						if (pWifiApState >= 10) {
							return WifiHotspotState.fromWifiApState(pWifiApState - 10);
						} else {
							throw new WifiUtilsException("TODO...");
						}
				}
			}
		}

		// ===========================================================
		// Getter & Setter
		// ===========================================================

		// ===========================================================
		// Methods for/from SuperClass/Interfaces
		// ===========================================================

		// ===========================================================
		// Methods
		// ===========================================================

		// ===========================================================
		// Inner and Anonymous Classes
		// ===========================================================
	}

	public static class WifiUtilsException extends AndEngineException {
		// ===========================================================
		// Constants
		// ===========================================================

		private static final long serialVersionUID = 1108697754015225179L;

		// ===========================================================
		// Constructors
		// ===========================================================

		public WifiUtilsException() {

		}

		public WifiUtilsException(final Throwable pThrowable) {
			super(pThrowable);
		}

		private WifiUtilsException(final String pMessage) {
			super(pMessage);
		}

		private WifiUtilsException(final String pMessage, final Throwable pThrowable) {
			super(pMessage, pThrowable);
		}
	}
}