package de.uni_hamburg.fs;

import java.util.Enumeration;
import java.util.Vector;

import org.junit.jupiter.api.Test;

import de.renew.util.Value;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class JavaArrayTypeTest {

    @Test
    void testConstructorWithObjectArray() {
        // given
        String[] testArray = { "test1", "test2", "test3" };

        // when
        JavaArrayType javaArrayType = new JavaArrayType(testArray);

        // then
        assertThat(javaArrayType).isNotNull();
        assertThat(javaArrayType.getBaseType()).isNotNull();
        assertThat(javaArrayType.getBaseType() instanceof JavaType).isTrue();
        assertThat(javaArrayType.isInstanceType()).isTrue();
    }

    @Test
    void testConstructorWithPrimitiveArray() {
        // given
        int[] testArray = { 1, 2, 3 };

        // when
        JavaArrayType javaArrayType = new JavaArrayType(testArray);

        // then
        assertThat(javaArrayType).isNotNull();
        assertThat(javaArrayType.isInstanceType()).isTrue();

        Object[] wrappedArray = (Object[]) javaArrayType.getJavaObject();
        assertThat(wrappedArray.length).isEqualTo(3);
        assertThat(wrappedArray[0]).isInstanceOf(Value.class);
        assertThat(((Value) wrappedArray[0]).value).isEqualTo(1);
    }

    @Test
    void testConstructorWithEnumeration() {
        // given
        Vector<String> vector = new Vector<>();
        vector.add("test1");
        vector.add("test2");
        Enumeration<String> enumeration = vector.elements();

        // when
        JavaArrayType javaArrayType = new JavaArrayType(enumeration);

        // then
        assertThat(javaArrayType).isNotNull();
        assertThat(javaArrayType.getBaseType()).isNotNull();
        Object[] extractedArray = (Object[]) javaArrayType.getJavaObject();
        assertThat(extractedArray).containsExactly("test1", "test2");
    }

    @Test
    void testGetJavaObject() {
        // given
        String[] testArray = { "test1", "test2" };
        JavaArrayType javaArrayType = new JavaArrayType(testArray);

        // when
        Object result = javaArrayType.getJavaObject();

        // then
        assertThat(result).isInstanceOf(Object[].class);
        assertThat(((Object[]) result).length).isEqualTo(2);
    }

    @Test
    void testEquals() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test2" };
        String[] array3 = { "test1", "test3" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);
        JavaArrayType type3 = new JavaArrayType(array3);

        // when & then
        assertThat(type1.equals(type2)).isTrue();
        assertThat(type1.equals(type3)).isFalse();
    }

    @Test
    void testMakeObjectArray() {
        // given
        int[] primitiveArray = { 1, 2, 3 };

        // when
        Object[] result = JavaArrayType.makeObjectArray(primitiveArray);

        // then
        assertThat(result).hasSize(3);
        assertThat(result[0]).isInstanceOf(Value.class);
        assertThat(((Value) result[0]).value).isEqualTo(1);
    }

    @Test
    void testMakeArray() {
        // given
        Value[] valueArray = { new Value(1), new Value(2), new Value(3) };

        // when
        Object result = JavaArrayType.makeArray(valueArray);

        // then
        assertThat(result).isInstanceOf(int[].class);
        int[] resultArray = (int[]) result;
        assertThat(resultArray).containsExactly(1, 2, 3);
    }

    @Test
    void testSubsumesWithIdenticalArrays() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test2" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when
        boolean result = type1.subsumes(type2);

        // then
        assertThat(result).isTrue();
    }

    @Test
    void testSubsumesWithDifferentArrays() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test3" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when
        boolean result = type1.subsumes(type2);

        // then
        assertThat(result).isFalse();
    }

    @Test
    void testUnifyWithIdenticalArrays() throws UnificationFailure {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test2" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when
        Type result = type1.unify(type2);

        // then
        assertThat(result).isSameAs(type1);
    }

    @Test
    void testUnifyWithDifferentArrays() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test3" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when & then
        assertThatThrownBy(() -> type1.unify(type2)).isInstanceOf(UnificationFailure.class);
    }

    @Test
    void testCanUnifyWithCompatibleArrays() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test2" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when
        boolean result = type1.canUnify(type2);

        // then
        assertThat(result).isTrue();
    }

    @Test
    void testCanUnifyWithIncompatibleArrays() {
        // given
        String[] array1 = { "test1", "test2" };
        String[] array2 = { "test1", "test3" };

        JavaArrayType type1 = new JavaArrayType(array1);
        JavaArrayType type2 = new JavaArrayType(array2);

        // when
        boolean result = type1.canUnify(type2);

        // then
        assertThat(result).isFalse();
    }

    @Test
    void testNewNode() {
        // given
        String[] testArray = { "test1", "test2" };
        JavaArrayType javaArrayType = new JavaArrayType(testArray);

        // when
        Node result = javaArrayType.newNode();

        // then
        assertThat(result).isNotNull();
        assertThat(result.getType()).isEqualTo(javaArrayType);
    }
}