/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF 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 org.apache.sshd.client.subsystem.sftp;

import java.util.Collection;
import java.util.List;
import java.util.stream.StreamSupport;

import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;

/**
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
 */
@FunctionalInterface
public interface SftpVersionSelector {
    /**
     * An {@link SftpVersionSelector} that returns the current version
     */
    SftpVersionSelector CURRENT = new NamedVersionSelector("CURRENT", (session, initial, current, available) -> current);

    /**
     * An {@link SftpVersionSelector} that returns the maximum available version
     */
    SftpVersionSelector MAXIMUM = new NamedVersionSelector(
            "MAXIMUM",
            (session, initial, current, available) -> GenericUtils.stream(available).mapToInt(Integer::intValue).max()
                    .orElse(current));

    /**
     * An {@link SftpVersionSelector} that returns the minimum available version
     */
    SftpVersionSelector MINIMUM = new NamedVersionSelector(
            "MINIMUM",
            (session, initial, current, available) -> GenericUtils.stream(available).mapToInt(Integer::intValue).min()
                    .orElse(current));

    /**
     * @param  session   The {@link ClientSession} through which the SFTP connection is made
     * @param  initial   If {@code true} then this is the initial version sent via {@code SSH_FXP_INIT} otherwise it is
     *                   a re-negotiation.
     * @param  current   The current version negotiated with the server
     * @param  available Extra versions available - may be empty and/or contain only the current one
     * @return           The new requested version - if same as current, then nothing is done
     */
    int selectVersion(ClientSession session, boolean initial, int current, List<Integer> available);

    /**
     * Creates a selector the always returns the requested (fixed version) regardless of what the current or reported
     * available versions are. If the requested version is not reported as available then an exception will be
     * eventually thrown by the client during re-negotiation phase.
     *
     * @param  version The requested version
     * @return         The {@link SftpVersionSelector}
     */
    static SftpVersionSelector fixedVersionSelector(int version) {
        return new NamedVersionSelector(Integer.toString(version), (session, initial, current, available) -> version);
    }

    /**
     * Selects a version in order of preference - if none of the preferred versions is listed as available then an
     * exception is thrown when the {@link SftpVersionSelector#selectVersion(ClientSession, boolean, int, List)} method
     * is invoked
     *
     * @param  preferred The preferred versions in decreasing order of preference (i.e., most preferred is 1st) - may
     *                   not be {@code null}/empty
     * @return           A {@link SftpVersionSelector} that attempts to select the most preferred version that is also
     *                   listed as available.
     */
    static SftpVersionSelector preferredVersionSelector(int... preferred) {
        return preferredVersionSelector(NumberUtils.asList(preferred));
    }

    /**
     * Selects a version in order of preference - if none of the preferred versions is listed as available then an
     * exception is thrown when the {@link SftpVersionSelector#selectVersion(ClientSession, boolean, int, List)} method
     * is invoked
     *
     * @param  preferred The preferred versions in decreasing order of preference (i.e., most preferred is 1st)
     * @return           A {@link SftpVersionSelector} that attempts to select the most preferred version that is also
     *                   listed as available.
     */
    static SftpVersionSelector preferredVersionSelector(Iterable<? extends Number> preferred) {
        ValidateUtils.checkNotNullAndNotEmpty((Collection<?>) preferred, "Empty preferred versions");
        return new NamedVersionSelector(
                GenericUtils.join(preferred, ','),
                (session, initial, current, available) -> StreamSupport.stream(preferred.spliterator(), false)
                        .mapToInt(Number::intValue)
                        .filter(v -> (v == current) || available.contains(v))
                        .findFirst()
                        .orElseThrow(() -> new IllegalStateException(
                                "Preferred versions (" + preferred + ") not available: " + available)));
    }

    class NamedVersionSelector implements SftpVersionSelector {
        private final String name;
        private final SftpVersionSelector selector;

        public NamedVersionSelector(String name, SftpVersionSelector selector) {
            this.name = name;
            this.selector = selector;
        }

        @Override
        public int selectVersion(ClientSession session, boolean initial, int current, List<Integer> available) {
            return selector.selectVersion(session, initial, current, available);
        }

        @Override
        public String toString() {
            return name;
        }
    }
}
