/*
 * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed 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.helidon.common.configurable;

import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;

import io.helidon.config.Config;

/**
 * Supplier of a custom thread pool with defaults appropriate for a thread-per-request server.
 * The returned thread pool supports {@link io.helidon.common.context.Context} propagation.
 */
public final class ServerThreadPoolSupplier implements Supplier<ExecutorService> {

    private static final int MINIMUM_CORES = 2;
    private static final int DEFAULT_THREADS_PER_CORE = 8;
    private static final int DEFAULT_QUEUE_CAPACITY = Integer.MAX_VALUE;

    private final ThreadPoolSupplier supplier;

    private ServerThreadPoolSupplier(final ThreadPoolSupplier.Builder builder) {
        this.supplier = builder.build();
    }

    @Override
    public ExecutorService get() {
        return supplier.get();
    }

    /**
     * Create a new fluent API builder to build thread pool supplier.
     *
     * @return a builder instance
     */
    public static ThreadPoolSupplier.Builder builder() {

        // Set defaults appropriate to a thread-per-request model, based on the number of cores.

        final int cores = Math.max(Runtime.getRuntime().availableProcessors(), MINIMUM_CORES);
        final int corePoolSize = cores * DEFAULT_THREADS_PER_CORE;

        // Since the ExecutorService only adds threads above the corePoolSize when the queue is full, setting
        // maxPoolSize > corePoolSize with a large queue is meaningless unless under extreme load. To ensure
        // we can actually use the max under normal load, use corePoolSize as maxPoolSize by default.

        return ThreadPoolSupplier.builder()
                                 .corePoolSize(corePoolSize)
                                 .maxPoolSize(corePoolSize)
                                 .queueCapacity(DEFAULT_QUEUE_CAPACITY);
    }

    /**
     * Create a new thread pool supplier with default configuration.
     *
     * @return a new thread pool supplier with default configuration
     */
    public static ThreadPoolSupplier create() {
        return builder().build();
    }

    /**
     * Create supplier from configuration.
     *
     * @param config config instance
     * @return a new thread pool supplier configured from config
     */
    public static ThreadPoolSupplier create(Config config) {
        return builder().config(config)
                        .build();
    }
}
