/*
 * 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 jdk.internal.classfile.impl;

import java.lang.classfile.AttributeMapper;
import java.lang.classfile.ClassBuilder;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassHierarchyResolver;
import java.lang.classfile.ClassModel;
import java.lang.classfile.ClassTransform;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.classfile.constantpool.Utf8Entry;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

import jdk.internal.classfile.impl.verifier.VerifierImpl;

import static java.util.Objects.requireNonNull;

public final class ClassFileImpl implements ClassFile {

    private Option stackMapsOption;
    private Option debugElementsOption;
    private Option lineNumbersOption;
    private Option attributesProcessingOption;
    private Option constantPoolSharingOption;
    private Option shortJumpsOption;
    private Option deadCodeOption;
    private Option deadLabelsOption;
    private Option classHierarchyResolverOption;
    private Option attributeMapperOption;

    private ClassFileImpl(Option stackMapsOption,
                          Option debugElementsOption,
                          Option lineNumbersOption,
                          Option attributesProcessingOption,
                          Option constantPoolSharingOption,
                          Option shortJumpsOption,
                          Option deadCodeOption,
                          Option deadLabelsOption,
                          Option classHierarchyResolverOption,
                          Option attributeMapperOption) {
        this.stackMapsOption              = stackMapsOption;
        this.debugElementsOption          = debugElementsOption;
        this.lineNumbersOption            = lineNumbersOption;
        this.attributesProcessingOption   = attributesProcessingOption;
        this.constantPoolSharingOption    = constantPoolSharingOption;
        this.shortJumpsOption             = shortJumpsOption;
        this.deadCodeOption               = deadCodeOption;
        this.deadLabelsOption             = deadLabelsOption;
        this.classHierarchyResolverOption = classHierarchyResolverOption;
        this.attributeMapperOption        = attributeMapperOption;
    }

    public static final ClassFileImpl DEFAULT_CONTEXT = new ClassFileImpl(
            null, // StackMapsOption.STACK_MAPS_WHEN_REQUIRED
            null, // DebugElementsOption.PASS_DEBUG,
            null, // LineNumbersOption.PASS_LINE_NUMBERS,
            null, // AttributesProcessingOption.PASS_ALL_ATTRIBUTES,
            null, // ConstantPoolSharingOption.SHARED_POOL,
            null, // ShortJumpsOption.FIX_SHORT_JUMPS,
            null, // DeadCodeOption.PATCH_DEAD_CODE,
            null, // DeadLabelsOption.FAIL_ON_DEAD_LABELS,
            null, // new ClassHierarchyResolverOptionImpl(ClassHierarchyResolver.defaultResolver()),
            null  // _ -> null
        );

    @SuppressWarnings("unchecked")
    @Override
    public ClassFileImpl withOptions(Option... options) {
        var smo = stackMapsOption;
        var deo = debugElementsOption;
        var lno = lineNumbersOption;
        var apo = attributesProcessingOption;
        var cpso = constantPoolSharingOption;
        var sjo = shortJumpsOption;
        var dco = deadCodeOption;
        var dlo = deadLabelsOption;
        var chro = classHierarchyResolverOption;
        var amo = attributeMapperOption;
        for (var o : options) {
            if (o instanceof StackMapsOption oo) {
                smo = oo;
            } else if (o instanceof ClassHierarchyResolverOption oo) {
                chro = oo;
            } else if (o instanceof DebugElementsOption oo) {
                deo = oo;
            } else if (o instanceof LineNumbersOption oo) {
                lno = oo;
            } else if (o instanceof AttributesProcessingOption oo) {
                apo = oo;
            } else if (o instanceof ConstantPoolSharingOption oo) {
                cpso = oo;
            } else if (o instanceof ShortJumpsOption oo) {
                sjo = oo;
            } else if (o instanceof DeadCodeOption oo) {
                dco = oo;
            } else if (o instanceof DeadLabelsOption oo) {
                dlo = oo;
            } else if (o instanceof AttributeMapperOption oo) {
                amo = oo;
            } else { // null or unknown Option type
                throw new IllegalArgumentException("Invalid option: " + requireNonNull(o));
            }
        }
        return new ClassFileImpl(smo, deo, lno, apo, cpso, sjo, dco, dlo, chro, amo);
    }

    @Override
    public ClassModel parse(byte[] bytes) {
        return new ClassImpl(bytes, this);
    }

    @Override
    public byte[] build(ClassEntry thisClassEntry,
                         ConstantPoolBuilder constantPool,
                         Consumer<? super ClassBuilder> handler) {
        thisClassEntry = AbstractPoolEntry.maybeClone(constantPool, thisClassEntry);
        DirectClassBuilder builder = new DirectClassBuilder((SplitConstantPool)constantPool, this, thisClassEntry);
        handler.accept(builder);
        return builder.build();
    }

    @Override
    public byte[] transformClass(ClassModel model, ClassEntry newClassName, ClassTransform transform) {
        ConstantPoolBuilder constantPool = sharedConstantPool() ? ConstantPoolBuilder.of(model)
                                                                : ConstantPoolBuilder.of();
        return build(newClassName, constantPool,
                new Consumer<ClassBuilder>() {
                    @Override
                    public void accept(ClassBuilder builder) {
                        ((DirectClassBuilder) builder).setOriginal((ClassImpl)model);
                        ((DirectClassBuilder) builder).setSizeHint(((ClassImpl)model).classfileLength());
                        builder.transform((ClassImpl)model, transform);
                    }
                });
    }

    public boolean sharedConstantPool() {
        return constantPoolSharingOption == null || constantPoolSharingOption == ConstantPoolSharingOption.SHARED_POOL;
    }

    @Override
    public List<VerifyError> verify(ClassModel model) {
        try {
            return VerifierImpl.verify(model, classHierarchyResolver(), null);
        } catch (IllegalArgumentException verifierInitializationError) {
            return List.of(new VerifyError(verifierInitializationError.getMessage()));
        }
    }

    @Override
    public List<VerifyError> verify(byte[] bytes) {
        try {
            return verify(parse(bytes));
        } catch (IllegalArgumentException parsingError) {
            return List.of(new VerifyError(parsingError.getMessage()));
        }
    }

    public Function<Utf8Entry, AttributeMapper<?>> attributeMapper() {
        if (attributeMapperOption == null) {
            return _ -> null;
        } else {
            return ((AttributeMapperOption)attributeMapperOption).attributeMapper();
        }
    }

    public ClassHierarchyResolver classHierarchyResolver() {
        if (classHierarchyResolverOption == null) {
            return ClassHierarchyImpl.DEFAULT_RESOLVER;
        } else {
            return ((ClassHierarchyResolverOption)classHierarchyResolverOption).classHierarchyResolver();
        }
    }

    public boolean dropDeadLabels() {
        return (deadLabelsOption != null && deadLabelsOption == DeadLabelsOption.DROP_DEAD_LABELS);
    }

    public boolean passDebugElements() {
        return (debugElementsOption == null || debugElementsOption == DebugElementsOption.PASS_DEBUG);
    }

    public boolean passLineNumbers() {
        return (lineNumbersOption == null || lineNumbersOption == LineNumbersOption.PASS_LINE_NUMBERS);
    }

    public AttributesProcessingOption attributesProcessingOption() {
        return (attributesProcessingOption == null) ? AttributesProcessingOption.PASS_ALL_ATTRIBUTES : (AttributesProcessingOption)attributesProcessingOption;
    }

    public boolean fixShortJumps() {
        return (shortJumpsOption == null || shortJumpsOption == ShortJumpsOption.FIX_SHORT_JUMPS);
    }

    public boolean stackMapsWhenRequired() {
        return (stackMapsOption == null || stackMapsOption == StackMapsOption.STACK_MAPS_WHEN_REQUIRED);
    }

    public boolean generateStackMaps() {
        return (stackMapsOption == StackMapsOption.GENERATE_STACK_MAPS);
    }

    public boolean dropStackMaps() {
        return (stackMapsOption == StackMapsOption.DROP_STACK_MAPS);
    }

    public boolean patchDeadCode() {
        return (deadCodeOption == null || deadCodeOption == DeadCodeOption.PATCH_DEAD_CODE);
    }

    public record AttributeMapperOptionImpl(Function<Utf8Entry, AttributeMapper<?>> attributeMapper)
            implements AttributeMapperOption {
    }

    public record ClassHierarchyResolverOptionImpl(ClassHierarchyResolver classHierarchyResolver)
            implements ClassHierarchyResolverOption {
    }
}
