/*
 * Copyright (c) 2022, 2024, 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.constant.ClassDesc;
import java.util.List;
import java.util.Optional;

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

import static java.util.Objects.requireNonNull;

/**
 * Models generic Java type signatures, as defined in JVMS {@jvms 4.7.9.1}.
 *
 * @sealedGraph
 * @since 22
 */
@PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
public sealed interface Signature {

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

    /**
     * Parses generic Java type signature from raw string
     * @param javaTypeSignature raw Java type signature string
     * @return Java type signature
     */
    public static Signature parseFrom(String javaTypeSignature) {
        return new SignaturesImpl(javaTypeSignature).parseSignature();
    }

    /**
     * {@return a Java type signature}
     * @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 or void
     *
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    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
         */
        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
         */
        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.
     *
     * @sealedGraph
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface RefTypeSig
            extends Signature
            permits ArrayTypeSig, ClassTypeSig, TypeVarSig {
    }

    /**
     * Models the signature of a possibly-parameterized class or interface type.
     *
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface ClassTypeSig
            extends RefTypeSig, ThrowableSig
            permits SignaturesImpl.ClassTypeSigImpl {

        /** {@return the signature of the outer type, if any} */
        Optional<ClassTypeSig> outerType();

        /** {@return the class name} */
        String className();

        /** {@return the class name, 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 the class} */
        List<TypeArg> typeArgs();

        /**
         * {@return a class type signature}
         * @param className the name of the class
         * @param typeArgs signatures of the type arguments
         */
        public static ClassTypeSig of(ClassDesc className, TypeArg... typeArgs) {
            return of(null, className, typeArgs);
        }

        /**
         * {@return a class type signature for an inner class}
         * @param outerType signature of the outer type
         * @param className the name of the class
         * @param typeArgs signatures of the type arguments
         */
        public static ClassTypeSig of(ClassTypeSig outerType, ClassDesc className, TypeArg... typeArgs) {
            requireNonNull(className);
            return of(outerType, Util.toInternalName(className), typeArgs);
        }

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

        /**
         * {@return a class type signature for an inner class}
         * @param outerType signature of the outer type
         * @param className the name of the class
         * @param typeArgs signatures of 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 the type argument.
     *
     * @sealedGraph
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface TypeArg {

        /**
         * Models an unbounded type argument {@code *}.
         * @since 23
         */
        @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
        public sealed interface Unbounded extends TypeArg permits SignaturesImpl.UnboundedTypeArgImpl {
        }

        /**
         * Models a type argument with an explicit bound type.
         * @since 23
         */
        @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
        public sealed interface Bounded extends TypeArg permits SignaturesImpl.TypeArgImpl {

            /**
             * Models a type argument's wildcard indicator.
             * @since 23
             */
            @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
            public enum WildcardIndicator {

                /**
                 * No wildcard (empty), an exact type. Also known as
                 * {@index invariant}.
                 */
                NONE,

                /**
                 * Upper-bound indicator {@code +}. Also known as
                 * {@index covariant}.
                 */
                EXTENDS,

                /**
                 * Lower-bound indicator {@code -}. Also known as
                 * {@index contravariant}.
                 */
                SUPER;
            }

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

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

        /**
         * {@return a bounded type arg}
         * @param boundType the bound
         * @since 23
         */
        public static TypeArg.Bounded of(RefTypeSig boundType) {
            requireNonNull(boundType);
            return bounded(Bounded.WildcardIndicator.NONE, boundType);
        }

        /**
         * {@return an unbounded type arg}
         * @since 23
         */
        public static TypeArg.Unbounded unbounded() {
            return SignaturesImpl.UnboundedTypeArgImpl.INSTANCE;
        }

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

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

        /**
         * {@return a bounded type arg}
         * @param wildcard the wild card
         * @param boundType optional bound type
         * @since 23
         */
        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.
     *
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    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.
     *
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface ArrayTypeSig
            extends RefTypeSig
            permits SignaturesImpl.ArrayTypeSigImpl {

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

        /**
         * {@return a signature for an array 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
         */
        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 or method.
     *
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface TypeParam
            permits SignaturesImpl.TypeParamImpl {

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

        /** {@return the class bound of the type parameter} */
        Optional<RefTypeSig> classBound();

        /** {@return the interface bounds of the type parameter} */
        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
         * @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 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));
        }
    }

    /**
     * Models a signature for a throwable type.
     *
     * @sealedGraph
     * @since 22
     */
    @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API)
    public sealed interface ThrowableSig extends Signature {
    }
}
