/*
 * Copyright 2014 - 2020 Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.dynamic.scaffold.inline;

import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.modifier.ModifierContributor;
import net.bytebuddy.description.type.RecordComponentDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeVariableToken;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.Transformer;
import net.bytebuddy.dynamic.TypeResolutionStrategy;
import net.bytebuddy.dynamic.scaffold.ClassWriterStrategy;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.dynamic.scaffold.TypeWriter;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.attribute.AnnotationRetention;
import net.bytebuddy.implementation.attribute.AnnotationValueFilter;
import net.bytebuddy.implementation.attribute.TypeAttributeAppender;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.LatentMatcher;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static net.bytebuddy.matcher.ElementMatchers.isVirtual;
import static net.bytebuddy.matcher.ElementMatchers.not;

/**
 * A type builder that decorates a type by allowing for the application of attribute changes and ASM visitor wrappers.
 *
 * @param <T> A loaded type that the built type is guaranteed to be a subclass of.
 */
@HashCodeAndEqualsPlugin.Enhance
public class DecoratingDynamicTypeBuilder<T> extends DynamicType.Builder.AbstractBase<T> {

    /**
     * The instrumented type to decorate.
     */
    private final TypeDescription instrumentedType;

    /**
     * The type attribute appender to apply onto the instrumented type.
     */
    private final TypeAttributeAppender typeAttributeAppender;

    /**
     * The ASM visitor wrapper to apply onto the class writer.
     */
    private final AsmVisitorWrapper asmVisitorWrapper;

    /**
     * The class file version to define auxiliary types in.
     */
    private final ClassFileVersion classFileVersion;

    /**
     * The naming strategy for auxiliary types to apply.
     */
    private final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;

    /**
     * The annotation value filter factory to apply.
     */
    private final AnnotationValueFilter.Factory annotationValueFilterFactory;

    /**
     * The annotation retention to apply.
     */
    private final AnnotationRetention annotationRetention;

    /**
     * The implementation context factory to apply.
     */
    private final Implementation.Context.Factory implementationContextFactory;

    /**
     * The method graph compiler to use.
     */
    private final MethodGraph.Compiler methodGraphCompiler;

    /**
     * Determines if a type should be explicitly validated.
     */
    private final TypeValidation typeValidation;

    /**
     * The class writer strategy to use.
     */
    private final ClassWriterStrategy classWriterStrategy;

    /**
     * A matcher for identifying methods that should be excluded from instrumentation.
     */
    private final LatentMatcher<? super MethodDescription> ignoredMethods;

    /**
     * A list of explicitly required auxiliary types.
     */
    private final List<DynamicType> auxiliaryTypes;

    /**
     * The class file locator for locating the original type's class file.
     */
    private final ClassFileLocator classFileLocator;

    /**
     * Creates a new decorating dynamic type builder.
     *
     * @param instrumentedType             The instrumented type to decorate.
     * @param classFileVersion             The class file version to define auxiliary types in.
     * @param auxiliaryTypeNamingStrategy  The naming strategy for auxiliary types to apply.
     * @param annotationValueFilterFactory The annotation value filter factory to apply.
     * @param annotationRetention          The annotation retention to apply.
     * @param implementationContextFactory The implementation context factory to apply.
     * @param methodGraphCompiler          The method graph compiler to use.
     * @param typeValidation               Determines if a type should be explicitly validated.
     * @param classWriterStrategy          The class writer strategy to use.
     * @param ignoredMethods               A matcher for identifying methods that should be excluded from instrumentation.
     * @param classFileLocator             The class file locator for locating the original type's class file.
     */
    public DecoratingDynamicTypeBuilder(TypeDescription instrumentedType,
                                        ClassFileVersion classFileVersion,
                                        AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
                                        AnnotationValueFilter.Factory annotationValueFilterFactory,
                                        AnnotationRetention annotationRetention,
                                        Implementation.Context.Factory implementationContextFactory,
                                        MethodGraph.Compiler methodGraphCompiler,
                                        TypeValidation typeValidation,
                                        ClassWriterStrategy classWriterStrategy,
                                        LatentMatcher<? super MethodDescription> ignoredMethods,
                                        ClassFileLocator classFileLocator) {
        this(instrumentedType,
                annotationRetention.isEnabled()
                        ? new TypeAttributeAppender.ForInstrumentedType.Differentiating(instrumentedType)
                        : TypeAttributeAppender.ForInstrumentedType.INSTANCE,
                AsmVisitorWrapper.NoOp.INSTANCE,
                classFileVersion,
                auxiliaryTypeNamingStrategy,
                annotationValueFilterFactory,
                annotationRetention,
                implementationContextFactory,
                methodGraphCompiler,
                typeValidation,
                classWriterStrategy,
                ignoredMethods,
                Collections.<DynamicType>emptyList(),
                classFileLocator);
    }

    /**
     * Creates a new decorating dynamic type builder.
     *
     * @param instrumentedType             The instrumented type to decorate.
     * @param typeAttributeAppender        The type attribute appender to apply onto the instrumented type.
     * @param asmVisitorWrapper            The ASM visitor wrapper to apply onto the class writer.
     * @param classFileVersion             The class file version to define auxiliary types in.
     * @param auxiliaryTypeNamingStrategy  The naming strategy for auxiliary types to apply.
     * @param annotationValueFilterFactory The annotation value filter factory to apply.
     * @param annotationRetention          The annotation retention to apply.
     * @param implementationContextFactory The implementation context factory to apply.
     * @param methodGraphCompiler          The method graph compiler to use.
     * @param typeValidation               Determines if a type should be explicitly validated.
     * @param classWriterStrategy          The class writer strategy to use.
     * @param ignoredMethods               A matcher for identifying methods that should be excluded from instrumentation.
     * @param auxiliaryTypes               A list of explicitly required auxiliary types.
     * @param classFileLocator             The class file locator for locating the original type's class file.
     */
    protected DecoratingDynamicTypeBuilder(TypeDescription instrumentedType,
                                           TypeAttributeAppender typeAttributeAppender,
                                           AsmVisitorWrapper asmVisitorWrapper,
                                           ClassFileVersion classFileVersion,
                                           AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
                                           AnnotationValueFilter.Factory annotationValueFilterFactory,
                                           AnnotationRetention annotationRetention,
                                           Implementation.Context.Factory implementationContextFactory,
                                           MethodGraph.Compiler methodGraphCompiler,
                                           TypeValidation typeValidation,
                                           ClassWriterStrategy classWriterStrategy,
                                           LatentMatcher<? super MethodDescription> ignoredMethods,
                                           List<DynamicType> auxiliaryTypes,
                                           ClassFileLocator classFileLocator) {
        this.instrumentedType = instrumentedType;
        this.typeAttributeAppender = typeAttributeAppender;
        this.asmVisitorWrapper = asmVisitorWrapper;
        this.classFileVersion = classFileVersion;
        this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
        this.annotationValueFilterFactory = annotationValueFilterFactory;
        this.annotationRetention = annotationRetention;
        this.implementationContextFactory = implementationContextFactory;
        this.methodGraphCompiler = methodGraphCompiler;
        this.typeValidation = typeValidation;
        this.classWriterStrategy = classWriterStrategy;
        this.ignoredMethods = ignoredMethods;
        this.auxiliaryTypes = auxiliaryTypes;
        this.classFileLocator = classFileLocator;
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> visit(AsmVisitorWrapper asmVisitorWrapper) {
        return new DecoratingDynamicTypeBuilder<T>(instrumentedType,
                typeAttributeAppender,
                new AsmVisitorWrapper.Compound(this.asmVisitorWrapper, asmVisitorWrapper),
                classFileVersion,
                auxiliaryTypeNamingStrategy,
                annotationValueFilterFactory,
                annotationRetention,
                implementationContextFactory,
                methodGraphCompiler,
                typeValidation,
                classWriterStrategy,
                ignoredMethods,
                auxiliaryTypes,
                classFileLocator);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> suffix(String suffix) {
        throw new UnsupportedOperationException("Cannot change name of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> name(String name) {
        throw new UnsupportedOperationException("Cannot change name of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> modifiers(int modifiers) {
        throw new UnsupportedOperationException("Cannot change modifiers of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> merge(Collection<? extends ModifierContributor.ForType> modifierContributors) {
        throw new UnsupportedOperationException("Cannot change modifiers of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> topLevelType() {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public InnerTypeDefinition.ForType<T> innerTypeOf(TypeDescription type) {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public InnerTypeDefinition<T> innerTypeOf(MethodDescription.InDefinedShape methodDescription) {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> declaredTypes(Collection<? extends TypeDescription> types) {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> nestHost(TypeDescription type) {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> nestMembers(Collection<? extends TypeDescription> types) {
        throw new UnsupportedOperationException("Cannot change type declaration of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> attribute(TypeAttributeAppender typeAttributeAppender) {
        return new DecoratingDynamicTypeBuilder<T>(instrumentedType,
                new TypeAttributeAppender.Compound(this.typeAttributeAppender, typeAttributeAppender),
                asmVisitorWrapper,
                classFileVersion,
                auxiliaryTypeNamingStrategy,
                annotationValueFilterFactory,
                annotationRetention,
                implementationContextFactory,
                methodGraphCompiler,
                typeValidation,
                classWriterStrategy,
                ignoredMethods,
                auxiliaryTypes,
                classFileLocator);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> annotateType(Collection<? extends AnnotationDescription> annotations) {
        return attribute(new TypeAttributeAppender.Explicit(new ArrayList<AnnotationDescription>(annotations)));
    }

    /**
     * {@inheritDoc}
     */
    public MethodDefinition.ImplementationDefinition.Optional<T> implement(Collection<? extends TypeDefinition> interfaceTypes) {
        throw new UnsupportedOperationException("Cannot implement interface for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> initializer(ByteCodeAppender byteCodeAppender) {
        throw new UnsupportedOperationException("Cannot add initializer of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> initializer(LoadedTypeInitializer loadedTypeInitializer) {
        throw new UnsupportedOperationException("Cannot add initializer of decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public TypeVariableDefinition<T> typeVariable(String symbol, Collection<? extends TypeDefinition> bounds) {
        throw new UnsupportedOperationException("Cannot add type variable to decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> transform(ElementMatcher<? super TypeDescription.Generic> matcher, Transformer<TypeVariableToken> transformer) {
        throw new UnsupportedOperationException("Cannot transform decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public FieldDefinition.Optional.Valuable<T> defineField(String name, TypeDefinition type, int modifiers) {
        throw new UnsupportedOperationException("Cannot define field for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public FieldDefinition.Valuable<T> field(LatentMatcher<? super FieldDescription> matcher) {
        throw new UnsupportedOperationException("Cannot change field for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public DynamicType.Builder<T> ignoreAlso(LatentMatcher<? super MethodDescription> ignoredMethods) {
        return new DecoratingDynamicTypeBuilder<T>(instrumentedType,
                typeAttributeAppender,
                asmVisitorWrapper,
                classFileVersion,
                auxiliaryTypeNamingStrategy,
                annotationValueFilterFactory,
                annotationRetention,
                implementationContextFactory,
                methodGraphCompiler,
                typeValidation,
                classWriterStrategy,
                new LatentMatcher.Disjunction<MethodDescription>(this.ignoredMethods, ignoredMethods),
                auxiliaryTypes,
                classFileLocator);
    }

    /**
     * {@inheritDoc}
     */
    public MethodDefinition.ParameterDefinition.Initial<T> defineMethod(String name, TypeDefinition returnType, int modifiers) {
        throw new UnsupportedOperationException("Cannot define method for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public MethodDefinition.ParameterDefinition.Initial<T> defineConstructor(int modifiers) {
        throw new UnsupportedOperationException("Cannot define constructor for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public MethodDefinition.ImplementationDefinition<T> invokable(LatentMatcher<? super MethodDescription> matcher) {
        throw new UnsupportedOperationException("Cannot intercept method for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public RecordComponentDefinition.Optional<T> defineRecordComponent(String name, TypeDefinition type) {
        throw new UnsupportedOperationException("Cannot define record component for decorated type: " + instrumentedType);
    }

    @Override
    public RecordComponentDefinition<T> recordComponent(LatentMatcher<? super RecordComponentDescription> matcher) {
        throw new UnsupportedOperationException("Cannot change record component for decorated type: " + instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Builder<T> require(Collection<DynamicType> auxiliaryTypes) {
        return new DecoratingDynamicTypeBuilder<T>(instrumentedType,
                typeAttributeAppender,
                asmVisitorWrapper,
                classFileVersion,
                auxiliaryTypeNamingStrategy,
                annotationValueFilterFactory,
                annotationRetention,
                implementationContextFactory,
                methodGraphCompiler,
                typeValidation,
                classWriterStrategy,
                ignoredMethods,
                CompoundList.of(this.auxiliaryTypes, new ArrayList<DynamicType>(auxiliaryTypes)),
                classFileLocator);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy) {
        return make(typeResolutionStrategy, TypePool.Empty.INSTANCE);
    }

    /**
     * {@inheritDoc}
     */
    public DynamicType.Unloaded<T> make(TypeResolutionStrategy typeResolutionStrategy, TypePool typePool) {
        return TypeWriter.Default.<T>forDecoration(instrumentedType,
                classFileVersion,
                auxiliaryTypes,
                CompoundList.of(methodGraphCompiler.compile(instrumentedType)
                        .listNodes()
                        .asMethodList()
                        .filter(not(ignoredMethods.resolve(instrumentedType))), instrumentedType.getDeclaredMethods().filter(not(isVirtual()))),
                typeAttributeAppender,
                asmVisitorWrapper,
                annotationValueFilterFactory,
                annotationRetention,
                auxiliaryTypeNamingStrategy,
                implementationContextFactory,
                typeValidation,
                classWriterStrategy,
                typePool,
                classFileLocator).make(typeResolutionStrategy.resolve());
    }

    /**
     * {@inheritDoc}
     */
    public TypeDescription toTypeDescription() {
        return instrumentedType;
    }
}