package org.kne.cloud.network.frpc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.ProcessHandle.Info;
import java.net.BindException;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

public class FrpcProcess extends Process{
	private static File defaultBase=new File("frpc");
	
	private volatile Thread hook;
	
	public static File getDefaultBase() {
		return defaultBase;
	}
	public static void setDefaultBase(File defaultBase) {
		FrpcProcess.defaultBase = defaultBase;
	}
	private File base;
	private File exe;
	private Process theProcess;

	private File config_file;
	
	public FrpcProcess(String ini) throws IOException {
		this(defaultBase,ini);
		
	}
	public FrpcProcess(File base,String ini) throws IOException {
		this.base=base;
		if(System.getProperty("os.name").toLowerCase().contains("windows")) {
			exe=new File(base,"frpc_windows_amd64.exe");
			}else {
				exe=new File(base,"frpc_linux_amd64");
			}
			AtomicReference<IOException >arex=new AtomicReference<IOException >();
		synchronized (getClass()) {
			UUID uid=UUID.randomUUID();
			config_file = new File(base,"frpc_conf_"+uid+".ini");
			config_file.createNewFile();
			config_file.deleteOnExit();
			PrintWriter ps=null;
			try {
			ps=new PrintWriter(config_file);
			ps.println(ini);
			}finally {
				if(ps!=null)
			ps.close();
			}
			List<String>lstc=new ArrayList<>();
			lstc.add(exe.getAbsolutePath());
			lstc.add("-c");
			lstc.add(config_file.getName());
			ProcessBuilder psb=new ProcessBuilder(lstc);
			psb.directory(base);
			theProcess=psb.start();
			Thread t=Thread.currentThread();
			
			hook=new Thread(()->{
				try {
					hook=null;
					finalize();
				} catch (Throwable e) {
					e.printStackTrace();
				}
			});
			Runtime.getRuntime().addShutdownHook(hook);
			
			CountDownLatch cdl=new CountDownLatch(1);
			new Thread(()->{
				BufferedReader br=new BufferedReader(new InputStreamReader(theProcess.getInputStream()));
			
					try {
				while(true) {
						String s=br.readLine();
						if(s==null) {
							break;
						}
						cdl.countDown();
						System.out.println  ("{"+uid+"}"+s);
						if(s.contains("start proxy success")) {
							t.interrupt();
						}else if(s.contains("port already used")){
							arex.set(new BindException("port on server already used"));
							t.interrupt();
						}else if(s.contains("远程端口已经在使用了")) {
							arex.set(new BindException("port on server already used"));
							t.interrupt();
						}else if(s.contains("no such host")) {
							arex.set(new UnknownHostException("no such host"));
							t.interrupt();
						}else if(s.contains("connection write timeout")) {
							arex.set(new ConnectException("connection write timeout"));
							t.interrupt();
						}else if(s.contains("already exists")&&s.contains("proxy")) {
							arex.set(new BindException("proxy already exists"));
							t.interrupt();
						}else if(s.contains("已经存在, 请检查是否重复开启")&&s.contains("隧道")) {
							arex.set(new BindException("proxy already exists"));
							t.interrupt();
						}
				}
					} catch (IOException e) {
						e.printStackTrace();
					}finally {
						try {
							br.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					
			}).start();
			try {
				cdl.await(5000, TimeUnit.MILLISECONDS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			}
		
			try {
				if(theProcess.waitFor(20000, TimeUnit.MILLISECONDS)) {
					destroy();
					throw new SocketException("Frpc exit code:"+theProcess.exitValue());
				}else {
					destroy();
					throw new SocketException("Frpc timed out");
				}
			} catch (InterruptedException e) {
				//destroy();
				Thread.interrupted();
				if(arex.get()!=null) {
					destroy();
					arex.get().fillInStackTrace();
					throw arex.get();
				}
			}
					
			
			
	}
	public File getBase() {
		return base;
	}
	@Override
	public OutputStream getOutputStream() {
		return theProcess.getOutputStream();
	}
	@Override
	public InputStream getInputStream() {
		return theProcess.getInputStream();
	}
	@Override
	public InputStream getErrorStream() {
		return theProcess.getErrorStream();
	}
	@Override
	public int waitFor() throws InterruptedException {
		return theProcess.waitFor();
	}
	@Override
	public int exitValue() {
		return theProcess.exitValue();
	}
	@Override
	public void destroy() {
		theProcess.destroy();
		//System.out.println("frpc已关闭");
		if(hook!=null) {
			Runtime.getRuntime().removeShutdownHook(hook);
		}
		if(config_file!=null) {
			config_file.delete();
		}
	}
	@Override
	public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
		return theProcess.waitFor(timeout, unit);
	}
	@Override
	public Process destroyForcibly() {
		Process b= theProcess.destroyForcibly();
		//System.out.println("frpc已关闭");
		if(hook!=null) {
			Runtime.getRuntime().removeShutdownHook(hook);
		}
		return b;
	}
	@Override
	public boolean supportsNormalTermination() {
		return theProcess.supportsNormalTermination();
	}
	@Override
	public boolean isAlive() {
		return theProcess.isAlive();
	}
	@Override
	public long pid() {
		return theProcess.pid();
	}
	@Override
	public CompletableFuture<Process> onExit() {
		return theProcess.onExit();
	}
	@Override
	public ProcessHandle toHandle() {
		return theProcess.toHandle();
	}
	@Override
	public Info info() {
		return theProcess.info();
	}
	@Override
	public Stream<ProcessHandle> children() {
		return theProcess.children();
	}
	@Override
	public Stream<ProcessHandle> descendants() {
		return theProcess.descendants();
	}
	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);
		
		str=fi.storeToString();
		//System.out.println(fi.getServerPort());
		FrpcProcess fp=new FrpcProcess(str);
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	@Override
	protected void finalize() throws Throwable {
		
		if(theProcess!=null) {
			destroy();
		}
	}
}
