/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.resolver.dns;

import static java.util.Objects.requireNonNull;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Provides an infinite sequence of DNS server addresses to {@link DnsNameResolver}.
 */
@SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
public abstract class DnsServerAddresses {
    /**
     * @deprecated Use {@link DefaultDnsServerAddressStreamProvider#defaultAddressList()}.
     * <p>
     * Returns the list of the system DNS server addresses. If it failed to retrieve the list of the system DNS server
     * addresses from the environment, it will return {@code "8.8.8.8"} and {@code "8.8.4.4"}, the addresses of the
     * Google public DNS servers.
     */
    @Deprecated
    public static List<InetSocketAddress> defaultAddressList() {
        return DefaultDnsServerAddressStreamProvider.defaultAddressList();
    }

    /**
     * @deprecated Use {@link DefaultDnsServerAddressStreamProvider#defaultAddresses()}.
     * <p>
     * Returns the {@link DnsServerAddresses} that yields the system DNS server addresses sequentially. If it failed to
     * retrieve the list of the system DNS server addresses from the environment, it will use {@code "8.8.8.8"} and
     * {@code "8.8.4.4"}, the addresses of the Google public DNS servers.
     * <p>
     * This method has the same effect with the following code:
     * <pre>
     * DnsServerAddresses.sequential(DnsServerAddresses.defaultAddressList());
     * </pre>
     * </p>
     */
    @Deprecated
    public static DnsServerAddresses defaultAddresses() {
        return DefaultDnsServerAddressStreamProvider.defaultAddresses();
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} sequentially. Once the
     * last address is yielded, it will start again from the first address.
     */
    public static DnsServerAddresses sequential(Iterable<? extends InetSocketAddress> addresses) {
        return sequential0(sanitize(addresses));
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} sequentially. Once the
     * last address is yielded, it will start again from the first address.
     */
    public static DnsServerAddresses sequential(InetSocketAddress... addresses) {
        return sequential0(sanitize(addresses));
    }

    private static DnsServerAddresses sequential0(final List<InetSocketAddress> addresses) {
        if (addresses.size() == 1) {
            return singleton(addresses.get(0));
        }

        return new DefaultDnsServerAddresses("sequential", addresses) {
            @Override
            public DnsServerAddressStream stream() {
                return new SequentialDnsServerAddressStream(addresses, 0);
            }
        };
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code address} in a shuffled order. Once all
     * addresses are yielded, the addresses are shuffled again.
     */
    public static DnsServerAddresses shuffled(Iterable<? extends InetSocketAddress> addresses) {
        return shuffled0(sanitize(addresses));
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a shuffled order. Once all
     * addresses are yielded, the addresses are shuffled again.
     */
    public static DnsServerAddresses shuffled(InetSocketAddress... addresses) {
        return shuffled0(sanitize(addresses));
    }

    private static DnsServerAddresses shuffled0(List<InetSocketAddress> addresses) {
        if (addresses.size() == 1) {
            return singleton(addresses.get(0));
        }

        return new DefaultDnsServerAddresses("shuffled", addresses) {
            @Override
            public DnsServerAddressStream stream() {
                return new ShuffledDnsServerAddressStream(addresses);
            }
        };
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a rotational sequential
     * order. It is similar to {@link #sequential(Iterable)}, but each {@link DnsServerAddressStream} starts from
     * a different starting point.  For example, the first {@link #stream()} will start from the first address, the
     * second one will start from the second address, and so on.
     */
    public static DnsServerAddresses rotational(Iterable<? extends InetSocketAddress> addresses) {
        return rotational0(sanitize(addresses));
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields the specified {@code addresses} in a rotational sequential
     * order. It is similar to {@link #sequential(Iterable)}, but each {@link DnsServerAddressStream} starts from
     * a different starting point.  For example, the first {@link #stream()} will start from the first address, the
     * second one will start from the second address, and so on.
     */
    public static DnsServerAddresses rotational(InetSocketAddress... addresses) {
        return rotational0(sanitize(addresses));
    }

    private static DnsServerAddresses rotational0(List<InetSocketAddress> addresses) {
        if (addresses.size() == 1) {
            return singleton(addresses.get(0));
        }

        return new RotationalDnsServerAddresses(addresses);
    }

    /**
     * Returns the {@link DnsServerAddresses} that yields only a single {@code address}.
     */
    public static DnsServerAddresses singleton(final InetSocketAddress address) {
        requireNonNull(address, "address");
        if (address.isUnresolved()) {
            throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + address);
        }

        return new SingletonDnsServerAddresses(address);
    }

    private static List<InetSocketAddress> sanitize(Iterable<? extends InetSocketAddress> addresses) {
        requireNonNull(addresses, "addresses");

        final List<InetSocketAddress> list;
        if (addresses instanceof Collection) {
            list = new ArrayList<>(((Collection<?>) addresses).size());
        } else {
            list = new ArrayList<>(4);
        }

        for (InetSocketAddress a : addresses) {
            if (a == null) {
                break;
            }
            if (a.isUnresolved()) {
                throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
            }
            list.add(a);
        }

        if (list.isEmpty()) {
            throw new IllegalArgumentException("empty addresses");
        }

        return list;
    }

    private static List<InetSocketAddress> sanitize(InetSocketAddress[] addresses) {
        requireNonNull(addresses, "addresses");

        List<InetSocketAddress> list = new ArrayList<>(addresses.length);
        for (InetSocketAddress a: addresses) {
            if (a == null) {
                break;
            }
            if (a.isUnresolved()) {
                throw new IllegalArgumentException("cannot use an unresolved DNS server address: " + a);
            }
            list.add(a);
        }

        if (list.isEmpty()) {
            return DefaultDnsServerAddressStreamProvider.defaultAddressList();
        }

        return list;
    }

    /**
     * Starts a new infinite stream of DNS server addresses. This method is invoked by {@link DnsNameResolver} on every
     * uncached {@link DnsNameResolver#resolve(String)}or {@link DnsNameResolver#resolveAll(String)}.
     */
    public abstract DnsServerAddressStream stream();
}
