package org.kne.cloud.network;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;

public class IPMulticastDiscovery implements Closeable, AutoCloseable {
	private static final boolean debug=false;
	
	private MulticastSocket soc;
	private NetworkInterface ninterface;
	private volatile boolean closed = false;
	private InetSocketAddress group;
	private InetSocketAddress bind;
	private int type;
	private List<MultipurposeSocketAddress> msas;
	private UUID selfUUID;

	private volatile Consumer<MultipurposeSocketAddress> con;
	private long timeInterval;
	
	public long getTimeInterval() {
		return timeInterval;
	}

	public void setTimeInterval(long timeInterval) {
		this.timeInterval = timeInterval;
	}

	public NetworkInterface getNinterface() {
		return ninterface;
	}

	public InetSocketAddress getGroup() {
		return group;
	}

	public InetSocketAddress getBind() {
		return bind;
	}

	public Consumer<MultipurposeSocketAddress> getCon() {
		return con;
	}

	public void setCon(Consumer<MultipurposeSocketAddress> con) {
		this.con = con;
	}

	public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
			List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException {
		soc = new MulticastSocket(bind);
		soc.joinGroup(group, ninterface);
		this.bind = bind;
		this.group = group;
		this.ninterface = ninterface;
		this.msas = msas;
		this.selfUUID=node;
		this.timeInterval=timeInterval;
	}

	public void start() {

		ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
			try {
				while (!closed) {
					HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>();
					synchronized (msas) {
					for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) {
						MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator
								.next();

						MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
								multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());
						try {
						if (s.add(mpsa)) {
							boolean bf = true;
							Enumeration<InetAddress> ei = ninterface.getInetAddresses();
							while (ei.hasMoreElements()) {
								InetAddress inetAddress = (InetAddress) ei.nextElement();
								if (inetAddress.equals(multipurposeSocketAddress.getInetAddress())) {
									bf = false;
									break;
								}
							}
							if (bf) {
							ByteArrayOutputStream baos = new ByteArrayOutputStream();
							DataOutputStream dops = new DataOutputStream(baos);
							dops.writeInt(multipurposeSocketAddress.getPort());
							dops.writeUTF(multipurposeSocketAddress.getType());
							dops.writeLong(selfUUID.getMostSignificantBits());
							dops.writeLong(selfUUID.getLeastSignificantBits());
							dops.close();
							byte[] b = baos.toByteArray();
							DatagramPacket dp = new DatagramPacket(b, b.length, group);
							soc.send(dp);
							if(debug)
							System.out.println("发送广播："+multipurposeSocketAddress+" "+group+" "+selfUUID);
							}
						}
						} catch (NoRouteToHostException|UnknownHostException e) {
						}
						
					}	
					}

					Thread.sleep(timeInterval);
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			} finally {
				try {
					close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}).start();

		ThreadTool.makeVDaemonThreadIfSupport("线路发现线程",()->{
		try {
			loop:while (!closed) {
				try {
				byte[] b = new byte[65535];
				DatagramPacket dp = new DatagramPacket(b, b.length);
				soc.receive(dp);
				ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
				DataInputStream dis = new DataInputStream(bis);
				int port = dis.readInt();
				String type = dis.readUTF();
				long h=dis.readLong();
				long l=dis.readLong();
				UUID uid=new UUID(h, l);
				dis.close();
				MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(type, dp.getAddress().getHostAddress(),
						port);
				if(debug) {
					if(uid.equals(selfUUID)) {
						System.out.println("丢弃广播："+mpsa+" "+dp.getSocketAddress()+" "+uid);
						continue;
					}else {
						System.out.println("接收广播："+mpsa+" "+dp.getSocketAddress()+" "+uid);
					}
				}
				InetAddress mpsai=mpsa.getInetAddress();
				Enumeration<InetAddress> ei = ninterface.getInetAddresses();
				while (ei.hasMoreElements()) {
					InetAddress inetAddress = (InetAddress) ei.nextElement();
					if (inetAddress.equals(mpsai)) {
						continue loop;
					}
				}
				
				if (con != null) {
					con.accept(mpsa);
				}
				}catch(UnknownHostException e) {
					
				}catch(EOFException e) {
					
				}
			}
		} catch(SocketException e) {
			if(!isClosed())
				e.printStackTrace();
		}catch (IOException e) {
			System.out.println(bind + " " + group + " " + ninterface);
			e.printStackTrace();
		} finally {
			try {
				
				close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		});
	}

	@Override
	public void close() throws IOException {
		soc.close();
		closed = true;
	}

	public boolean isClosed() {
		return closed;
	}

}
