package hh.dejsem.net;

import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiManager;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;

import hh.dejsem.K;
import hh.lib.D;

public class NetUDP {

	final static String NULL_HOST = "0.0.0.0";

	public static InetAddress localAddr = null;
	public static InetAddress broadcastAddr = null;
	static NetCrypt c = null;

	D d;
	public boolean go = true;
	DatagramSocket socket = null;

	public NetUDP(D d) throws SocketException, UnknownHostException {
		this.d = d.klon(this);
		synchronized(K.app) {
			if(localAddr == null) {
				localAddr = getLocalIp();
				broadcastAddr = getBroadcastAddress();
				if(c == null) c = new NetCrypt(d, null, "heslo", 0);
			}
		}
	}

	InetAddress getLocalIp() throws SocketException {
		Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
		for(; en.hasMoreElements(); ) {
			NetworkInterface i = en.nextElement();
			for(Enumeration<InetAddress> enumIpAddr = i.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
				InetAddress ia = enumIpAddr.nextElement();
				if( !ia.isLoopbackAddress() &&
					!ia.toString().substring(0, 0).getBytes().equals((byte) -128) &&
					Inet4Address.class.isInstance(ia)) return ia;
			}
		}
		return null;
	}

	InetAddress getBroadcastAddress() throws UnknownHostException {
		InetAddress broadcastAddr = null;
		DhcpInfo dhcp = ((WifiManager)d.getAct().getApplicationContext().getSystemService(Context.WIFI_SERVICE)).getDhcpInfo();
		if(dhcp != null && dhcp.ipAddress != 0) broadcastAddr = int2ia((dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask);
		return broadcastAddr;
	}

	void setSocket() throws SocketException, UnknownHostException {
		socket = new DatagramSocket(new InetSocketAddress(InetAddress.getByName(NULL_HOST), K.UDP_PORT));
		socket.setBroadcast(true);
	}

	void closeSocket() { if(socket != null) socket.close(); }

	InetAddress int2ia(int ia) throws UnknownHostException {
		byte[] quad = new byte[4];
		for(int k = 0; k < 4; k++) quad[k] = (byte)((ia >> k * 8) & 0xFF);
		return InetAddress.getByAddress(quad);
    }

	DatagramPacket allocRecDgram() {
		byte[] recvBuf = new byte[15000];
		DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
		return packet;
	}

	String receiveUDPData(DatagramSocket socket, DatagramPacket packet) throws IOException {
		socket.receive(packet);
		int len = packet.getLength();
		byte[] enc = Arrays.copyOf(packet.getData(), len);
		String data = "";
		try { data = c.decrypt(enc); }
		catch(Exception e) { return null; }
		return data;
	}

	/**
	 * get InetSocketAddress of peer declaring itself as server side of peer connection
	 * <br>● peer broadcasts ipaddr:port in UDP datagrams
	 *
	 * @return the inet socket address
	 * @throws IOException the io exception
	 */
	public InetSocketAddress keepReceivingPeerIp() throws IOException {
		if(d.ll(3)) d.l("getting peer ipaddr:port via UDP broadcast");
		String data = null;
		String key = "";
		String peerIp = "";
		final int numLen = 12;
		int strLen = 0;
		int beg = 0;
		try {
			setSocket();
			DatagramPacket packet = allocRecDgram();
			go = true;
			while(go && peerIp.equals("") || peerIp.equals(localAddr.getHostAddress())) {
				key = "";
				while(go && !key.equals(K.UDP_ADVERT_KEY)) {
					data = receiveUDPData(socket, packet);
					beg = 0;
					strLen = Integer.valueOf(data.substring(beg, beg + numLen));
					beg += numLen;
					key = data.substring(beg, beg + strLen);
				}
				beg += strLen;
				strLen = Integer.valueOf(data.substring(beg, beg + 12));
				beg += 12;
				peerIp = data.substring(beg, beg + strLen);
			}
			if(go) {
				beg += strLen;
				int port = Integer.valueOf(data.substring(beg, beg + numLen));
				return new InetSocketAddress(peerIp, port);
			}
		}
		catch(IOException e) {
			d.l(String.format("datagram exception: errno=%d, emsg=[%s]", d.errno(e), e.getMessage()));
			throw e;
		}
		finally { closeSocket(); }
		return null;
	}

	public void keepSendingPeerIp(String ipPort) throws Exception {
		try {
			byte[] sendData = c.encrypt(ipPort);
			setSocket();
			DatagramPacket packet = new DatagramPacket(sendData, sendData.length, broadcastAddr, K.UDP_PORT);
			while(go) {
				socket.send(packet);
				try { Thread.sleep(1000, 0); } catch(InterruptedException e) {}
			}
		}
		catch(Exception e) {
			d.l(String.format("datagram exception: errno=%d, emsg=[%s]", d.errno(e), e.getMessage()));
			throw e;
		}
		finally { closeSocket(); }
	}
}
