package de.uni_hamburg.fs;

import java.util.Collections;
import java.util.Vector;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;


class AbstractNodeTest {

    @Test
    void testDeltaWithEmptyPath() throws NoSuchFeatureException {
        // given
        AbstractNode node = mock(AbstractNode.class);
        Path localMockPath = mock(Path.class);

        when(localMockPath.features()).thenReturn(Collections.emptyEnumeration());
        when(node.delta(localMockPath)).thenCallRealMethod();

        // when
        Node resultNode = node.delta(localMockPath);

        // then
        assertThat(resultNode).isSameAs(node);
    }

    @Test
    void testDeltaWithSingleFeaturePathSuccess() throws NoSuchFeatureException {
        // given
        AbstractNode node = mock(AbstractNode.class);
        Path localMockPath = mock(Path.class);
        Name localFeature1 = mock(Name.class);
        Node expectedNode = mock(Node.class);

        when(localMockPath.features()).thenAnswer(invocation -> {
            Vector<Name> featuresVector = new Vector<>();
            featuresVector.add(localFeature1);
            return featuresVector.elements();
        });

        when(node.delta(localFeature1)).thenReturn(expectedNode);
        when(node.delta(localMockPath)).thenCallRealMethod();

        // when
        Node resultNode = node.delta(localMockPath);

        // then
        assertThat(resultNode).isSameAs(expectedNode);
    }

    @Test
    void testDeltaWithMultiFeaturePathSuccess() throws NoSuchFeatureException {
        // given
        AbstractNode node = mock(AbstractNode.class);
        Path localMockPath = mock(Path.class);
        Name localFeature1 = mock(Name.class);
        Name localFeature2 = mock(Name.class);
        Node intermediateNode = mock(Node.class);
        Node finalNode = mock(Node.class);

        when(localMockPath.features()).thenAnswer(invocation -> {
            Vector<Name> featuresVector = new Vector<>();
            featuresVector.add(localFeature1);
            featuresVector.add(localFeature2);
            return featuresVector.elements();
        });

        when(node.delta(localFeature1)).thenReturn(intermediateNode);
        when(intermediateNode.delta(localFeature2)).thenReturn(finalNode);
        when(node.delta(localMockPath)).thenCallRealMethod();

        // when
        Node resultNode = node.delta(localMockPath);

        // then
        assertThat(resultNode).isSameAs(finalNode);
    }

    @Test
    void testDeltaPathThrowsNoSuchFeatureExceptionFirstStep() throws NoSuchFeatureException {
        // given
        AbstractNode node = mock(AbstractNode.class);
        Path localMockPath = mock(Path.class);
        Name localFeature1 = mock(Name.class);
        Name localFeature2 = mock(Name.class);
        Type nodeType = mock(Type.class);

        NoSuchFeatureException expectedException =
            new NoSuchFeatureException(localFeature1, nodeType);

        when(localMockPath.features()).thenAnswer(invocation -> {
            Vector<Name> featuresVector = new Vector<>();
            featuresVector.add(localFeature1);
            featuresVector.add(localFeature2);
            return featuresVector.elements();
        });
        when(node.getType()).thenReturn(nodeType);
        when(node.delta(localFeature1)).thenThrow(expectedException);
        when(node.delta(localMockPath)).thenCallRealMethod();

        // when & then
        assertThatThrownBy(() -> node.delta(localMockPath))
            .isInstanceOf(NoSuchFeatureException.class).isSameAs(expectedException);
    }

    @Test
    void testDeltaPathThrowsNoSuchFeatureExceptionSecondStep() throws NoSuchFeatureException {
        // given
        AbstractNode node = mock(AbstractNode.class);
        Path localMockPath = mock(Path.class);
        Name localFeature1 = mock(Name.class);
        Name localFeature2 = mock(Name.class);
        Node intermediateNode = mock(Node.class);
        Type intermediateNodeType = mock(Type.class);

        NoSuchFeatureException expectedException =
            new NoSuchFeatureException(localFeature2, intermediateNodeType);

        when(localMockPath.features()).thenAnswer(invocation -> {
            Vector<Name> featuresVector = new Vector<>();
            featuresVector.add(localFeature1);
            featuresVector.add(localFeature2);
            return featuresVector.elements();
        });
        when(node.delta(localFeature1)).thenReturn(intermediateNode);
        when(intermediateNode.getType()).thenReturn(intermediateNodeType);
        when(intermediateNode.delta(localFeature2)).thenThrow(expectedException);
        when(node.delta(localMockPath)).thenCallRealMethod();

        // when & then
        assertThatThrownBy(() -> node.delta(localMockPath))
            .isInstanceOf(NoSuchFeatureException.class).isSameAs(expectedException);
    }
}