/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.sdk.server.nodes.filters;

import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.milo.opcua.sdk.core.AccessLevel;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.UaNodeManager;
import org.eclipse.milo.opcua.sdk.server.api.NodeManager;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext;
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.NamespaceTable;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

public class AttributeFilterChainTest {

    private final UaNodeManager nodeManager = new UaNodeManager();

    private final UaNodeContext context = new UaNodeContext() {
        @Override
        public OpcUaServer getServer() {
            return null;
        }

        @Override
        public NodeManager<UaNode> getNodeManager() {
            return nodeManager;
        }

        @Override
        public NamespaceTable getNamespaceTable() {
            return new NamespaceTable();
        }
    };

    @Test
    public void testAddFirst() {
        AttributeFilterChain chain = new AttributeFilterChain();

        chain.addFirst(
            AttributeFilters.getValue(ctx -> new DataValue(new Variant("A"))),
            AttributeFilters.getValue(ctx -> new DataValue(new Variant("B")))
        );

        DataValue value = (DataValue) chain.getAttribute(null, AttributeId.Value);
        assertEquals(value.getValue().getValue(), "B");
    }

    @Test
    public void testAddLast() {
        AttributeFilterChain chain = new AttributeFilterChain();

        chain.addLast(
            AttributeFilters.getValue(ctx -> (DataValue) ctx.getAttribute(AttributeId.Value)),
            AttributeFilters.getValue(ctx -> new DataValue(new Variant("Last")))
        );

        DataValue value = (DataValue) chain.getAttribute(null, AttributeId.Value);
        assertEquals(value.getValue().getValue(), "Last");
    }

    @Test
    public void testEmptyChain() {
        UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(context)
            .setNodeId(NodeId.NULL_VALUE)
            .setAccessLevel(AccessLevel.READ_WRITE)
            .setBrowseName(QualifiedName.NULL_VALUE)
            .setDisplayName(LocalizedText.NULL_VALUE)
            .setDataType(Identifiers.String)
            .setTypeDefinition(Identifiers.BaseDataVariableType)
            .build();

        node.setValue(new DataValue(new Variant("foo")));

        DataValue value = (DataValue) node.getFilterChain().getAttribute(node, AttributeId.Value);
        assertEquals(value.getValue().getValue(), "foo");
    }

    @Test
    public void testObservable() {
        final AtomicBoolean observed = new AtomicBoolean(false);

        UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(context)
            .setNodeId(NodeId.NULL_VALUE)
            .setAccessLevel(AccessLevel.READ_WRITE)
            .setBrowseName(QualifiedName.NULL_VALUE)
            .setDisplayName(LocalizedText.NULL_VALUE)
            .setDataType(Identifiers.String)
            .setTypeDefinition(Identifiers.BaseDataVariableType)
            .build();

        node.addAttributeObserver((node1, attributeId, value) -> {
            observed.set(true);
        });

        node.getFilterChain().addLast(
            AttributeFilters.getValue(
                ctx -> {
                    ctx.setObservable(true);
                    
                    return new DataValue(new Variant("foo"));
                }
            )
        );

        assertEquals(node.getValue().getValue().getValue(), "foo");

        assertTrue(observed.get());
    }


}
