package de.renew.navigator.gui;

import java.io.File;
import java.util.List;
import java.util.Vector;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import de.renew.navigator.models.Directory;
import de.renew.navigator.models.TreeElement;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;


@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class FileTreeNodeTest {

    private TreeElement fileElem;
    private FileTreeNode fileNode;
    private FileTreeNode dirNodeClosed;
    private FileTreeNode dirNodeOpen;
    private File testFile;

    @Mock
    Directory closedDirElem;

    @Mock
    Directory openDirElem;

    @BeforeEach
    void setUp() {
        testFile = new File("foo.txt").getAbsoluteFile();

        fileElem = mock(TreeElement.class);
        when(fileElem.getFile()).thenReturn(testFile);
        when(fileElem.getName()).thenReturn("foo.txt");
        fileNode = new FileTreeNode(fileElem);

        when(closedDirElem.isOpened()).thenReturn(false);
        dirNodeClosed = new FileTreeNode(closedDirElem);

        when(openDirElem.isOpened()).thenReturn(true);
        dirNodeOpen = new FileTreeNode(openDirElem);
    }

    @Test
    void testBasicAccessors() {
        assertEquals(testFile, fileNode.getFile());
        assertEquals("foo.txt", fileNode.toString());
        assertEquals(testFile.getAbsolutePath(), fileNode.getToolTip());

        assertNull(fileNode.getParent());
        assertTrue(fileNode.isLeaf());
        assertFalse(fileNode.getAllowsChildren());
        assertTrue(fileNode.getChildren().isEmpty());
        assertEquals(0, fileNode.getChildCount());
    }

    @Test
    void testAllowsChildrenForDirectory() {
        assertTrue(dirNodeClosed.getAllowsChildren());
        assertTrue(dirNodeOpen.getAllowsChildren());
    }

    @Test
    void testAddInsertRemove() {
        MutableTreeNode child1 = new FileTreeNode(fileElem);
        MutableTreeNode child2 = new FileTreeNode(fileElem);

        fileNode.add(child1);
        fileNode.insert(child2, 0);

        assertEquals(2, fileNode.getChildCount());
        assertSame(child2, fileNode.getChildAt(0));
        assertSame(child1, fileNode.getChildAt(1));

        fileNode.remove(1);
        assertEquals(1, fileNode.getChildCount());

        fileNode.remove(child2);
        assertEquals(0, fileNode.getChildCount());
    }

    @Test
    void testRemoveFromParent() {
        FileTreeNode parent = new FileTreeNode(fileElem);
        FileTreeNode child = new FileTreeNode(fileElem);
        parent.add(child);

        child.removeFromParent();
        assertSame(parent, child.getParent()); // parent reference remains
        assertEquals(0, parent.getChildCount());
    }

    @Test
    void testSetParentOnly() {
        FileTreeNode other = new FileTreeNode(fileElem);
        fileNode.setParent(other);
        assertSame(other, fileNode.getParent());
    }

    @Test
    void testGetPath() {
        FileTreeNode root = new FileTreeNode(fileElem);
        FileTreeNode child = new FileTreeNode(fileElem);
        FileTreeNode grand = new FileTreeNode(fileElem);
        root.add(child);
        child.add(grand);

        TreePath path = grand.getPath();
        assertArrayEquals(new Object[] { root, child, grand }, path.getPath());
    }

    @Test
    void testIsLeafLogicForDirectory() {
        assertTrue(dirNodeClosed.isLeaf());
        assertTrue(dirNodeOpen.isLeaf());

        dirNodeOpen.add(new FileTreeNode(fileElem));
        assertFalse(dirNodeOpen.isLeaf());
    }

    @Test
    void testGetOpenedDirectories() {
        assertTrue(dirNodeClosed.getOpenedDirectories().isEmpty());

        List<FileTreeNode> opened = dirNodeOpen.getOpenedDirectories();
        assertEquals(1, opened.size());
        assertSame(dirNodeOpen, opened.get(0));

        FileTreeNode childDir = new FileTreeNode(openDirElem);
        dirNodeOpen.add(childDir);
        List<FileTreeNode> nested = dirNodeOpen.getOpenedDirectories();
        assertTrue(nested.contains(childDir));
        assertTrue(nested.contains(dirNodeOpen));
    }

    @Test
    void testGetModelAndChildrenAccessor() {
        assertSame(fileElem, fileNode.getModel());
        Vector<TreeNode> children = fileNode.getChildren();
        assertNotNull(children);
        assertTrue(children.isEmpty());
    }
}