package com.its.app.utils;

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 lombok.extern.slf4j.Slf4j;

import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;

@Slf4j
public final class NettyUtils {

    public static String getTcpAddress(Channel ch) {
        String localIp = "local-unknown";
        String remoteIp = "remote-unknown";
        int localPort = 0;
        int remotePort = 0;
        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) {
        String ip = getRemoteIpAddress(ch);
        int  port = getRemotePort(ch);
        return "[Remote #(" + ip + ":" + port + ")]";
    }

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

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

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

    public static String getLocalIpAddress(Channel ch) {
        String ip = "unknown";
        InetSocketAddress inetAddr = (InetSocketAddress)ch.localAddress();
        if (inetAddr != null) {
            ip = inetAddr.getAddress().getHostAddress();
        }
        return ip;
    }

    public static int getLocalPort(Channel ch) {
        int port = 0;
        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;
        }
    }

    public static final String OS_NAME = System.getProperty("os.name");
    private static boolean isLinuxPlatform = false;
    private static boolean isWindowsPlatform = false;

    static {
        if (OS_NAME != null && OS_NAME.toLowerCase().contains("linux")) {
            isLinuxPlatform = true;
        }

        if (OS_NAME != null && OS_NAME.toLowerCase().contains("windows")) {
            isWindowsPlatform = true;
        }
    }

    public static String getLocalAddress() {
        // Traversal Network interface to get the first non-loopback and non-private address
        Enumeration<NetworkInterface> enumeration = null;
        try {
            enumeration = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            log.error("getLocalAddress: getNetworkInterfaces");
            return null;
        }
        ArrayList<String> ipv4Result = new ArrayList<String>();
        ArrayList<String> ipv6Result = new ArrayList<String>();
        while (enumeration.hasMoreElements()) {
            final NetworkInterface networkInterface = enumeration.nextElement();
            final Enumeration<InetAddress> en = networkInterface.getInetAddresses();
            while (en.hasMoreElements()) {
                final InetAddress address = en.nextElement();
                if (!address.isLoopbackAddress()) {
                    if (address instanceof Inet6Address) {
                        ipv6Result.add(normalizeHostAddress(address));
                    } else {
                        ipv4Result.add(normalizeHostAddress(address));
                    }
                }
            }
        }

        // prefer ipv4
        if (!ipv4Result.isEmpty()) {
            for (String ip : ipv4Result) {
                if (ip.startsWith("127.0") || ip.startsWith("192.168")) {
                    continue;
                }
                return ip;
            }

            return ipv4Result.get(ipv4Result.size() - 1);
        } else if (!ipv6Result.isEmpty()) {
            return ipv6Result.get(0);
        }
        //If failed to find,fall back to localhost
        final InetAddress localHost;
        try {
            localHost = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            log.error("getLocalAddress: UnknownHostException");
            return null;
        }
        return normalizeHostAddress(localHost);
    }

    public static String normalizeHostAddress(final InetAddress localHost) {
        if (localHost instanceof Inet6Address) {
            return "[" + localHost.getHostAddress() + "]";
        } else {
            return localHost.getHostAddress();
        }
    }

    public static SocketAddress string2SocketAddress(final String addr) {
        String[] s = addr.split(":");
        InetSocketAddress isa = new InetSocketAddress(s[0], Integer.parseInt(s[1]));
        return isa;
    }

    public static String socketAddress2String(final SocketAddress addr) {
        StringBuilder sb = new StringBuilder();
        InetSocketAddress inetSocketAddress = (InetSocketAddress) addr;
        sb.append(inetSocketAddress.getAddress().getHostAddress());
        sb.append(":");
        sb.append(inetSocketAddress.getPort());
        return sb.toString();
    }

    public static String parseChannelRemoteAddr(final Channel channel) {
        if (null == channel) {
            return "";
        }
        SocketAddress remote = channel.remoteAddress();
        final String addr = remote != null ? remote.toString() : "";

        if (addr.length() > 0) {
            int index = addr.lastIndexOf("/");
            if (index >= 0) {
                return addr.substring(index + 1);
            }

            return addr;
        }

        return "";
    }

    public static String parseSocketAddressAddr(SocketAddress socketAddress) {
        if (socketAddress != null) {
            final String addr = socketAddress.toString();

            if (addr.length() > 0) {
                return addr.startsWith("/") ? addr.substring(1) : addr;
            }
        }
        return "";
    }
}