/*
 * 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;

import java.util.Collections;
import java.util.List;
import java.util.function.Function;

import org.apache.sshd.client.config.hosts.DefaultConfigFileHostEntryResolver;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
import org.apache.sshd.client.config.keys.ClientIdentityLoader;
import org.apache.sshd.client.global.OpenSshHostKeysHandler;
import org.apache.sshd.client.kex.DHGClient;
import org.apache.sshd.client.kex.DHGEXClient;
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.common.BaseBuilder;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.channel.Channel;
import org.apache.sshd.common.channel.RequestHandler;
import org.apache.sshd.common.compression.BuiltinCompressions;
import org.apache.sshd.common.compression.CompressionFactory;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.kex.DHFactory;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.common.session.ConnectionService;
import org.apache.sshd.server.forward.ForwardedTcpipFactory;

/**
 * SshClient builder
 */
public class ClientBuilder extends BaseBuilder<SshClient, ClientBuilder> {

    public static final Function<DHFactory, NamedFactory<KeyExchange>> DH2KEX = factory ->
            factory == null
                ? null
                : factory.isGroupExchange()
                    ? DHGEXClient.newFactory(factory)
                    : DHGClient.newFactory(factory);

    // Compression is not enabled by default for the client
    public static final List<CompressionFactory> DEFAULT_COMPRESSION_FACTORIES =
            Collections.unmodifiableList(Collections.singletonList(BuiltinCompressions.none));

    public static final List<NamedFactory<Channel>> DEFAULT_CHANNEL_FACTORIES =
            Collections.unmodifiableList(Collections.singletonList(ForwardedTcpipFactory.INSTANCE));
    public static final List<RequestHandler<ConnectionService>> DEFAULT_GLOBAL_REQUEST_HANDLERS =
            Collections.unmodifiableList(Collections.singletonList(OpenSshHostKeysHandler.INSTANCE));

    public static final ServerKeyVerifier DEFAULT_SERVER_KEY_VERIFIER = AcceptAllServerKeyVerifier.INSTANCE;
    public static final HostConfigEntryResolver DEFAULT_HOST_CONFIG_ENTRY_RESOLVER = DefaultConfigFileHostEntryResolver.INSTANCE;
    public static final ClientIdentityLoader DEFAULT_CLIENT_IDENTITY_LOADER = ClientIdentityLoader.DEFAULT;
    public static final FilePasswordProvider DEFAULT_FILE_PASSWORD_PROVIDER = FilePasswordProvider.EMPTY;

    protected ServerKeyVerifier serverKeyVerifier;
    protected HostConfigEntryResolver hostConfigEntryResolver;
    protected ClientIdentityLoader clientIdentityLoader;
    protected FilePasswordProvider filePasswordProvider;

    public ClientBuilder() {
        super();
    }

    public ClientBuilder serverKeyVerifier(ServerKeyVerifier serverKeyVerifier) {
        this.serverKeyVerifier = serverKeyVerifier;
        return me();
    }

    public ClientBuilder hostConfigEntryResolver(HostConfigEntryResolver resolver) {
        this.hostConfigEntryResolver = resolver;
        return me();
    }

    public ClientBuilder clientIdentityLoader(ClientIdentityLoader loader) {
        this.clientIdentityLoader = loader;
        return me();
    }

    public ClientBuilder filePasswordProvider(FilePasswordProvider provider) {
        this.filePasswordProvider = provider;
        return me();
    }

    @Override
    protected ClientBuilder fillWithDefaultValues() {
        super.fillWithDefaultValues();

        if (compressionFactories == null) {
            compressionFactories = NamedFactory.setUpBuiltinFactories(false, DEFAULT_COMPRESSION_FACTORIES);
        }

        if (keyExchangeFactories == null) {
            keyExchangeFactories = setUpDefaultKeyExchanges(false);
        }

        if (channelFactories == null) {
            channelFactories = DEFAULT_CHANNEL_FACTORIES;
        }

        if (globalRequestHandlers == null) {
            globalRequestHandlers = DEFAULT_GLOBAL_REQUEST_HANDLERS;
        }

        if (serverKeyVerifier == null) {
            serverKeyVerifier = DEFAULT_SERVER_KEY_VERIFIER;
        }

        if (hostConfigEntryResolver == null) {
            hostConfigEntryResolver = DEFAULT_HOST_CONFIG_ENTRY_RESOLVER;
        }

        if (clientIdentityLoader == null) {
            clientIdentityLoader = DEFAULT_CLIENT_IDENTITY_LOADER;
        }

        if (filePasswordProvider == null) {
            filePasswordProvider = DEFAULT_FILE_PASSWORD_PROVIDER;
        }

        if (factory == null) {
            factory = SshClient.DEFAULT_SSH_CLIENT_FACTORY;
        }

        return me();
    }

    @Override
    public SshClient build(boolean isFillWithDefaultValues) {
        SshClient client = super.build(isFillWithDefaultValues);
        client.setServerKeyVerifier(serverKeyVerifier);
        client.setHostConfigEntryResolver(hostConfigEntryResolver);
        client.setClientIdentityLoader(clientIdentityLoader);
        client.setFilePasswordProvider(filePasswordProvider);
        return client;
    }

    /**
     * @param ignoreUnsupported If {@code true} then all the default
     *                          key exchanges are included, regardless of whether they are currently
     *                          supported by the JCE. Otherwise, only the supported ones out of the
     *                          list are included
     * @return A {@link List} of the default {@link NamedFactory}
     * instances of the {@link KeyExchange}s according to the preference
     * order defined by {@link #DEFAULT_KEX_PREFERENCE}.
     * <B>Note:</B> the list may be filtered to exclude unsupported JCE
     * key exchanges according to the <tt>ignoreUnsupported</tt> parameter
     * @see org.apache.sshd.common.kex.BuiltinDHFactories#isSupported()
     */
    public static List<NamedFactory<KeyExchange>> setUpDefaultKeyExchanges(boolean ignoreUnsupported) {
        return NamedFactory.setUpTransformedFactories(ignoreUnsupported, DEFAULT_KEX_PREFERENCE, DH2KEX);
    }

    public static ClientBuilder builder() {
        return new ClientBuilder();
    }
}