package com.tsi.comm.server.xnet;

import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;

import java.net.InetSocketAddress;

public final class NettyUtils {

    private NettyUtils() {}

    public static String getAddress(Channel ch) {
        String localIp = "local-unknown";
        String remoteIp = "remote-unknown";
        int localPort = 0;
        int remotePort = 0;
        if (ch == null) {
            return "[Local #(" + localIp + ":" + localPort + ") Remote #(" + remoteIp + ":" + remotePort + ")]";
        }
        InetSocketAddress localAddr = (InetSocketAddress)ch.localAddress();
        if (localAddr != null) {
            localIp = localAddr.getAddress().getHostAddress();
            localPort = localAddr.getPort();
        }

        InetSocketAddress remoteAddr = (InetSocketAddress)ch.remoteAddress();
        if (remoteAddr != null) {
            remoteIp = remoteAddr.getAddress().getHostAddress();
            remotePort = remoteAddr.getPort();
        }
        return "[Local #(" + localIp + ":" + localPort + ") Remote #(" + remoteIp + ":" + remotePort + ")]";
    }

    public static String getRemoteAddress(Channel ch) {
        if (ch == null) {
            return "[Remote #(255.255.255.255:0)]";
        }
        String ip = getRemoteIpAddress(ch);
        int port = getRemotePort(ch);
        return "[Remote #(" + ip + ":" + port + ")]";
    }

    public static String getLocalAddress(Channel ch) {
        if (ch == null) {
            return "[Local #(255.255.255.255:0)]";
        }
        String ip = getLocalIpAddress(ch);
        int port = getLocalPort(ch);
        return "[Local #(" + ip + ":" + port + ")]";
    }

    public static String getRemoteIpAddress(Channel ch) {
        String ip = "255.255.255.255";
        if (ch == null) {
            return ip;
        }
        InetSocketAddress inetAddr = (InetSocketAddress)ch.remoteAddress();
        if (inetAddr != null) {
            ip = inetAddr.getAddress().getHostAddress();
        }
        return ip;
    }

    public static long getRemoteIpAddressToLong(Channel ch) {
        String[] ipAddressInArray = getRemoteIpAddress(ch).split("\\.");
        long result = 0;
        if (ch == null) {
            return result;
        }
        for (int i = 0; i < ipAddressInArray.length; i++) {
            int power = 3 - i;
            int ip = Integer.parseInt(ipAddressInArray[i]);
            result += ip * Math.pow(256, power);
        }
        return result;
    }

    public static int getRemotePort(Channel ch) {
        int port = 0;
        if (ch == null) {
            return port;
        }
        InetSocketAddress inetAddr = (InetSocketAddress)ch.remoteAddress();
        if (inetAddr != null) {
            port = inetAddr.getPort();
        }
        return port;
    }

    public static String getLocalIpAddress(Channel ch) {
        String ip = "127.0.0.1";
        if (ch == null) {
            return ip;
        }
        InetSocketAddress inetAddr = (InetSocketAddress)ch.localAddress();
        if (inetAddr != null) {
            ip = inetAddr.getAddress().getHostAddress();
        }
        return ip;
    }
    public static int getLocalPort(Channel ch) {
        int port = 0;
        if (ch == null) {
            return port;
        }
        InetSocketAddress inetAddr = (InetSocketAddress)ch.localAddress();
        if (inetAddr != null) {
            port = inetAddr.getPort();
        }
        return port;
    }

    public static boolean isEpollAvailable() {
        // Netty epoll transport does not work with WSL (Windows Sybsystem for Linux) yet.
/*
        boolean HAS_WSLENV = System.getenv("WSLENV") != null;
        return Epoll.isAvailable() && !HAS_WSLENV;
*/
        return Epoll.isAvailable();
    }

    public static EventLoopGroup newEventLoopGroup(int nThreads, String threadPoolName) {
        if (isEpollAvailable()) {
            if (threadPoolName.equals("")) {
                return new EpollEventLoopGroup(nThreads);
            }
            else {
                return new EpollEventLoopGroup(nThreads, new DefaultThreadFactory("epo"+threadPoolName));
            }
        } else {
            if (threadPoolName.equals("")) {
                return new NioEventLoopGroup(nThreads);
            }
            else {
                return new NioEventLoopGroup(nThreads, new DefaultThreadFactory("nio" + threadPoolName));
            }
        }
    }

    public static Class<? extends SocketChannel> getSocketChannel() {
        if (isEpollAvailable()) {
            return EpollSocketChannel.class;
        } else {
            return NioSocketChannel.class;
        }
    }

    public static Class<? extends ServerSocketChannel> getServerSocketChannel() {
        if (isEpollAvailable()) {
            return EpollServerSocketChannel.class;
        } else {
            return NioServerSocketChannel.class;
        }
    }

}
