package net.bytebuddy.description.type;

import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.annotation.AnnotatedCodeElement;
import net.bytebuddy.description.annotation.AnnotationList;
import org.objectweb.asm.Opcodes;

/**
 * A package description represents a Java package.
 */
public interface PackageDescription extends NamedElement.WithRuntimeName, AnnotatedCodeElement {

    /**
     * The name of a Java class representing a package description.
     */
    String PACKAGE_CLASS_NAME = "package-info";

    /**
     * The modifiers of a Java class representing a package description.
     */
    int PACKAGE_MODIFIERS = Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_SYNTHETIC;

    /**
     * Represents any undefined property of a type description that is instead represented as {@code null} in order
     * to resemble the Java reflection API which returns {@code null} and is intuitive to many Java developers.
     */
    PackageDescription UNDEFINED = null;

    /**
     * Checks if this package contains the provided type.
     *
     * @param typeDescription The type to examine.
     * @return {@code true} if the given type contains the provided type.
     */
    boolean contains(TypeDescription typeDescription);

    /**
     * An abstract base implementation of a package description.
     */
    abstract class AbstractBase implements PackageDescription {

        @Override
        public String getInternalName() {
            return getName().replace('.', '/');
        }

        @Override
        public String getSourceCodeName() {
            return getName();
        }

        @Override
        public boolean contains(TypeDescription typeDescription) {
            return this.equals(typeDescription.getPackage());
        }

        @Override
        public int hashCode() {
            return getName().hashCode();
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof PackageDescription
                    && getName().equals(((PackageDescription) other).getName());
        }

        @Override
        public String toString() {
            return "package " + getName();
        }
    }

    /**
     * A simple implementation of a package without annotations.
     */
    class Simple extends AbstractBase {

        /**
         * The name of the package.
         */
        private final String name;

        /**
         * Creates a new simple package.
         *
         * @param name The name of the package.
         */
        public Simple(String name) {
            this.name = name;
        }

        @Override
        public AnnotationList getDeclaredAnnotations() {
            return new AnnotationList.Empty();
        }

        @Override
        public String getName() {
            return name;
        }
    }

    /**
     * Represents a loaded {@link java.lang.Package} wrapped as a
     * {@link PackageDescription}.
     */
    class ForLoadedPackage extends AbstractBase {

        /**
         * The represented package.
         */
        private final Package aPackage;

        /**
         * Creates a new loaded package representation.
         *
         * @param aPackage The represented package.
         */
        public ForLoadedPackage(Package aPackage) {
            this.aPackage = aPackage;
        }

        @Override
        public AnnotationList getDeclaredAnnotations() {
            return new AnnotationList.ForLoadedAnnotation(aPackage.getDeclaredAnnotations());
        }

        @Override
        public String getName() {
            return aPackage.getName();
        }
    }
}
