// SPDX-License-Identifier: BSD-3-Clause
package org.xbill.DNS.config;

import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.Name;
import org.xbill.DNS.TextParseException;

/**
 * Base class for resolver config providers that provides a default implementation for the lists and
 * utility methods to prevent duplicates.
 *
 * @since 3.2
 */
public abstract class BaseResolverConfigProvider implements ResolverConfigProvider {
  private static final boolean IPV4_ONLY = Boolean.getBoolean("java.net.preferIPv4Stack");
  private static final boolean IPV6_FIRST = Boolean.getBoolean("java.net.preferIPv6Addresses");

  protected static final int DEFAULT_PORT = 53;

  private final List<InetSocketAddress> nameservers = new ArrayList<>(3);

  protected final Logger log = LoggerFactory.getLogger(getClass());
  protected final List<Name> searchlist = new ArrayList<>(1);

  protected final void reset() {
    // TODO v4: make a final void initialize() that clears and then calls abstract doInit()
    nameservers.clear();
    searchlist.clear();
  }

  protected void parseSearchPathList(String search, String delimiter) {
    if (search != null) {
      StringTokenizer st = new StringTokenizer(search, delimiter);
      while (st.hasMoreTokens()) {
        addSearchPath(st.nextToken());
      }
    }
  }

  protected void addSearchPath(String searchPath) {
    if (searchPath == null || searchPath.isEmpty()) {
      return;
    }

    try {
      Name n = Name.fromString(searchPath, Name.root);
      if (!searchlist.contains(n)) {
        searchlist.add(n);
        log.debug("Added {} to search paths", n);
      }
    } catch (TextParseException e) {
      log.warn("Could not parse search path {} as a dns name, ignoring", searchPath);
    }
  }

  protected void addNameserver(InetSocketAddress server) {
    if (!nameservers.contains(server)) {
      nameservers.add(server);
      log.debug("Added {} to nameservers", server);
    }
  }

  protected int parseNdots(String token) {
    if (token != null && !token.isEmpty()) {
      try {
        int ndots = Integer.parseInt(token);
        if (ndots >= 0) {
          if (ndots > 15) {
            // man resolv.conf:
            // The value for this option is silently capped to 15
            ndots = 15;
          }

          return ndots;
        }
      } catch (NumberFormatException e) {
        // ignore
      }
    }

    return 1;
  }

  @Override
  public final List<InetSocketAddress> servers() {
    if (IPV6_FIRST) {
      // prefer IPv6: return IPv6 first, then IPv4 (each in the order added)
      return nameservers.stream()
          .sorted(
              (a, b) ->
                  Integer.compare(
                      b.getAddress().getAddress().length, a.getAddress().getAddress().length))
          .collect(Collectors.toList());
    } else if (IPV4_ONLY) {
      // skip IPv6 addresses
      return nameservers.stream()
          .filter(isa -> isa.getAddress() instanceof Inet4Address)
          .collect(Collectors.toList());
    }

    // neither is specified, return in the order added
    return Collections.unmodifiableList(nameservers);
  }

  @Override
  public final List<Name> searchPaths() {
    return Collections.unmodifiableList(searchlist);
  }
}
