package nxm.sys.net;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Foreign;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.TextFile;

/** Internal Use Only: Various networking utilities.

    @author  J. Czechowski IV
    @since   NeXtMidas 3.3.1
 */
@InternalUseOnly
public final class NetUtil {
  private NetUtil () { } // prevent instantiation

  private static String[] domainNames; // Domain names to search

  /** Returns the local host.  This is identical to
      <code>getFQDN(InetAddress.getLocalHost())</code>.
      @return The FQDN of the local host if available or the short name (or numeric)
              IP address if the FQDN could not be found.
      @throws UnknownHostException If the local host name could not be resolved.
   */
  public static String getLocalHost () throws UnknownHostException {
    return getFQDN(InetAddress.getLocalHost());
  }

  /** Returns the IP address associated with the given name. This is identical to
      <code>getFQDN(InetAddress.getByName(name))</code> except that if <code>getByName</code>
      throws a {@link UnknownHostException} or an {@link SecurityException} (as
      was the case for applets, which are no longer supported) the name passed in is returned.
      @param name The host name.
      @return The FQDN of the given host if available or the short name (or numeric)
              IP address if the FQDN could not be found.
   */
  public static String getFQDN (String name) {
    try {
      return (StringUtil.isNull(name))? name : getFQDN(InetAddress.getByName(name));
    }
    catch (UnknownHostException e) {
      return name;
    }
    catch (SecurityException e) {
      return name;
    }
  }

  /** Attempts to look up the Fully Qualified Domain Name (FQDN) for an address. <br>
      <br>
      This is a companion to methods in {@link InetAddress} that takes additional
      steps in trying to resolve the fully-qualified domain name (FQDN). For some
      reason the methods in {@link InetAddress} only make a half-hearted attempt
      to resolve the FQDN for the local address (probably cross-platform reasons).
      This method will try additional steps to return the FQDN, though it is not
      exhaustive. The algorithm used here is intentionally "undefined" to allow
      future versions to alter it as may seem appropriate.<br>
      <br>
      If <code>hostAddr</code> is null, this method simply returns null.
      @param hostAddr The host address.
      @return An {@link InetAddress} with the FQDN matching the address given
              if found or <code>hostAddr</code> if not found.
   */
  public static String getFQDN (InetAddress hostAddr) {
    if (hostAddr == null) return null;
    String hostName = hostAddr.getHostName();
    String address  = hostAddr.getHostAddress();

    if (hostName.contains(".")) return hostName;

    for (String domainName : getDomainNames()) {
      try {
        String      testName = hostName+"."+domainName;
        InetAddress testHost = InetAddress.getByName(testName);
        if (address.equals(testHost.getHostAddress())) {
          return testName; // Success!
        }
      }
      catch (Exception e) {
        // Ignore exception and go on to next one
      }
    }

    // We do the getCanonicalHostName() last since it can be very slow (30+ sec)
    // in the case where a nslookup may time out. The result is a long wait that
    // results in getCanonicalHostName() returning the IP address rather than a
    // host name (hence our ".equals(address)" check).
    String canonical = hostAddr.getCanonicalHostName();
    return (canonical.equals(address))? hostName : canonical;
  }

  /** Attempts to look up the FQDN for an address. */
  @InternalUseOnly
  private static synchronized String[] getDomainNames () {
    if (domainNames != null) return domainNames;

    if (Shell.ostype.equals("UNIX")) {
      // ==== USE SEARCH PATH ==================================================
      // First look in /etc/resolv.conf.
      TextFile tf = new TextFile(null, "/etc/resolv.conf");

      if (tf.open(TextFile.NOABORT)) {
        String   domain = null;
        String[] search = null;

        for (String line : tf.readAllLines()) {
          int i = line.indexOf('#');
          if (i > 0) line = line.substring(0,i); // Strip off comments
          line = line.trim();

          if (line.startsWith("domain ") || line.startsWith("domain\t")) {
            domain = line.substring(6).trim();
          }
          else if (line.startsWith("search ") || line.startsWith("search\t")) {
            search = line.substring(6).trim().split("[ \t]+");

            // Old unix rule -- only first 6 are supported, any others should
            // be ignored
            if (search.length > 6) search = Arrays.copyOf(search, 6);
          }
        }
        tf.close();

        if (search != null) return domainNames = search;
        if (domain != null) return domainNames = new String[] { domain };
      }

      // ==== USE LOCAL DOMAIN =================================================
      // This uses same domain as host's domain. This must come AFTER checking
      // in /etc/resolv.conf. Note that we need to use InetAddress.getLocalHost()
      // here to prevent a cyclical call.
      try {
        InetAddress localAddr = InetAddress.getLocalHost();
        String      localHost = localAddr.getCanonicalHostName();
        int         index     = localHost.indexOf('.');

        if (index > 0) return new String[] { localHost.substring(index+1) };
      }
      catch (Exception e) {
        // Ignore this error
      }
    }
    else if (Shell.ostype.equals("DOS")) {
      StringBuilder out    = new StringBuilder();
      StringBuilder err    = new StringBuilder();
      int           status = Foreign.runInternal("ipconfig /all", out, err);
      String        domain = null;
      List<String>  search = new ArrayList<>();

      if (status == 0) {
        String lastName = null;
        for (String line : out.toString().split("\r\n")) {
          line = line.trim();

          if (line.isEmpty()) {
            lastName = null;
            continue;
          }

          String value;
          String name;
          int    index  = line.indexOf(':');

          if (index < 0) {
            name  = lastName;
            value = line;
          }
          else {
            name  = line.substring(0,index).trim().toLowerCase();
            value = line.substring(index+1).trim();
          }

          if (value.isEmpty() || name==null || name.isEmpty()) {  // name can be null on Windows
            // ignore
          }
          else if (name.startsWith("primary dns suffix")) {
            domain = value;
          }
          else if (name.startsWith("dns suffix search list")) {
            String[] vals = value.split("[ \t]+");
            search.addAll(Arrays.asList(vals));
          }
          else {
            // currently we ignore connection-specific entries
          }
          lastName = name;
        }

        if (!search.isEmpty()) return domainNames = search.toArray(StringUtil.EMPTY_STRING_ARRAY);
        if (domain    != null) return domainNames = new String[] { domain };
      }
    }

    return domainNames = StringUtil.EMPTY_STRING_ARRAY; // None found
  }
}
