/*
 * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.foreign;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Optional;
import java.util.List;

import jdk.internal.foreign.FunctionDescriptorImpl;

/**
 * A function descriptor models the signature of a foreign function. A function
 * descriptor is made up of zero or more argument layouts, and zero or one return layout.
 * A function descriptor is used to create
 * {@linkplain Linker#downcallHandle(MemorySegment, FunctionDescriptor, Linker.Option...) downcall method handles}
 * and
 * {@linkplain Linker#upcallStub(MethodHandle, FunctionDescriptor, Arena, Linker.Option...) upcall stubs}.
 *
 * @implSpec
 * Implementing classes are immutable, thread-safe and
 * <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
 *
 * @see MemoryLayout
 * @since 22
 */
public sealed interface FunctionDescriptor permits FunctionDescriptorImpl {

    /**
     * {@return the return layout (if any) of this function descriptor}
     */
    Optional<MemoryLayout> returnLayout();

    /**
     * {@return the argument layouts of this function descriptor (as an unmodifiable list)}
     */
    List<MemoryLayout> argumentLayouts();

    /**
     * Returns a function descriptor with the given argument layouts appended to the
     * argument layouts of this function descriptor.
     *
     * @param addedLayouts the argument layouts to append
     * @throws IllegalArgumentException if one of the layouts in {@code addedLayouts} is
     *         a padding layout
     * @return a new function descriptor, with the provided additional argument layouts
     */
    FunctionDescriptor appendArgumentLayouts(MemoryLayout... addedLayouts);

    /**
     * Returns a function descriptor with the given argument layouts inserted at the
     * given index, into the argument layout array of this function descriptor.
     *
     * @param index the index at which to insert the arguments
     * @param addedLayouts the argument layouts to insert at given index
     * @return a new function descriptor, with the provided additional argument layouts
     * @throws IllegalArgumentException if one of the layouts in {@code addedLayouts}
     *         is a padding layout
     * @throws IllegalArgumentException if {@code index < 0 || index > argumentLayouts().size()}
     */
    FunctionDescriptor insertArgumentLayouts(int index, MemoryLayout... addedLayouts);

    /**
     * Returns a function descriptor with the provided return layout.
     * @param newReturn the new return layout
     * @throws IllegalArgumentException if {@code newReturn} is a padding layout
     * @return a new function descriptor, with the provided return layout
     */
    FunctionDescriptor changeReturnLayout(MemoryLayout newReturn);

    /**
     * {@return a new function descriptor, with no return layout}
     */
    FunctionDescriptor dropReturnLayout();

    /**
     * Returns the method type consisting of the carrier types of the layouts in this
     * function descriptor.
     * <p>
     * The carrier type of a layout {@code L} is determined as follows:
     * <ul>
     * <li>If {@code L} is a {@link ValueLayout} the carrier type is
     *     determined through {@link ValueLayout#carrier()}.</li>
     * <li>If {@code L} is a {@link GroupLayout} or a {@link SequenceLayout},
     *     the carrier type is {@link MemorySegment}.</li>
     * </ul>
     *
     * @apiNote A function descriptor cannot, by construction, contain any padding
     *          layouts. As such, it is not necessary to specify how padding layout
     *          should be mapped to carrier types.
     *
     * @return the method type consisting of the carrier types of the layouts in this
     *         function descriptor
     */
    MethodType toMethodType();

    /**
     * Creates a function descriptor with the given return and argument layouts.
     *
     * @param resLayout the return layout
     * @param argLayouts the argument layouts
     * @throws IllegalArgumentException if {@code resLayout} is a padding layout
     * @throws IllegalArgumentException if one of the layouts in {@code argLayouts}
     *         is a padding layout
     * @return a new function descriptor with the provided return and argument layouts
     */
    static FunctionDescriptor of(MemoryLayout resLayout, MemoryLayout... argLayouts) {
        Objects.requireNonNull(resLayout);
        // Null checks are implicit in List.of(argLayouts)
        return FunctionDescriptorImpl.of(resLayout, List.of(argLayouts));
    }

    /**
     * Creates a function descriptor with the given argument layouts and no return
     * layout. This is useful to model functions that return no values.
     *
     * @param argLayouts the argument layouts
     * @throws IllegalArgumentException if one of the layouts in {@code argLayouts}
     *         is a padding layout
     * @return a new function descriptor with the provided argument layouts
     */
    static FunctionDescriptor ofVoid(MemoryLayout... argLayouts) {
        // Null checks are implicit in List.of(argLayouts)
        return FunctionDescriptorImpl.ofVoid(List.of(argLayouts));
    }
}
