package org.kne.cloud.network;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
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.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;

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

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class MultipurposeSocketAddress implements Serializable{
	/**
	 * 
	 */
	private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
	
	
	static {
		socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory()));
		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);
		if(port<0) {
			throw new IllegalArgumentException("port < 0");
		}
	}
	@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 boolean equals2(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
		InetAddress addra=null;
		try {
			addra = InetAddress.getByName(host);
		} catch (UnknownHostException e) {
		}
		InetAddress addrb=null;
		try {
			addrb = InetAddress.getByName(other.host);
		} catch (UnknownHostException e) {
		}
		boolean hostequals=Objects.equals(addra, addrb);
		
		return hostequals && 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;
	}
	
	public InetSocketAddress getSocketAddress() {
		return new InetSocketAddress(host, port);
	}
	
	@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();
		try {
		s.bind(new InetSocketAddress(bindip, bindport));
		s.connect(new InetSocketAddress(host, port),timeout);
		}catch(Exception e) {
			s.close();
			throw e;
		}
		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();
		try {
		s.bind(new InetSocketAddress(bindip, bindport));
		s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}
		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();
		try {
		s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}
		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();
		try {
		s.connect(new InetSocketAddress(host, port),timeout);
		}catch(Exception e) {
			s.close();
			throw e;
		}
		return s;
	}
	
	
	
	private static TimeoutTimer tmoTimer=new TimeoutTimer();
	
	
	
	public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
		SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		SocketChannel s=sf.createSocketChannel();
		TimerTask tt=null;
		try {
		s.bind(new InetSocketAddress(bindip, bindport));
		 tt=tmoTimer.createTimeOutTask(s, timeout);
		
		s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}finally {
			if(tt!=null)
			tt.cancel();
		}
		return s;
	}
	public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
		SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		SocketChannel s=sf.createSocketChannel();
		try {
		s.bind(new InetSocketAddress(bindip, bindport));
		s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}
		return s;
	}
	public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
		SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		SocketChannel s=sf.createSocketChannel();
		try {
		s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}
		return s;
	}
	public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
		SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
		if(sf==null) {
			throw new UnsupportedOperationException("Socket Unsupported");
		}
		SocketChannel s=sf.createSocketChannel();

		TimerTask tt=tmoTimer.createTimeOutTask(s, timeout);
		try {
			s.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			s.close();
			throw e;
		}finally {
			if(tt!=null)
			tt.cancel();
		}
		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 ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
		ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("ServerSocket Unsupported");
		}
		ServerSocketChannel sk=srf.createServerSocketChannel(port, 50, InetAddress.getByName(host));
		return sk;
	}
	public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
		ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
		if(srf==null) {
			throw new UnsupportedOperationException("ServerSocket Unsupported");
		}
		ServerSocketChannel sk=srf.createServerSocketChannel(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();
		try {
		dgd.bind(new InetSocketAddress(bindip, bindport));
		dgd.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			dgd.close();
			throw e;
		}
		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();
		try {
		dgd.connect(new InetSocketAddress(host, port));
		}catch(Exception e) {
			dgd.close();
			throw e;
		}
		return dgd;
	}
	public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
		DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
		if(dgs==null) {
			throw new UnsupportedOperationException("DatagramSocket Unsupported");
		}
		DatagramSocket dgd=dgs.createSocket();
		try {
		dgd.bind(new InetSocketAddress(host, port));
		}catch(Exception e) {
			dgd.close();
			throw e;
		}
		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;
	}
	public InetAddress getInetAddress() throws UnknownHostException {
		return InetAddress.getByName(host);
	}
	public InetAddress[] getAllInetAddress() throws UnknownHostException {
		return InetAddress.getAllByName(host);
	}
	public boolean supportNIO() {
		SocketType stp= socketTypeRegister.get(getType());
		if(stp==null)
		return false;
		if(stp.getSocketChannelFactory()==null) {
			return false;
		}else {
			return true;
		}
	}
	
	public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){
		return new JsonDeserializer<MultipurposeSocketAddress>() {
			
			@Override
			public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
					throws JsonParseException {
				if(arg0.isJsonPrimitive()) {
					JsonPrimitive jp=(JsonPrimitive) arg0;
					if(jp.isString()) {
						return new MultipurposeSocketAddress(jp.getAsString());
					}
				}
				throw new JsonParseException("not a string:"+arg0);
			}
		};
	}
	public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){
		return new JsonSerializer<MultipurposeSocketAddress>() {
			
			@Override
			public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
				return new JsonPrimitive(arg0.toString());
			}
		};
	}
	
	public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
		gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer());
		gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer());
	}
	
}
