/*
 * Copyright (c) 2013, 2017, 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 com.oracle.svm.truffle;

//Checkstyle: allow reflection

import static com.oracle.svm.core.util.VMError.shouldNotReachHere;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;

import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.svm.core.annotate.AnnotateOriginal;
import com.oracle.svm.core.annotate.NeverInline;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.graal.hosted.GraalFeature;
import com.oracle.svm.graal.hosted.GraalObjectReplacer;
import com.oracle.svm.graal.meta.SubstrateType;
import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeClass;

public class NodeClassFeature implements Feature {

    private AnalysisMetaAccess metaAccess;
    private GraalObjectReplacer graalObjectReplacer;

    private final Set<Class<?>> registeredClasses = new HashSet<>();

    @Override
    public List<Class<? extends Feature>> getRequiredFeatures() {
        return Arrays.asList(TruffleFeature.class, GraalFeature.class);
    }

    @Override
    public void duringSetup(DuringSetupAccess access) {
        metaAccess = ((DuringSetupAccessImpl) access).getMetaAccess();
        graalObjectReplacer = ImageSingletons.lookup(GraalFeature.class).getObjectReplacer();
        access.registerObjectReplacer(this::replaceNodeFieldAccessor);
    }

    @SuppressWarnings("deprecation")
    private Object replaceNodeFieldAccessor(Object source) {
        if (source instanceof com.oracle.truffle.api.nodes.NodeFieldAccessor ||
                        (source instanceof com.oracle.truffle.api.nodes.NodeFieldAccessor[] && ((com.oracle.truffle.api.nodes.NodeFieldAccessor[]) source).length > 0)) {
            throw VMError.shouldNotReachHere("Cannot have NodeFieldAccessor in image, they must be created lazily");

        } else if (source instanceof NodeClass && !(source instanceof SubstrateType)) {
            NodeClass nodeClass = (NodeClass) source;
            NodeClass replacement = graalObjectReplacer.createType(metaAccess.lookupJavaType(nodeClass.getType()));
            assert replacement != null;
            return replacement;
        }
        return source;
    }

    @Override
    public void duringAnalysis(DuringAnalysisAccess access) {
        for (Class<?> clazz : access.reachableSubtypes(Node.class)) {
            registerUnsafeAccess(access, clazz.asSubclass(Node.class));

            AnalysisType type = ((DuringAnalysisAccessImpl) access).getMetaAccess().lookupJavaType(clazz);
            if (type.isInstantiated()) {
                graalObjectReplacer.createType(type);
            }
        }
    }

    @SuppressWarnings("deprecation")
    private void registerUnsafeAccess(DuringAnalysisAccess access, Class<? extends Node> clazz) {
        if (registeredClasses.contains(clazz)) {
            return;
        }
        registeredClasses.add(clazz);

        NodeClass nodeClass = NodeClass.get(clazz);

        for (com.oracle.truffle.api.nodes.NodeFieldAccessor accessor : nodeClass.getFields()) {
            Field field;
            try {
                field = accessor.getDeclaringClass().getDeclaredField(accessor.getName());
            } catch (NoSuchFieldException ex) {
                throw shouldNotReachHere(ex);
            }

            if (accessor.getKind() == com.oracle.truffle.api.nodes.NodeFieldAccessor.NodeFieldKind.PARENT ||
                            accessor.getKind() == com.oracle.truffle.api.nodes.NodeFieldAccessor.NodeFieldKind.CHILD ||
                            accessor.getKind() == com.oracle.truffle.api.nodes.NodeFieldAccessor.NodeFieldKind.CHILDREN) {
                /*
                 * It's a field which represents an edge in the graph. Such fields are written with
                 * Unsafe in the NodeClass, e.g. when making changes in the graph.
                 */
                // TODO register unsafe accessed Truffle nodes in a separate partition?
                access.registerAsUnsafeAccessed(field);
            }

            if (accessor.getKind() == com.oracle.truffle.api.nodes.NodeFieldAccessor.NodeFieldKind.DATA &&
                            com.oracle.truffle.api.nodes.NodeCloneable.class.isAssignableFrom(accessor.getType())) {
                /*
                 * It's a cloneable non-child data field of the node. Such fields are written with
                 * Unsafe in the NodeUtil.deepCopyImpl.
                 */
                access.registerAsUnsafeAccessed(field);
            }

            /* All other fields are only read with Unsafe. */
            access.registerAsAccessed(field);
        }

        access.requireAnalysisIteration();
    }
}

@TargetClass(className = "com.oracle.truffle.api.nodes.NodeClass", onlyWith = TruffleFeature.IsEnabled.class)
final class Target_com_oracle_truffle_api_nodes_NodeClass {

    @Substitute
    public static NodeClass get(Class<?> clazz) {
        CompilerAsserts.neverPartOfCompilation();

        NodeClass nodeClass = (NodeClass) DynamicHub.fromClass(clazz).getMetaType();
        if (nodeClass == null) {
            throw shouldNotReachHere("Unknown node class: " + clazz.getName());
        }
        return nodeClass;
    }
}

@TargetClass(className = "com.oracle.truffle.api.nodes.Node", onlyWith = TruffleFeature.IsEnabled.class)
final class Target_com_oracle_truffle_api_nodes_Node {
    @AnnotateOriginal
    @NeverInline("")
    public native void adoptChildren();
}