/*
 * 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.constantpool.*;
import java.lang.constant.ClassDesc;
import java.lang.constant.Constable;
import java.util.ArrayList;
import java.util.List;

import jdk.internal.classfile.impl.AnnotationImpl;
import jdk.internal.classfile.impl.TemporaryConstantPool;
import jdk.internal.classfile.impl.Util;

import static java.util.Objects.requireNonNull;

/**
 * Models an {@code element_value} structure, or a value of an element-value
 * pair of an annotation, as defined in JVMS {@jvms 4.7.16.1}.
 * <p>
 * Two {@code AnnotationValue} objects should be compared using the {@link
 * Object#equals(Object) equals} method.
 *
 * @see Annotation
 * @see AnnotationElement
 * @see java.lang.reflect.AnnotatedElement Annotations in core reflection
 *
 * @sealedGraph
 * @since 24
 */
public sealed interface AnnotationValue {

    /**
     * Models an annotation value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_ANNOTATION}.
     *
     * @since 24
     */
    sealed interface OfAnnotation extends AnnotationValue
            permits AnnotationImpl.OfAnnotationImpl {
        /** {@return the annotation value} */
        Annotation annotation();
    }

    /**
     * Models an array value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_ARRAY}.
     *
     * @since 24
     */
    sealed interface OfArray extends AnnotationValue
            permits AnnotationImpl.OfArrayImpl {
        /**
         * {@return the array elements of the array value}
         *
         * @apiNote
         * All array elements derived from Java source code have the same type,
         * which must not be an array type. (JLS {@jls 9.6.1}) If such elements are
         * annotations, they have the same annotation interface; if such elements
         * are enum, they belong to the same enum class.
         */
        List<AnnotationValue> values();
    }

    /**
     * Models a constant value of an element-value pair.
     *
     * @sealedGraph
     * @since 24
     */
    sealed interface OfConstant extends AnnotationValue {
        /**
         * {@return the constant pool entry backing this constant element}
         *
         * @apiNote
         * Different types of constant values may share the same type of entry
         * because they have the same {@linkplain TypeKind##computational-type
         * computational type}.
         * For example, {@link OfInt} and {@link OfChar} are both
         * backed by {@link IntegerEntry}. Use {@link #resolvedValue
         * resolvedValue()} for a value of accurate type.
         */
        AnnotationConstantValueEntry constant();

        /**
         * {@return the resolved live constant value, as an object} The type of
         * the returned value may be a wrapper class or {@link String}.
         *
         * @apiNote
         * The returned object, despite being {@link Constable}, may not
         * {@linkplain Constable#describeConstable() describe} the right constant
         * for encoding the annotation value in a class file. For example,
         * {@link Character} returned by {@link OfChar} describes itself as a
         * {@link DynamicConstantPoolEntry}, but it is actually backed by
         * {@link IntegerEntry} in annotation format.
         * Use {@link #constant constant()} for a correct constant pool representation.
         */
        Constable resolvedValue();
    }

    /**
     * Models a string value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_STRING}.
     *
     * @since 24
     */
    sealed interface OfString extends OfConstant
            permits AnnotationImpl.OfStringImpl {
        /** {@return the backing UTF8 entry} */
        @Override
        Utf8Entry constant();

        /** {@return the constant string value} */
        String stringValue();

        /**
         * {@return the resolved string value}
         *
         * @implSpec
         * This method returns the same as {@link #stringValue()}.
         */
        @Override
        default String resolvedValue() {
            return stringValue();
        }
    }

    /**
     * Models a double value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_DOUBLE}.
     *
     * @since 24
     */
    sealed interface OfDouble extends OfConstant
            permits AnnotationImpl.OfDoubleImpl {
        /** {@return the backing double entry} */
        @Override
        DoubleEntry constant();

        /** {@return the constant double value} */
        double doubleValue();

        /**
         * {@return the resolved double value}
         *
         * @implSpec
         * This method returns the same as {@link #doubleValue()}.
         */
        @Override
        default Double resolvedValue() {
            return doubleValue();
        }
    }

    /**
     * Models a float value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_FLOAT}.
     *
     * @since 24
     */
    sealed interface OfFloat extends OfConstant
            permits AnnotationImpl.OfFloatImpl {
        /** {@return the backing float entry} */
        @Override
        FloatEntry constant();

        /** {@return the constant float value} */
        float floatValue();

        /**
         * {@return the resolved float value}
         *
         * @implSpec
         * This method returns the same as {@link #floatValue()}.
         */
        @Override
        default Float resolvedValue() {
            return floatValue();
        }
    }

    /**
     * Models a long value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_LONG}.
     *
     * @since 24
     */
    sealed interface OfLong extends OfConstant
            permits AnnotationImpl.OfLongImpl {
        /** {@return the backing long entry} */
        @Override
        LongEntry constant();

        /** {@return the constant long value} */
        long longValue();

        /**
         * {@return the resolved long value}
         *
         * @implSpec
         * This method returns the same as {@link #longValue()}.
         */
        @Override
        default Long resolvedValue() {
            return longValue();
        }
    }

    /**
     * Models an int value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_INT}.
     *
     * @since 24
     */
    sealed interface OfInt extends OfConstant
            permits AnnotationImpl.OfIntImpl {
        /** {@return the backing integer entry} */
        @Override
        IntegerEntry constant();

        /** {@return the constant int value} */
        int intValue();

        /**
         * {@return the resolved int value}
         *
         * @implSpec
         * This method returns the same as {@link #intValue()}.
         */
        @Override
        default Integer resolvedValue() {
            return intValue();
        }
    }

    /**
     * Models a short value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_SHORT}.
     *
     * @since 24
     */
    sealed interface OfShort extends OfConstant
            permits AnnotationImpl.OfShortImpl {
        /** {@return the backing integer entry} */
        @Override
        IntegerEntry constant();

        /**
         * {@return the constant short value}
         * @jvms 2.11.1 Types and the Java Virtual Machine
         */
        short shortValue();

        /**
         * {@return the resolved short value}
         *
         * @implSpec
         * This method returns the same as {@link #shortValue()}.
         */
        @Override
        default Short resolvedValue() {
            return shortValue();
        }
    }

    /**
     * Models a char value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_CHAR}.
     *
     * @since 24
     */
    sealed interface OfChar extends OfConstant
            permits AnnotationImpl.OfCharImpl {
        /** {@return the backing integer entry} */
        @Override
        IntegerEntry constant();

        /**
         * {@return the constant char value}
         * @jvms 2.11.1 Types and the Java Virtual Machine
         */
        char charValue();

        /**
         * {@return the resolved char value}
         *
         * @implSpec
         * This method returns the same as {@link #charValue()}.
         */
        @Override
        default Character resolvedValue() {
            return charValue();
        }
    }

    /**
     * Models a byte value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_BYTE}.
     *
     * @since 24
     */
    sealed interface OfByte extends OfConstant
            permits AnnotationImpl.OfByteImpl {
        /** {@return the backing integer entry} */
        @Override
        IntegerEntry constant();

        /**
         * {@return the constant byte value}
         * @jvms 2.11.1 Types and the Java Virtual Machine
         */
        byte byteValue();

        /**
         * {@return the resolved byte value}
         *
         * @implSpec
         * This method returns the same as {@link #byteValue()}.
         */
        @Override
        default Byte resolvedValue() {
            return byteValue();
        }
    }

    /**
     * Models a boolean value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_BOOLEAN}.
     *
     * @since 24
     */
    sealed interface OfBoolean extends OfConstant
            permits AnnotationImpl.OfBooleanImpl {
        /** {@return the backing integer entry} */
        @Override
        IntegerEntry constant();

        /**
         * {@return the constant boolean value}
         * @jvms 2.3.4 The <i>boolean</i> Type
         */
        boolean booleanValue();

        /**
         * {@return the resolved boolean value}
         *
         * @implSpec
         * This method returns the same as {@link #booleanValue()}.
         */
        @Override
        default Boolean resolvedValue() {
            return booleanValue();
        }
    }

    /**
     * Models a class value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_CLASS}.
     *
     * @since 24
     */
    sealed interface OfClass extends AnnotationValue
            permits AnnotationImpl.OfClassImpl {
        /** {@return the class descriptor string} */
        Utf8Entry className();

        /** {@return the class descriptor} */
        default ClassDesc classSymbol() {
            return Util.fieldTypeSymbol(className());
        }
    }

    /**
     * Models an enum value of an element-value pair.
     * The {@linkplain #tag tag} of this value is {@value %c TAG_ENUM}.
     *
     * @since 24
     */
    sealed interface OfEnum extends AnnotationValue
            permits AnnotationImpl.OfEnumImpl {
        /** {@return the enum class descriptor string} */
        Utf8Entry className();

        /** {@return the enum class descriptor} */
        default ClassDesc classSymbol() {
            return Util.fieldTypeSymbol(className());
        }

        /** {@return the enum constant name} */
        Utf8Entry constantName();
    }

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfByte}. */
    int TAG_BYTE = 'B';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfChar}. */
    int TAG_CHAR = 'C';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfDouble}. */
    int TAG_DOUBLE = 'D';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfFloat}. */
    int TAG_FLOAT = 'F';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfInt}. */
    int TAG_INT = 'I';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfLong}. */
    int TAG_LONG = 'J';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfShort}. */
    int TAG_SHORT = 'S';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfBoolean}. */
    int TAG_BOOLEAN = 'Z';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfString}. */
    int TAG_STRING = 's';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfEnum}. */
    int TAG_ENUM = 'e';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfClass}. */
    int TAG_CLASS = 'c';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfAnnotation}. */
    int TAG_ANNOTATION = '@';

    /** The {@link #tag() tag} indicating the value of an element-value pair is {@link OfArray}. */
    int TAG_ARRAY = '[';

    /**
     * {@return the tag character for this value as per JVMS {@jvms 4.7.16.1}}
     * The tag characters have a one-to-one mapping to the types of annotation element values.
     *
     * @apiNote
     * {@code TAG_}-prefixed constants in this class, such as {@link #TAG_INT},
     * describe the possible return values of this method.  The return type is
     * {@code int} for consistency with union indicator items in other union
     * structures in the {@code class} file format.
     */
    int tag();

    /**
     * {@return an enum value for an element-value pair}
     * @param className the descriptor string of the enum class
     * @param constantName the name of the enum constant
     */
    static OfEnum ofEnum(Utf8Entry className,
                         Utf8Entry constantName) {
        requireNonNull(className);
        requireNonNull(constantName);
        return new AnnotationImpl.OfEnumImpl(className, constantName);
    }

    /**
     * {@return an enum value for an element-value pair}
     * @param className the descriptor of the enum class
     * @param constantName the name of the enum constant
     */
    static OfEnum ofEnum(ClassDesc className, String constantName) {
        return ofEnum(TemporaryConstantPool.INSTANCE.utf8Entry(className),
                      TemporaryConstantPool.INSTANCE.utf8Entry(constantName));
    }

    /**
     * {@return a class value for an element-value pair}
     * @param className the descriptor string of the class
     */
    static OfClass ofClass(Utf8Entry className) {
        requireNonNull(className);
        return new AnnotationImpl.OfClassImpl(className);
    }

    /**
     * {@return a class value for an element-value pair}
     * @param className the descriptor of the class
     */
    static OfClass ofClass(ClassDesc className) {
        return ofClass(TemporaryConstantPool.INSTANCE.utf8Entry(className));
    }

    /**
     * {@return a string value for an element-value pair}
     * @param value the string
     */
    static OfString ofString(Utf8Entry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfStringImpl(value);
    }

    /**
     * {@return a string value for an element-value pair}
     * @param value the string
     */
    static OfString ofString(String value) {
        return ofString(TemporaryConstantPool.INSTANCE.utf8Entry(value));
    }

    /**
     * {@return a double value for an element-value pair}
     * @param value the double value
     */
    static OfDouble ofDouble(DoubleEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfDoubleImpl(value);
    }

    /**
     * {@return a double value for an element-value pair}
     * @param value the double value
     */
    static OfDouble ofDouble(double value) {
        return ofDouble(TemporaryConstantPool.INSTANCE.doubleEntry(value));
    }

    /**
     * {@return a float value for an element-value pair}
     * @param value the float value
     */
    static OfFloat ofFloat(FloatEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfFloatImpl(value);
    }

    /**
     * {@return a float value for an element-value pair}
     * @param value the float value
     */
    static OfFloat ofFloat(float value) {
        return ofFloat(TemporaryConstantPool.INSTANCE.floatEntry(value));
    }

    /**
     * {@return a long value for an element-value pair}
     * @param value the long value
     */
    static OfLong ofLong(LongEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfLongImpl(value);
    }

    /**
     * {@return a long value for an element-value pair}
     * @param value the long value
     */
    static OfLong ofLong(long value) {
        return ofLong(TemporaryConstantPool.INSTANCE.longEntry(value));
    }

    /**
     * {@return an int value for an element-value pair}
     * @param value the int value
     */
    static OfInt ofInt(IntegerEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfIntImpl(value);
    }

    /**
     * {@return an int value for an element-value pair}
     * @param value the int value
     */
    static OfInt ofInt(int value) {
        return ofInt(TemporaryConstantPool.INSTANCE.intEntry(value));
    }

    /**
     * {@return a short value for an element-value pair}
     * @param value the short value
     */
    static OfShort ofShort(IntegerEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfShortImpl(value);
    }

    /**
     * {@return a short value for an element-value pair}
     * @param value the short value
     */
    static OfShort ofShort(short value) {
        return ofShort(TemporaryConstantPool.INSTANCE.intEntry(value));
    }

    /**
     * {@return a char value for an element-value pair}
     * @param value the char value
     */
    static OfChar ofChar(IntegerEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfCharImpl(value);
    }

    /**
     * {@return a char value for an element-value pair}
     * @param value the char value
     */
    static OfChar ofChar(char value) {
        return ofChar(TemporaryConstantPool.INSTANCE.intEntry(value));
    }

    /**
     * {@return a byte value for an element-value pair}
     * @param value the byte value
     */
    static OfByte ofByte(IntegerEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfByteImpl(value);
    }

    /**
     * {@return a byte value for an element-value pair}
     * @param value the byte value
     */
    static OfByte ofByte(byte value) {
        return ofByte(TemporaryConstantPool.INSTANCE.intEntry(value));
    }

    /**
     * {@return a boolean value for an element-value pair}
     * @param value the boolean value
     */
    static OfBoolean ofBoolean(IntegerEntry value) {
        requireNonNull(value);
        return new AnnotationImpl.OfBooleanImpl(value);
    }

    /**
     * {@return a boolean value for an element-value pair}
     * @param value the boolean value
     */
    static OfBoolean ofBoolean(boolean value) {
        int i = value ? 1 : 0;
        return ofBoolean(TemporaryConstantPool.INSTANCE.intEntry(i));
    }

    /**
     * {@return an annotation value for an element-value pair}
     * @param value the annotation
     */
    static OfAnnotation ofAnnotation(Annotation value) {
        requireNonNull(value);
        return new AnnotationImpl.OfAnnotationImpl(value);
    }

    /**
     * {@return an array value for an element-value pair}
     *
     * @apiNote
     * See {@link AnnotationValue.OfArray#values() values()} for conventions
     * on array values derived from Java source code.
     *
     * @param values the array elements
     */
    static OfArray ofArray(List<AnnotationValue> values) {
        return new AnnotationImpl.OfArrayImpl(values);
    }

    /**
     * {@return an array value for an element-value pair}
     *
     * @apiNote
     * See {@link AnnotationValue.OfArray#values() values()} for conventions
     * on array values derived from Java source code.
     *
     * @param values the array elements
     */
    static OfArray ofArray(AnnotationValue... values) {
        return ofArray(List.of(values));
    }

    /**
     * {@return an annotation element}  The {@code value} parameter must be
     * a primitive, a wrapper of primitive, a String, a ClassDesc, an enum
     * constant, or an array of one of these.
     *
     * @param value the annotation value
     * @throws IllegalArgumentException when the {@code value} parameter is not
     *         a primitive, a wrapper of primitive, a String, a ClassDesc,
     *         an enum constant, or an array of one of these.
     */
    static AnnotationValue of(Object value) {
        if (value instanceof String s) {
            return ofString(s);
        } else if (value instanceof Byte b) {
            return ofByte(b);
        } else if (value instanceof Boolean b) {
            return ofBoolean(b);
        } else if (value instanceof Short s) {
            return ofShort(s);
        } else if (value instanceof Character c) {
            return ofChar(c);
        } else if (value instanceof Integer i) {
            return ofInt(i);
        } else if (value instanceof Long l) {
            return ofLong(l);
        } else if (value instanceof Float f) {
            return ofFloat(f);
        } else if (value instanceof Double d) {
            return ofDouble(d);
        } else if (value instanceof ClassDesc clsDesc) {
            return ofClass(clsDesc);
        } else if (value instanceof byte[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofByte(el));
            }
            return ofArray(els);
        } else if (value instanceof boolean[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofBoolean(el));
            }
            return ofArray(els);
        } else if (value instanceof short[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofShort(el));
            }
            return ofArray(els);
        } else if (value instanceof char[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofChar(el));
            }
            return ofArray(els);
        } else if (value instanceof int[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofInt(el));
            }
            return ofArray(els);
        } else if (value instanceof long[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofLong(el));
            }
            return ofArray(els);
        } else if (value instanceof float[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofFloat(el));
            }
            return ofArray(els);
        } else if (value instanceof double[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(ofDouble(el));
            }
            return ofArray(els);
        } else if (value instanceof Object[] arr) {
            var els = new ArrayList<AnnotationValue>(arr.length);
            for (var el : arr) {
                els.add(of(el));
            }
            return ofArray(els);
        } else if (value instanceof Enum<?> e) {
            return ofEnum(ClassDesc.ofDescriptor(e.getDeclaringClass().descriptorString()), e.name());
        }
        throw new IllegalArgumentException("Illegal annotation constant value type " + requireNonNull(value).getClass());
    }
}
