package org.kne.cloud.network.frpc;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketImpl;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Set;

import org.kne.cloud.network.MultiProtocolSocketAddress;

public class FrpcServerSocket extends ServerSocket {
	private FrpcProcess frpc;
	private FrpcIni frpcini;
	private String tunnel;
	public FrpcServerSocket() throws IOException {
		super();
	}

	public FrpcServerSocket(FrpcIni fi, int port, int backlog, InetAddress bindAddr) throws IOException {
		super();
		this.frpcini=fi;
		 if (port < 0 || port > 0xFFFF)
	            throw new IllegalArgumentException("Port value out of range: " + port);
	        if (backlog < 1)
	            backlog = 50;

	        try {
	            bind(new InetSocketAddress(bindAddr, port), backlog);
	        } catch (IOException | SecurityException e) {
	            close();
	            throw e;
	        }
	}

	public FrpcServerSocket(FrpcIni fi,int port, int backlog) throws IOException {
		this(fi,port, backlog, null);
	}

	public FrpcServerSocket(FrpcIni fi,int port) throws IOException {
		  this(fi,port, 50, null);
	}
	public FrpcServerSocket(FrpcIni fi) throws IOException {
		  this(fi,0, 50, null);
	}
	protected FrpcServerSocket(SocketImpl impl) {
		super(impl);
	}
	
	@Override
	public void bind(SocketAddress endpoint) throws IOException {
		  bind(endpoint, 50);
	}
	
	
	public String getTunnelHost() {
		 if (!isBound())
	            return null;
		return frpcini.getServerAddr();
	}
	public InetAddress getTunnelInetAddress() throws UnknownHostException {
		 if (!isBound())
	            return null;
		return InetAddress.getByName( frpcini.getServerAddr());
	}

	public int getTunnelPort() {
		  if (!isBound())
	            return -1;
		return frpcini.getRemotePort(tunnel);
	}

	public MultiProtocolSocketAddress getTunnelSocketAddress() {
		 if (!isBound())
	            return null;
		return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
	}

	@Override
	public void bind(SocketAddress endpoint, int backlog) throws IOException {
		if(frpcini!=null) {
			if(tunnel==null) {
				Set<String> e=frpcini.keySet();
				for (Iterator<String> iterator = e.iterator(); iterator.hasNext();) {
					String key =  iterator.next();
					if(!key.equals("common")) {
						tunnel=key;
						break;
					}
				}
				if(tunnel==null) {
					throw new IllegalArgumentException("cant found avaliable tunnel!");
				}
			}
			super.bind(endpoint, backlog);
			InetAddress iad=((InetSocketAddress) endpoint).getAddress();
			frpcini.setLocalIP(tunnel,iad.isAnyLocalAddress()?"127.0.0.1": iad.getHostAddress());
			frpcini.setLocalPort(tunnel, getLocalPort());
			frpcini.setConnectServerLocalIp(((InetSocketAddress)endpoint).getAddress().getHostAddress());
			frpc=new FrpcProcess(frpcini.storeToString());
		}else {
		super.bind(endpoint, backlog);
		}
	}

	public FrpcIni getFrpcini() {
		return frpcini;
	}

	public void setFrpcini(FrpcIni frpcini) {
		this.frpcini = frpcini;
	}

	public String getTunnel() {
		return tunnel;
	}

	public void setTunnel(String tunnel) {
		this.tunnel = tunnel;
	}

	@Override
	public void close() throws IOException {
		if(frpc!=null) {
			frpc.destroy();
		}
		super.close();
	}
	/*public MultipurposeSocketAddress getTunnelRemoteSocketAddress() {
		return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(), frpcini.getServerAddr(), frpcini.getRemotePort(tunnel));
	}*/
	public static void main(String[] args) throws IOException {
		
		String str="""
				[common]
server_addr = us.afrps.cn
server_port = 7000
token = afrps.cn
protocol=kcp
[test5]
type = tcp
local_ip = 127.0.0.1
local_port = 7461
remote_port = 49966
use_encryption = true
use_compression = true
				""";
		FrpcIni fi=new FrpcIni(str);
		FrpcServerSocket fss=	new FrpcServerSocket(fi);
		System.out.println(fss.getTunnelSocketAddress());
		System.out.println(fss.accept());
	}
}
