/*
 * Copyright (c) 2014 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.tree.impl;

import static java.util.Objects.requireNonNull;

import com.google.common.collect.ImmutableMap;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.tree.api.DataTreeConfiguration;
import org.opendaylight.yangtools.yang.data.tree.impl.AbstractNodeContainerModificationStrategy.Visible;
import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base strategy for applying changes to a ContainerNode, irrespective of its
 * actual type.
 *
 * @param <T> Type of the container node
 */
class DataNodeContainerModificationStrategy<T extends DataNodeContainer & WithStatus> extends Visible<T> {
    private static final Logger LOG = LoggerFactory.getLogger(DataNodeContainerModificationStrategy.class);

    private final @NonNull DataTreeConfiguration treeConfig;

    @SuppressWarnings("rawtypes")
    private static final AtomicReferenceFieldUpdater<DataNodeContainerModificationStrategy, ImmutableMap> UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(DataNodeContainerModificationStrategy.class, ImmutableMap.class,
                "children");
    private volatile ImmutableMap<PathArgument, ModificationApplyOperation> children = ImmutableMap.of();

    DataNodeContainerModificationStrategy(final NormalizedNodeContainerSupport<?, ?> support, final T schema,
            final DataTreeConfiguration treeConfig) {
        super(support, treeConfig, schema);
        this.treeConfig = requireNonNull(treeConfig, "treeConfig");
    }

    @Override
    public final ModificationApplyOperation childByArg(final PathArgument arg) {
        final var local = children;
        final var existing = local.get(arg);
        if (existing != null) {
            return existing;
        }

        final var childOperation = resolveChild(arg);
        return childOperation != null ? appendChild(local, arg, childOperation) : null;
    }

    private ModificationApplyOperation resolveChild(final PathArgument identifier) {
        final T schema = getSchema();
        if (identifier instanceof AugmentationIdentifier augId && schema instanceof AugmentationTarget augTarget) {
            return SchemaAwareApplyOperation.from(schema, augTarget, augId, treeConfig);
        }

        final var qname = identifier.getNodeType();
        final var child = schema.dataChildByName(qname);
        if (child == null) {
            LOG.trace("Child {} not present in container schema {} children {}", identifier, this,
                schema.getChildNodes());
            return null;
        }

        try {
            return SchemaAwareApplyOperation.from(child, treeConfig);
        } catch (ExcludedDataSchemaNodeException e) {
            LOG.trace("Failed to instantiate child {} in container schema {} children {}", identifier, this,
                schema.getChildNodes(), e);
            return null;
        }
    }

    private @Nullable ModificationApplyOperation appendChild(
            final ImmutableMap<PathArgument, ModificationApplyOperation> initial, final PathArgument identifier,
            final ModificationApplyOperation computed) {
        var previous = initial;
        while (true) {
            // Build up a new map based on observed snapshot and computed child
            final var updated = ImmutableMap
                .<PathArgument, ModificationApplyOperation>builderWithExpectedSize(previous.size() + 1)
                .putAll(previous)
                .put(identifier, computed)
                .build();

            // Attempt to install the updated map
            if (UPDATER.compareAndSet(this, previous, updated)) {
                return computed;
            }

            // We have raced, acquire a new snapshot, recheck presence and retry if needed
            previous = children;
            final var raced = previous.get(identifier);
            if (raced != null) {
                return raced;
            }
        }
    }
}

@@ -470,58 +470,68 @@
 ava.
-util.concurrent.atomic.AtomicReferenceFieldUpdater
+lang.invoke.MethodHandles;%0Aimport java.lang.invoke.VarHandle
 ;%0Aim
@@ -1608,17 +1608,16 @@
 class);%0A
-%0A
     priv
@@ -1624,323 +1624,345 @@
 ate 
-final @NonNull DataTreeConfiguration treeConfig;%0A%0A    @SuppressWarnings(%22rawtypes%22)%0A    private static final AtomicReferenceFieldUpdater%3CDataNodeContainerModificationStrategy, ImmutableMap%3E UPDATER =%0A            AtomicReferenceFieldUpdater.newUpdater(DataNodeContainerModificationStrategy.class, ImmutableMap.class,
+static final VarHandle CHILDREN;%0A%0A    static %7B%0A        try %7B%0A            CHILDREN = MethodHandles.lookup().findVarHandle(%0A                DataNodeContainerModificationStrategy.class, %22children%22, ImmutableMap.class);%0A        %7D catch (NoSuchFieldException %7C IllegalAccessException e) %7B%0A            throw new ExceptionInInitializerError(e);
 %0A   
@@ -1970,28 +1970,78 @@
     
+%7D%0A
     
+%7D%0A%0A
     
-%22children%22);
+private final @NonNull DataTreeConfiguration treeConfig;%0A
 %0A   
@@ -4496,19 +4496,109 @@
     
-if (UPDATER
+final var witness = (ImmutableMap%3CPathArgument, ModificationApplyOperation%3E)%0A                CHILDREN
 .com
@@ -4608,11 +4608,16 @@
 eAnd
-Set
+Exchange
 (thi
@@ -4637,16 +4637,53 @@
 updated)
+;%0A            if (witness == previous
 ) %7B%0A    
@@ -4840,24 +4840,23 @@
 vious = 
-children
+witness
 ;%0A      

Use VarHandles in DataNodeContainerModificationStrategy