package net.northking.cctp.element.internal.protocol.socket;

import net.northking.cctp.element.core.exception.ComponentRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * SocketClient
 *
 * @author fengxiang.zhang
 * @date 2022/4/6
 * @since 1.0
 */
public class SocketClient {

    private static final Logger logger= LoggerFactory.getLogger(SocketClient.class);

    private static final int MAX_TIMEOUT = 5 * 60 * 1000;
    /**
     * 目标IP
     */
    private String ip;
    /**
     * 目标端口
     */
    private int port;
    /**
     * 连接超时毫秒
     */
    private int connectTimeout;
    /**
     * 读超时毫秒
     */
    private int readTimeout;
    /**
     * 自定义报文协议
     */
    private MessageHandler messageHandler;

    private Socket socket;

    public SocketClient(String ip, int port, int connectTimeout, int readTimeout, MessageHandler messageHandler) {
        this.ip = ip;
        this.port = port;
        this.connectTimeout = connectTimeout > MAX_TIMEOUT ? MAX_TIMEOUT : connectTimeout;
        this.readTimeout = readTimeout > MAX_TIMEOUT ? MAX_TIMEOUT : readTimeout;
        this.messageHandler = messageHandler;
    }

    public SocketClient connect() throws ComponentRuntimeException {
        try {
            socket = new Socket();
            socket.setTcpNoDelay(true);
            socket.setReuseAddress(true);
            socket.setSoTimeout(readTimeout);
            socket.connect(new InetSocketAddress(ip, port), connectTimeout);
        } catch (Exception e) {
            if (e instanceof UnknownHostException) {
                throw new ComponentRuntimeException(String.format("连接目标地址[%s:%d]失败：%s", ip, port, "Unknown host"), e);
            }
            throw new ComponentRuntimeException(String.format("连接目标地址[%s:%d]失败：%s", ip, port, e.getMessage()), e);
        }
        return this;
    }

    public SocketResponseData send(String request) throws Exception {
        byte[] requestMsg;
        try {
            requestMsg = messageHandler.getRequestMessage(request);
        } catch (Exception e) {
            throw new ComponentRuntimeException("组装请求报文失败->" + e.getMessage(), e);
        }
        try {
            OutputStream out = socket.getOutputStream();
            out.write(requestMsg);
            out.flush();
            FutureTask<SocketResponseData> task = messageHandler.getResponseFuture(socket.getInputStream());
            new Thread(task).start();
            try {
                return task.get(readTimeout, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                if (e instanceof TimeoutException) {
                    throw new ComponentRuntimeException("读取响应报文超时");
                }
                throw e;
            }
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e) {
                }
            }
        }
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public MessageHandler getMessageHandler() {
        return messageHandler;
    }

    public void setMessageHandler(MessageHandler messageHandler) {
        this.messageHandler = messageHandler;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }
}
