package org.kne.cloud.network;

import java.io.IOException;
import java.io.Serializable;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.Objects;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

public class MultipurposeSocketAddress implements Serializable{
	/**
	 * 
	 */
	private static Map<String,SocketType> socketTypeRegister=new HashMap<>();
	
	
	static {
		socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory()));
		socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory()));
	}
	

	
	public static Map<String, SocketType> getSocketTypeRegister() {
		return socketTypeRegister;
	}
	private static final long serialVersionUID = 1L;
	private String type;
	private String host;
	private ProtocolStack ps=new ProtocolStack();
	
	
	private int port;
	
	public MultipurposeSocketAddress(String hostport)  {
		this(hostport,"TCP");
	}
	public MultipurposeSocketAddress(String hostport,String defaulttype)  {
		hostport=hostport.trim();
		if (hostport.startsWith("{")) {
			int i=hostport.indexOf("}");
			if(i==-1) {
				throw new IllegalArgumentException("missing }");
			}
			type=hostport.substring(1, i);
			hostport=hostport.substring(i+1);
		}else {
			type=defaulttype;
		}
		int index =hostport.lastIndexOf(":");
		host=hostport.substring(0,index);
		host=host.replace("[", "");
		host=host.replace("]", "");
		String tps=hostport.substring(index+1);
		int v=tps.indexOf("(");
		if(v!=-1) {
			ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
			tps=tps.substring(0, v);
		}
		port=Integer.parseInt(tps);
		
	}
	@Override
	public int hashCode() {
		return Objects.hash(host, port, ps, type);
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
		return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
				&& Objects.equals(type, other.type);
	}
	public MultipurposeSocketAddress(String host2, int port2) {
		this("TCP", host2, port2);
	}
	
	public MultipurposeSocketAddress(String type,String host2, int port2) {
		host=host2;
		port=port2;
		this.type=type;
	}
	public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) {
		this("TCP",remoteSocketAddress);
	}
	public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) {
		host=remoteSocketAddress.getAddress().getHostAddress();
		port=remoteSocketAddress.getPort();
		this.type=type;
	}
	public String getHost() {
		return host;
	}
	public int getPort() {
		return port;
	}
	public String getType() {
		return type;
	}
	@Override
	public String toString() {
		StringBuilder sb=new StringBuilder();
		if(!"TCP".equals(type)) {
			sb.append('{').append(type).append('}');
		}
		if(host.contains(":")) {
			sb.append('[').append( host).append( "]:").append( port);
		}else {
	sb.append( host).append( ':').append( port);
		}
		if(!ps.isEmpty()) {
			sb.append('(').append(ps.pop().getName()).append(')');
		}
		return sb.toString();
	}
	public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
		SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		Socket s=sf.createSocket();
		s.bind(new InetSocketAddress(bindip, bindport));
		s.connect(new InetSocketAddress(host, port),timeout);
		return s;
	}
	public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
		SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		Socket s=sf.createSocket();
		s.bind(new InetSocketAddress(bindip, bindport));
		s.connect(new InetSocketAddress(host, port));
		return s;
	}
	public Socket connectSocket() throws UnknownHostException, IOException {
		SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		Socket s=sf.createSocket();
		s.connect(new InetSocketAddress(host, port));
		return s;
	}
	public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
		SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		Socket s=sf.createSocket();
		s.connect(new InetSocketAddress(host, port),timeout);
		return s;
	}
	
	public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
		ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("ServerSocket Unsupported");
		}
		ServerSocket sk=srf.createServerSocket(port, 50, InetAddress.getByName(host));
		return sk;
	}
	public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
		ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("ServerSocket Unsupported");
		}
		ServerSocket sk=srf.createServerSocket(port, backlog, InetAddress.getByName(host));
		return sk;
	}
	public boolean isStream() {
		return checkIsStream(type);
	}
	public static boolean checkIsStream(String type2) {
		return socketTypeRegister.get(type2).isStream();
	}
	public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
		DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
		if(dgs==null) {
			throw new UnsupportedOperationException("DatagramSocket Unsupported");
		}
		DatagramSocket dgd=dgs.createSocket();
		dgd.bind(new InetSocketAddress(bindip, bindport));
		dgd.connect(new InetSocketAddress(host, port));
		return dgd;
	}
	public DatagramSocket connectDatagramSocket() throws IOException {
		DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
		if(dgs==null) {
			throw new UnsupportedOperationException("DatagramSocket Unsupported");
		}
		DatagramSocket dgd=dgs.createSocket();
		dgd.connect(new InetSocketAddress(host, port));
		return dgd;
	}
	public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
		DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
		}
		DatagramServerSocket sk=srf.createDatagramServerSocket(port, 50, InetAddress.getByName(host));
		return sk;
	}
	public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
		DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
		}
		DatagramServerSocket sk=srf.createDatagramServerSocket(port, backlog, InetAddress.getByName(host));
		return sk;
	}
}
