package net.geedge.api.config;

import cn.hutool.log.Log;
import com.pty4j.PtyProcessBuilder;
import net.geedge.api.entity.EnvApiYml;
import net.geedge.api.util.AdbCommandBuilder;
import net.geedge.api.util.AdbUtil;
import net.geedge.api.util.CommandExec;
import net.geedge.common.T;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AdbShellProxyHandler extends TextWebSocketHandler {

    private static final Log log = Log.get();

    private EnvApiYml.Adb adb;

    private Process process = null;
    private InputStream inputStream = null;
    private OutputStream outputStream = null;
    private ExecutorService executorService = Executors.newFixedThreadPool(2);

    public AdbShellProxyHandler(EnvApiYml.Adb adb) {
        this.adb = adb;
    }

    @Override
    public synchronized void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("[afterConnectionEstablished] [WebSocket connection established] [websocket uri: {}]", session.getUri());
        super.afterConnectionEstablished(session);

        List<String> cmd = AdbCommandBuilder.builder()
                .serial(AdbUtil.getInstance(adb, new CommandExec(null)).getSerial())
                .buildShellCommand("shell")
                .build();

        Map<String, String> env = new HashMap<>(System.getenv());
        env.put("TERM", "xterm");

        // start process
        process = new PtyProcessBuilder()
                .setCommand(cmd.toArray(new String[]{}))
                .setEnvironment(env)
                .setRedirectErrorStream(true)
                .start();

//        process = new PtyProcessBuilder()
//                .setCommand(new String[]{"cmd.exe", "/C", "D:\\softwares\\platform-tools\\platform-tools\\adb.exe shell"})
//                .setEnvironment(env)
//                .setRedirectErrorStream(true)
//                .start();

        // stream
        inputStream = process.getInputStream();
        outputStream = process.getOutputStream();

        // server to client
        executorService.submit(() -> {
            byte[] buffer = new byte[1024 * 4];
            try {
                while (session != null && session.isOpen()) {
                    int size = inputStream.read(buffer);
                    if (size == -1) {
                        try {
                            if (session.isOpen()) {
                                session.sendMessage(new TextMessage("Connection closed \r\n"));
                            }
                        } catch (Exception e) {
                        }
                        return;
                    }
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < size; i++) {
                        char chr = (char) (buffer[i] & 0xff);
                        sb.append(chr);
                    }

                    String message = sb.toString();
                    message = T.StrUtil.str(message.getBytes(T.DigestUtils.getEncoding(message)), "UTF-8");

                    session.sendMessage(new TextMessage(message));
                }
            } catch (Exception e) {
                log.error(e, "[serverToClient] [error]");
            }
        });
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        if (T.ObjectUtil.isNotNull(outputStream)) {
            // write cmd byte
            T.IoUtil.write(outputStream, false, message.getPayload().getBytes());
            T.IoUtil.flush(outputStream);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("[afterConnectionClosed] [WebSocket connection closed] [websocket uri: {}]", session.getUri());
        super.afterConnectionClosed(session, status);

        // close resources
        this.closeResources();
    }

    /**
     * close resources
     */
    private void closeResources() {
        try {
            T.IoUtil.close(outputStream);
            T.IoUtil.close(inputStream);
            if (process != null)
                process.destroy();
        } catch (Exception e) {
        }
    }

}