package org.kne.cloud.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.LockSupport;

public class DatagramServerSocket implements java.io.Closeable{
	public static final ByteArrayPool dataarraypool=new ByteArrayPool(100, 65535);
	
	private boolean closed=false;
	private boolean bound=false;
	private DatagramSocketFactory dgsf;
	private Set<DatagramDistributor> skts=new HashSet<>();
    private final Object stateLock = new Object();
    private BlockingQueue<DatagramSocket> recs;
	private InetSocketAddress bindpoint;
	 
	
	
	
	
	public DatagramServerSocket() throws SocketException {
		this(new DefaultDatagramSocketFactory());
	}

	protected DatagramServerSocket(DatagramSocketFactory impl) throws SocketException {
		this.dgsf=impl;
	}

	public DatagramServerSocket(int port, InetAddress laddr) throws SocketException {
		this(new DefaultDatagramSocketFactory());
		bind(new InetSocketAddress(laddr, port));
	}

	public DatagramServerSocket(int port) throws SocketException {
		this(new DefaultDatagramSocketFactory());
		bind(new InetSocketAddress(port));
	}

	public DatagramServerSocket(SocketAddress bindaddr) throws SocketException {
		this(new DefaultDatagramSocketFactory());
		bind(bindaddr);
	}

	public DatagramServerSocket(int port, int backlog) throws SocketException {
		this(new DefaultDatagramSocketFactory());
		bind(new InetSocketAddress(port));
	}
	
	public DatagramServerSocket(int port, int backlog, InetAddress laddr) throws SocketException {
		this(new DefaultDatagramSocketFactory());
		bind(new InetSocketAddress(laddr, port));
	}
	 public void bind(SocketAddress endpoint) throws SocketException {
	        bind(endpoint, 50);
	    }
	 public void bind(SocketAddress endpoint, int backlog) throws SocketException {
	        if (isClosed())
	            throw new SocketException("Socket is closed");
	        if (isBound())
	            throw new SocketException("Already bound");
	        if (endpoint == null)
	            endpoint = new InetSocketAddress(0);
	        if (!(endpoint instanceof InetSocketAddress epoint))
	            throw new IllegalArgumentException("Unsupported address type");
	        if (epoint.isUnresolved())
	            throw new SocketException("Unresolved address");
	        if (backlog < 1)
	            backlog = 50;



	        synchronized (stateLock) {
	            if (closed)
	                throw new SocketException("Socket is closed");
	            if (bound)
	                throw new SocketException("Already bound");
	            bind0(epoint);
	            recs=new ArrayBlockingQueue<DatagramSocket>(backlog);
	            bound = true;
	        }
	    }

	
	private void bind0(InetSocketAddress epoint) throws SocketException {
		bindpoint=epoint;
		if(epoint.getAddress().isAnyLocalAddress()) {
				Enumeration<NetworkInterface>eu= NetworkInterface.getNetworkInterfaces();
				while (eu.hasMoreElements()) {
					NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
					if(networkInterface.isUp()) {
					//System.out.println(networkInterface+" "+networkInterface.isUp());
					Enumeration<InetAddress>ei= networkInterface.getInetAddresses();
					while (ei.hasMoreElements()) {
						InetAddress inetAddress = (InetAddress) ei.nextElement();
						DatagramDistributor dd=new DatagramDistributor(new InetSocketAddress(inetAddress, epoint.getPort()));
						skts.add(dd);
					}
					}
				}
						
		}else {
			DatagramDistributor dd=new DatagramDistributor(epoint);
			skts.add(dd);
		}
	}
	private class DatagramDistributor implements Runnable{
		private Map<SocketAddress,SubDatagramSocket> clients=new ConcurrentHashMap<>();

		private InetSocketAddress epoint;
		private DatagramSocket datagramSocket;
		public DatagramDistributor(InetSocketAddress epoint) throws SocketException {
			this.epoint=epoint;
			datagramSocket=new DatagramSocket(epoint);
			ThreadTool.makeVDaemonThreadIfSupport("Datagram Distribute Thread", this).start();
		}
		
		public DatagramSocket getDatagramSocket() {
			return datagramSocket;
		}

		@Override
		public void run() {
			try {
			while(!closed) {
			byte[]data=dataarraypool.borrow();
			DatagramPacket dp=new DatagramPacket(data,data.length);
				datagramSocket.receive(dp);
				//System.out.println("get:"+dp);
				SubDatagramSocket sdds=clients.get(dp.getSocketAddress());
				if(sdds==null) {
					if(recs.remainingCapacity()>1) {
					sdds=new SubDatagramSocket(epoint, (InetSocketAddress) dp.getSocketAddress(),this );
					if(recs.offer(sdds))
					clients.put(dp.getSocketAddress(), sdds);
					}
					
				}
				if(sdds!=null) {
					//System.out.println("give:"+dp);
				sdds.givePacket(dp);
				}
				
			}
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}

		public void removeClient(SubDatagramSocket subDatagramSocket) {
			clients.remove(subDatagramSocket.getRemoteSocketAddress());
		}

		public void close() {
			datagramSocket.close();
		}
		
	}
	public DatagramSocket accept()throws IOException{
		while(true) {
			if(isClosed())
				throw new SocketException("DatagramServerSocket closed");
	        if (!isBound())
	            throw new SocketException("DatagramServerSocket is not bound yet");
			DatagramSocket sds=	recs.poll();
			if(sds!=null) {
				return sds;
			}
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	private boolean isClosed() {
		return closed;
	}

	private boolean isBound() {
		return bound;
	}
	public class SubDatagramSocket extends DatagramSocket{
		private DatagramDistributor parent;
		private InetSocketAddress local;
		private InetSocketAddress remote;
		private boolean closed=false;
		private int timeout=0;
		private BlockingQueue<DatagramPacket> recs=new ArrayBlockingQueue(100);
		private volatile Thread t;
		private void unwait() {
			if(t!=null)
			LockSupport.unpark(t);
		}
		
		public SubDatagramSocket(InetSocketAddress local,InetSocketAddress remote,DatagramDistributor datagramDistributor) throws SocketException {
			super((SocketAddress)null);
			this.local=local;
			this.remote=remote;
			this.parent=datagramDistributor;
		}

		protected boolean givePacket(DatagramPacket dp) {
			boolean b=recs.offer(dp);
			unwait();
			return b;
		}
		public DatagramPacket receive()throws IOException{
			long start=System.nanoTime();
			while(true) {
				if(closed) {
					throw new SocketException("DatagramSocket closed");
				}
				DatagramPacket dp=recs.poll();
				if(dp!=null) {
					
					return dp;
				}
				if(timeout!=0) {
				long dms=(System.nanoTime()-start)/1000000;
				if(dms>timeout)
					throw new SocketTimeoutException("Receive Time Out");
				}
				t=Thread.currentThread();
				LockSupport.parkNanos(1000000);
			}
		}
		@Override
		public void receive(DatagramPacket p) throws IOException {
				DatagramPacket dp=null;
				long start=System.nanoTime();
				while(true) {
					if(closed) {
						throw new SocketException("DatagramSocket closed");
					}
					dp=recs.poll();
					if(dp!=null) {
						p.setLength(dp.getLength());
					System.arraycopy(dp.getData(), dp.getOffset(), p.getData(), p.getOffset(), dp.getLength());
						dataarraypool.back(dp.getData());
						break;
					}
					if(timeout!=0) {
					long dms=(System.nanoTime()-start)/1000000;
					if(dms>timeout)
						throw new SocketTimeoutException("Receive Time Out");
					}
					t=Thread.currentThread();
					LockSupport.parkNanos(1000000);
				}
		}

		@Override
		public void send(DatagramPacket p) throws IOException {
			p.setSocketAddress(remote);
			parent.getDatagramSocket().send(p);
		}
		
		@Override
		public SocketAddress getRemoteSocketAddress() {
			return remote;
		}

		@Override
		public InetAddress getInetAddress() {
			return remote.getAddress();
		}

		@Override
		public int getPort() {
			return remote.getPort();
		}

		@Override
		public SocketAddress getLocalSocketAddress() {
			return local;
		}

		@Override
		public InetAddress getLocalAddress() {
			return local.getAddress();
		}

		@Override
		public int getLocalPort() {
			return local.getPort();
		}

		@Override
		public void setSoTimeout(int timeout) throws SocketException {
			this.timeout=timeout;
		}
		
		
		@Override
		public int getSoTimeout() throws SocketException {
			return timeout;
		}

		@Override
		public void close() {
			if(!closed)
				parent.removeClient(this);
			closed=true;
		}

		@Override
		public boolean isClosed() {
			return closed;
		}
	
		
		
	}
	
	@Override
	public void close() {
		closed=true;
		for (Iterator<DatagramDistributor> iterator = skts.iterator(); iterator.hasNext();) {
			DatagramDistributor datagramSocket = (DatagramDistributor) iterator.next();
			datagramSocket.close();
		}
	}
	
	public SocketAddress getLocalSocketAddress() {
        return bindpoint;
    }
	
	
	public InetAddress getLocalAddress() {
		 if(bindpoint==null)
	    	   return null;
	       else
	    	   return bindpoint.getAddress();
	}

	public int getLocalPort() {
       if(bindpoint==null)
    	   return -1;
       else
    	   return bindpoint.getPort();
    }
}
