/*
 * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.yangtools.yang.data.util;

import static java.util.Objects.requireNonNull;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.CheckedValue;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveModelContextProvider;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;

/**
 * Semantic tree binding a {@link EffectiveModelContext} to a {@link NormalizedNode} tree. Since the layout of the
 * schema and data has differences, the mapping is not trivial -- which is where this class comes in.
 *
 * @author Robert Varga
 */
public final class DataSchemaContextTree extends AbstractEffectiveModelContextProvider {
    // FIXME: record once we have JDK17+
    public static final class NodeAndStack {
        private final @NonNull DataSchemaContextNode<?> node;
        private final @NonNull SchemaInferenceStack stack;

        NodeAndStack(final DataSchemaContextNode<?> node, final @NonNull SchemaInferenceStack stack) {
            this.node = requireNonNull(node);
            this.stack = requireNonNull(stack);
        }

        public @NonNull DataSchemaContextNode<?> node() {
            return node;
        }

        public @NonNull SchemaInferenceStack stack() {
            return stack;
        }
    }

    private static final LoadingCache<EffectiveModelContext, @NonNull DataSchemaContextTree> TREES =
        CacheBuilder.newBuilder().weakKeys().weakValues().build(new CacheLoader<>() {
            @Override
            public DataSchemaContextTree load(final EffectiveModelContext key) {
                return new DataSchemaContextTree(key);
            }
        });

    private final @NonNull ContainerContextNode root;

    private DataSchemaContextTree(final EffectiveModelContext ctx) {
        super(ctx);
        root = new ContainerContextNode(ctx);
    }

    public static @NonNull DataSchemaContextTree from(final @NonNull EffectiveModelContext ctx) {
        return TREES.getUnchecked(ctx);
    }

    /**
     * Find a child node as identified by an absolute {@link YangInstanceIdentifier}.
     *
     * @param path Path towards the child node
     * @return Child node if present, or empty when corresponding child is not found.
     * @throws NullPointerException if {@code path} is null
     */
    public @NonNull Optional<@NonNull DataSchemaContextNode<?>> findChild(final @NonNull YangInstanceIdentifier path) {
        return root.findChild(path);
    }

    /**
     * Find a child node as identified by an absolute {@link YangInstanceIdentifier} and return it along with a suitably
     * initialized {@link SchemaInferenceStack}.
     *
     * @param path Path towards the child node
     * @return A {@link NodeAndStack}, or empty when corresponding child is not found.
     * @throws NullPointerException if {@code path} is null
     */
    public @NonNull CheckedValue<@NonNull NodeAndStack, @NonNull IllegalArgumentException> enterPath(
            final YangInstanceIdentifier path) {
        final var stack = SchemaInferenceStack.of((EffectiveModelContext) root.getDataSchemaNode());
        DataSchemaContextNode<?> node = root;
        for (var arg : path.getPathArguments()) {
            final var child = node.enterChild(arg, stack);
            if (child == null) {
                return CheckedValue.ofException(new IllegalArgumentException("Failed to find " + arg + " in " + node));
            }
            node = child;
        }

        return CheckedValue.ofValue(new NodeAndStack(node, stack));
    }

    public @NonNull DataSchemaContextNode<?> getRoot() {
        return root;
    }
}

@@ -1388,112 +1388,35 @@
     
-// FIXME: record once we have JDK17+%0A    public static final class NodeAndStack %7B%0A        private final 
+public record NodeAndStack(
 @Non
@@ -1453,31 +1453,9 @@
 node
-;%0A        private final
+,
  @No
@@ -1486,18 +1486,19 @@
 ck stack
-;%0A
+) %7B
 %0A       
@@ -1498,16 +1498,23 @@
         
+public 
 NodeAndS
@@ -1524,16 +1524,25 @@
 k(final 
+@NonNull 
 DataSche
@@ -1716,194 +1716,8 @@
    %7D
-%0A%0A        public @NonNull DataSchemaContextNode%3C?%3E node() %7B%0A            return node;%0A        %7D%0A%0A        public @NonNull SchemaInferenceStack stack() %7B%0A            return stack;%0A        %7D
 %0A   

Make DataSchemaContextTree.NodeAndStack a record