/*
 * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.lang.classfile;

import java.lang.classfile.attribute.SignatureAttribute;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.constant.ClassDesc;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.List;
import java.util.Optional;

import jdk.internal.classfile.impl.SignaturesImpl;
import jdk.internal.classfile.impl.Util;

import static java.util.Objects.requireNonNull;

/**
 * Models generic Java type signatures, as defined in JVMS {@jvms 4.7.9.1}.
 *
 * @see Type
 * @see SignatureAttribute
 * @jls 4.1 The Kinds of Types and Values
 * @jvms 4.7.9.1 Signatures
 * @sealedGraph
 * @since 24
 */
public sealed interface Signature {

    /** {@return the raw signature string} */
    String signatureString();

    /**
     * Parses a Java type signature from a raw string.
     *
     * @param javaTypeSignature raw Java type signature string
     * @return a Java type signature
     * @throws IllegalArgumentException if the string is not a valid Java type
     *         signature string
     */
    public static Signature parseFrom(String javaTypeSignature) {
        return new SignaturesImpl(javaTypeSignature).parseSignature();
    }

    /**
     * {@return a Java type signature from a field descriptor}  The returned
     * signature represents a reifiable type (JLS {@jls 4.7}).
     *
     * @param classDesc the symbolic description of the Java type
     */
    public static Signature of(ClassDesc classDesc) {
        requireNonNull(classDesc);
        if (classDesc.isArray())
            return ArrayTypeSig.of(of(classDesc.componentType()));
        if (classDesc.isPrimitive())
            return BaseTypeSig.of(classDesc);
        return ClassTypeSig.of(classDesc);
    }

    /**
     * Models the signature of a primitive type (JLS {@jls 4.2}) or void.
     *
     * @jls 4.2 Primitive Types and Values
     * @jvms 4.7.9.1 Signatures
     * @since 24
     */
    public sealed interface BaseTypeSig extends Signature
            permits SignaturesImpl.BaseTypeSigImpl {

        /** {@return the single-letter descriptor for the base type} */
        char baseType();

        /**
         * {@return the signature of a primitive type or void}
         * @param classDesc a symbolic descriptor for the base type, must correspond
         *                  to a primitive type
         * @throws IllegalArgumentException if the {@code classDesc} is not
         *         primitive
         */
        public static BaseTypeSig of(ClassDesc classDesc) {
            requireNonNull(classDesc);
            if (!classDesc.isPrimitive())
                throw new IllegalArgumentException("primitive class type required");
            return new SignaturesImpl.BaseTypeSigImpl(classDesc.descriptorString().charAt(0));
        }

        /**
         * {@return the signature of a primitive type or void}
         * @param baseType the single-letter descriptor for the base type
         * @throws IllegalArgumentException if the {@code baseType} is not a
         *         valid descriptor character for a primitive type or void
         */
        public static BaseTypeSig of(char baseType) {
            if ("VIJCSBFDZ".indexOf(baseType) < 0)
                throw new IllegalArgumentException("invalid base type signature");
            return new SignaturesImpl.BaseTypeSigImpl(baseType);
        }
    }

    /**
     * Models the signature of a reference type, which may be a class, interface,
     * type variable, or array type.
     *
     * @jls 4.3 Reference Types and Values
     * @jvms 4.7.9.1 Signatures
     * @sealedGraph
     * @since 24
     */
    public sealed interface RefTypeSig
            extends Signature
            permits ArrayTypeSig, ClassTypeSig, TypeVarSig {
    }

    /**
     * Models the signature of a possibly-parameterized class or interface type.
     *
     * @see Type
     * @see ParameterizedType
     * @jvms 4.7.9.1 Signatures
     * @since 24
     */
    public sealed interface ClassTypeSig
            extends RefTypeSig, ThrowableSig
            permits SignaturesImpl.ClassTypeSigImpl {

        /**
         * {@return the signature of the class that this class is a member of,
         * only if this is a member class}  Note that the outer class may be
         * absent if it is not a parameterized type.
         *
         * @jls 4.5 Parameterized Types
         */
        Optional<ClassTypeSig> outerType();

        /**
         * {@return the class or interface name; includes the {@linkplain
         * ClassEntry##internalname slash-separated} package name if there is no
         * outer type}
         */
        String className();

        /**
         * {@return this class or interface, as a symbolic descriptor}
         */
        default ClassDesc classDesc() {
            var outer = outerType();
            return outer.isEmpty() ? ClassDesc.ofInternalName(className())
                    : outer.get().classDesc().nested(className());
        }

        /**
         * {@return the type arguments of this class or interface}
         * Note that the outer type may have more type arguments.
         *
         * @jls 4.5 Parameterized Types
         */
        List<TypeArg> typeArgs();

        /**
         * {@return a class or interface signature without an outer type}
         *
         * @param className the name of the class or interface
         * @param typeArgs the type arguments
         * @throws IllegalArgumentException if {@code className} does not
         *         represent a class or interface
         */
        public static ClassTypeSig of(ClassDesc className, TypeArg... typeArgs) {
            return of(null, className, typeArgs);
        }

        /**
         * {@return a class or interface signature}
         *
         * @param outerType signature of the outer type, may be {@code null}
         * @param className the name of this class or interface
         * @param typeArgs the type arguments
         * @throws IllegalArgumentException if {@code className} does not
         *         represent a class or interface
         */
        public static ClassTypeSig of(ClassTypeSig outerType, ClassDesc className, TypeArg... typeArgs) {
            requireNonNull(className);
            return of(outerType, Util.toInternalName(className), typeArgs);
        }

        /**
         * {@return a class or interface signature without an outer type}
         *
         * @param className the name of the class or interface
         * @param typeArgs the type arguments
         */
        public static ClassTypeSig of(String className, TypeArg... typeArgs) {
            return of(null, className, typeArgs);
        }

        /**
         * {@return a class type signature}
         *
         * @param outerType signature of the outer type, may be {@code null}
         * @param className the name of this class or interface
         * @param typeArgs the type arguments
         */
        public static ClassTypeSig of(ClassTypeSig outerType, String className, TypeArg... typeArgs) {
            requireNonNull(className);
            return new SignaturesImpl.ClassTypeSigImpl(Optional.ofNullable(outerType), className.replace(".", "/"), List.of(typeArgs));
        }
    }

    /**
     * Models a type argument, an argument to a type parameter.
     *
     * @see Type
     * @see WildcardType
     * @jls 4.5.1 Type Arguments of Parameterized Types
     * @jvms 4.7.9.1 Signatures
     * @sealedGraph
     * @since 24
     */
    public sealed interface TypeArg {

        /**
         * Models an unbounded wildcard type argument {@code *}, or {@code
         * ?} in Java programs.  This type argument has an implicit upper
         * bound of {@link Object}.
         *
         * @see WildcardType#getUpperBounds()
         * @jls 4.5.1 Type Arguments of Parameterized Types
         * @jvms 4.7.9.1 Signatures
         * @since 24
         */
        public sealed interface Unbounded extends TypeArg permits SignaturesImpl.UnboundedTypeArgImpl {
        }

        /**
         * Models a type argument with an explicit bound type.
         *
         * @jls 4.5.1 Type Arguments of Parameterized Types
         * @jvms 4.7.9.1 Signatures
         * @since 24
         */
        public sealed interface Bounded extends TypeArg permits SignaturesImpl.TypeArgImpl {

            /**
             * Models a type argument's wildcard indicator.
             *
             * @jls 4.5.1 Type Arguments of Parameterized Types
             * @jvms 4.7.9.1 Signatures
             * @since 24
             */
            public enum WildcardIndicator {

                /**
                 * No wildcard (empty), an exact type.  Also known as
                 * {@index invariant}.  This is the direct use of a
                 * reference type in Java programs.
                 *
                 * @see Type
                 */
                NONE,

                /**
                 * Upper-bound indicator {@code +}.  Also known as
                 * {@index covariant}.  This is the {@code ? extends}
                 * prefix in Java programs.
                 *
                 * @see WildcardType#getUpperBounds()
                 */
                EXTENDS,

                /**
                 * Lower-bound indicator {@code -}.  Also known as
                 * {@index contravariant}.  This is the {@code ? super}
                 * prefix in Java programs.
                 *
                 * @see WildcardType#getLowerBounds()
                 */
                SUPER;
            }

            /** {@return the kind of wildcard} */
            WildcardIndicator wildcardIndicator();

            /** {@return the signature of the type bound} */
            RefTypeSig boundType();
        }

        /**
         * {@return a type argument of a reference type}
         *
         * @param boundType the reference type
         * @see Bounded.WildcardIndicator#NONE
         */
        public static TypeArg.Bounded of(RefTypeSig boundType) {
            requireNonNull(boundType);
            return bounded(Bounded.WildcardIndicator.NONE, boundType);
        }

        /**
         * {@return an unbounded wildcard type argument {@code *}}
         */
        public static TypeArg.Unbounded unbounded() {
            return SignaturesImpl.UnboundedTypeArgImpl.INSTANCE;
        }

        /**
         * {@return an upper-bounded wildcard type argument}
         *
         * @param boundType the upper bound
         * @see Bounded.WildcardIndicator#EXTENDS
         */
        public static TypeArg.Bounded extendsOf(RefTypeSig boundType) {
            requireNonNull(boundType);
            return bounded(Bounded.WildcardIndicator.EXTENDS, boundType);
        }

        /**
         * {@return a lower-bounded wildcard type argument}
         *
         * @param boundType the lower bound
         * @see Bounded.WildcardIndicator#SUPER
         */
        public static TypeArg.Bounded superOf(RefTypeSig boundType) {
            requireNonNull(boundType);
            return bounded(Bounded.WildcardIndicator.SUPER, boundType);
        }

        /**
         * {@return a bounded type argument}
         *
         * @param wildcard the wildcard indicator
         * @param boundType the bound type
         */
        public static TypeArg.Bounded bounded(Bounded.WildcardIndicator wildcard, RefTypeSig boundType) {
            requireNonNull(wildcard);
            requireNonNull(boundType);
            return new SignaturesImpl.TypeArgImpl(wildcard, boundType);
        }
    }

    /**
     * Models the signature of a type variable.  A type variable is introduced
     * by a {@linkplain TypeParam type parameter} declaration.
     *
     * @see TypeVariable
     * @see TypeParam
     * @jls 4.4 Type Variables
     * @jvms 4.7.9.1 Signatures
     * @since 24
     */
    public sealed interface TypeVarSig
            extends RefTypeSig, ThrowableSig
            permits SignaturesImpl.TypeVarSigImpl {

        /** {@return the name of the type variable} */
        String identifier();

        /**
         * {@return a signature for a type variable}
         *
         * @param identifier the name of the type variable
         */
        public static TypeVarSig of(String identifier) {
            return new SignaturesImpl.TypeVarSigImpl(requireNonNull(identifier));
        }
    }

    /**
     * Models the signature of an array type.
     *
     * @see Type
     * @see GenericArrayType
     * @jls 10.1 Array Types
     * @jvms 4.7.9.1 Signatures
     * @since 24
     */
    public sealed interface ArrayTypeSig
            extends RefTypeSig
            permits SignaturesImpl.ArrayTypeSigImpl {

        /** {@return the signature of the component type} */
        Signature componentSignature();

        /**
         * {@return an array type with the given component type}
         * @param componentSignature the component type
         */
        public static ArrayTypeSig of(Signature componentSignature) {
            return of(1, requireNonNull(componentSignature));
        }

        /**
         * {@return a signature for an array type}
         * @param dims the dimension of the array
         * @param componentSignature the component type
         * @throws IllegalArgumentException if the resulting array type exceeds
         *         255 dimensions
         */
        public static ArrayTypeSig of(int dims, Signature componentSignature) {
            requireNonNull(componentSignature);
            if (dims < 1 || dims > 255)
                throw new IllegalArgumentException("illegal array depth value");
            if (componentSignature instanceof SignaturesImpl.ArrayTypeSigImpl arr)
                return new SignaturesImpl.ArrayTypeSigImpl(dims + arr.arrayDepth(), arr.elemType());
            return new SignaturesImpl.ArrayTypeSigImpl(dims, componentSignature);
        }
    }

    /**
     * Models a signature for a type parameter of a generic class, interface,
     * method, or constructor, which introduces a {@linkplain TypeVarSig type
     * variable}.
     *
     * @see GenericDeclaration#getTypeParameters()
     * @see TypeVariable
     * @see TypeVarSig
     * @jls 4.4 Type Variables
     * @jvms 4.7.9.1 Signatures
     * @since 24
     */
    public sealed interface TypeParam
            permits SignaturesImpl.TypeParamImpl {

        /** {@return the name of the type parameter} */
        String identifier();

        /**
         * {@return the class bound of the type parameter}  This may be empty
         * if this type parameter only has interface bounds.
         */
        Optional<RefTypeSig> classBound();

        /**
         * {@return the interface bounds of the type parameter}  This may be
         * empty.
         */
        List<RefTypeSig> interfaceBounds();

        /**
         * {@return a signature for a type parameter}
         *
         * @param identifier the name of the type parameter
         * @param classBound the class bound of the type parameter, may be {@code null}
         * @param interfaceBounds the interface bounds of the type parameter
         */
        public static TypeParam of(String identifier, RefTypeSig classBound, RefTypeSig... interfaceBounds) {
            return new SignaturesImpl.TypeParamImpl(
                    requireNonNull(identifier),
                    Optional.ofNullable(classBound),
                    List.of(interfaceBounds));
        }

        /**
         * {@return a signature for a type parameter}
         *
         * @param identifier the name of the type parameter
         * @param classBound the optional class bound of the type parameter
         * @param interfaceBounds the interface bounds of the type parameter
         */
        public static TypeParam of(String identifier, Optional<RefTypeSig> classBound, RefTypeSig... interfaceBounds) {
            return new SignaturesImpl.TypeParamImpl(
                    requireNonNull(identifier),
                    requireNonNull(classBound),
                    List.of(interfaceBounds));
        }
    }

    /**
     * Marker interface for a signature for a throwable type.
     *
     * @jls 8.4.6 Method Throws
     * @jvms 4.7.9.1 Signatures
     * @sealedGraph
     * @since 24
     */
    public sealed interface ThrowableSig extends Signature {
    }
}
