"""Test module for diferent kind of links under PyTables."""

import re
import tempfile
from pathlib import Path

import tables as tb
from tables.tests import common


# Test for hard links
class HardLinkTestCase(common.TempFileMixin, common.PyTablesTestCase):

    def setUp(self):
        super().setUp()
        self._createFile()

    def _createFile(self):
        self.h5file.create_array('/', 'arr1', [1, 2])
        group1 = self.h5file.create_group('/', 'group1')
        arr2 = self.h5file.create_array(group1, 'arr2', [1, 2, 3])
        lgroup1 = self.h5file.create_hard_link('/', 'lgroup1', '/group1')
        self.assertIsNotNone(lgroup1)
        larr1 = self.h5file.create_hard_link(group1, 'larr1', '/arr1')
        self.assertIsNotNone(larr1)
        larr2 = self.h5file.create_hard_link('/', 'larr2', arr2)
        self.assertIsNotNone(larr2)

    def test00_create(self):
        """Creating hard links."""

        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup1,
                                 hardlink=True)
        self._checkEqualityLeaf(self.h5file.root.arr1,
                                self.h5file.root.group1.larr1,
                                hardlink=True)
        self._checkEqualityLeaf(self.h5file.root.lgroup1.arr2,
                                self.h5file.root.larr2,
                                hardlink=True)

    def test01_open(self):
        """Opening a file with hard links."""

        self._reopen()
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup1,
                                 hardlink=True)
        self._checkEqualityLeaf(self.h5file.root.arr1,
                                self.h5file.root.group1.larr1,
                                hardlink=True)
        self._checkEqualityLeaf(self.h5file.root.lgroup1.arr2,
                                self.h5file.root.larr2,
                                hardlink=True)

    def test02_removeLeaf(self):
        """Removing a hard link to a Leaf."""

        # First delete the initial link
        self.h5file.root.arr1.remove()
        self.assertNotIn('/arr1', self.h5file)
        # The second link should still be there
        if common.verbose:
            print("Remaining link:", self.h5file.root.group1.larr1)
        self.assertIn('/group1/larr1', self.h5file)
        # Remove the second link
        self.h5file.root.group1.larr1.remove()
        self.assertNotIn('/group1/larr1', self.h5file)

    def test03_removeGroup(self):
        """Removing a hard link to a Group."""

        if common.verbose:
            print("Original object tree:", self.h5file)
        # First delete the initial link
        self.h5file.root.group1._f_remove(force=True)
        self.assertNotIn('/group1', self.h5file)
        # The second link should still be there
        if common.verbose:
            print("Remaining link:", self.h5file.root.lgroup1)
            print("Object tree:", self.h5file)
        self.assertIn('/lgroup1', self.h5file)
        # Remove the second link
        self.h5file.root.lgroup1._g_remove(recursive=True)
        self.assertNotIn('/lgroup1', self.h5file)
        if common.verbose:
            print("Final object tree:", self.h5file)


# Test for soft links
class SoftLinkTestCase(common.TempFileMixin, common.PyTablesTestCase):

    def setUp(self):
        super().setUp()
        self._createFile()

    def _createFile(self):
        self.h5file.create_array('/', 'arr1', [1, 2])
        group1 = self.h5file.create_group('/', 'group1')
        arr2 = self.h5file.create_array(group1, 'arr2', [1, 2, 3])
        lgroup1 = self.h5file.create_soft_link('/', 'lgroup1', '/group1')
        self.assertIsNotNone(lgroup1)
        larr1 = self.h5file.create_soft_link(group1, 'larr1', '/arr1')
        self.assertIsNotNone(larr1)
        larr2 = self.h5file.create_soft_link('/', 'larr2', arr2)
        self.assertIsNotNone(larr2)

    def test00_create(self):
        """Creating soft links."""

        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup1())
        self._checkEqualityLeaf(self.h5file.root.arr1,
                                self.h5file.root.group1.larr1())
        self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2,
                                self.h5file.root.larr2())

    def test01_open(self):
        """Opening a file with soft links."""

        self._reopen()
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup1())
        self._checkEqualityLeaf(self.h5file.root.arr1,
                                self.h5file.root.group1.larr1())
        self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2,
                                self.h5file.root.larr2())

    def test02_remove(self):
        """Removing a soft link."""

        # First delete the referred link
        self.h5file.root.arr1.remove()
        self.assertNotIn('/arr1', self.h5file)
        # The soft link should still be there (but dangling)
        if common.verbose:
            print("Dangling link:", self.h5file.root.group1.larr1)
        self.assertIn('/group1/larr1', self.h5file)
        # Remove the soft link itself
        self.h5file.root.group1.larr1.remove()
        self.assertNotIn('/group1/larr1', self.h5file)

    def test03_copy(self):
        """Copying a soft link."""

        # Copy the link into another location
        root = self.h5file.root
        lgroup1 = root.lgroup1
        lgroup2 = lgroup1.copy('/', 'lgroup2')
        self.assertIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self.assertIn('lgroup2', root._v_children)
        self.assertIn('lgroup2', root._v_links)
        if common.verbose:
            print("Copied link:", lgroup2)
        # Remove the first link
        lgroup1.remove()
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test03_overwrite(self):
        """Overwrite a soft link."""

        # Copy the link into another location
        root = self.h5file.root
        lgroup1 = root.lgroup1
        lgroup2 = lgroup1.copy('/', 'lgroup2')
        lgroup2 = lgroup1.copy('/', 'lgroup2', overwrite=True)
        self.assertIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self.assertIn('lgroup2', root._v_children)
        self.assertIn('lgroup2', root._v_links)
        if common.verbose:
            print("Copied link:", lgroup2)
        # Remove the first link
        lgroup1.remove()
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test04_move(self):
        """Moving a soft link."""

        # Move the link into another location
        lgroup1 = self.h5file.root.lgroup1
        group2 = self.h5file.create_group('/', 'group2')
        lgroup1.move(group2, 'lgroup2')
        lgroup2 = self.h5file.root.group2.lgroup2
        if common.verbose:
            print("Moved link:", lgroup2)
        self.assertNotIn('/lgroup1', self.h5file)
        self.assertIn('/group2/lgroup2', self.h5file)
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.group2.lgroup2())

    def test05_rename(self):
        """Renaming a soft link."""

        # Rename the link
        lgroup1 = self.h5file.root.lgroup1
        lgroup1.rename('lgroup2')
        lgroup2 = self.h5file.root.lgroup2
        if common.verbose:
            print("Moved link:", lgroup2)
        self.assertNotIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self._checkEqualityGroup(self.h5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test06a_relative_path(self):
        """Using soft links with relative paths."""

        # Create new group
        self.h5file.create_group('/group1', 'group3')
        # ... and relative link
        lgroup3 = self.h5file.create_soft_link(
            '/group1', 'lgroup3', 'group3')
        if common.verbose:
            print("Relative path link:", lgroup3)
        self.assertIn('/group1/lgroup3', self.h5file)
        self._checkEqualityGroup(self.h5file.root.group1.group3,
                                 self.h5file.root.group1.lgroup3())

    def test06b_relative_path(self):
        """Using soft links with relative paths (./ version)"""

        # Create new group
        self.h5file.create_group('/group1', 'group3')
        # ... and relative link
        lgroup3 = self.h5file.create_soft_link(
            '/group1', 'lgroup3', './group3')
        if common.verbose:
            print("Relative path link:", lgroup3)
        self.assertIn('/group1/lgroup3', self.h5file)
        self._checkEqualityGroup(self.h5file.root.group1.group3,
                                 self.h5file.root.group1.lgroup3())

    def test07_walkNodes(self):
        """Checking `walk_nodes` with `classname` option."""

        links = [node._v_pathname for node in
                 self.h5file.walk_nodes('/', classname="Link")]
        if common.verbose:
            print("detected links (classname='Link'):", links)
        self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1'])
        links = [node._v_pathname for node in
                 self.h5file.walk_nodes('/', classname="SoftLink")]
        if common.verbose:
            print("detected links (classname='SoftLink'):", links)
        self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1'])

    def test08__v_links(self):
        """Checking `Group._v_links`."""

        links = [node for node in self.h5file.root._v_links]
        if common.verbose:
            print("detected links (under root):", links)
        self.assertEqual(len(links), 2)
        links = [node for node in self.h5file.root.group1._v_links]
        if common.verbose:
            print("detected links (under /group1):", links)
        self.assertEqual(links, ['larr1'])

    def test09_link_to_link(self):
        """Checking linked links."""

        # Create a link to another existing link
        lgroup2 = self.h5file.create_soft_link(
            '/', 'lgroup2', '/lgroup1')
        # Dereference it once:
        self.assertIs(lgroup2(), self.h5file.get_node('/lgroup1'))
        if common.verbose:
            print("First dereference is correct:", lgroup2())
        # Dereference it twice:
        self.assertIs(lgroup2()(), self.h5file.get_node('/group1'))
        if common.verbose:
            print("Second dereference is correct:", lgroup2()())

    def test10_copy_link_to_file(self):
        """Checking copying a link to another file."""

        fname = tempfile.mktemp(".h5")
        h5f = tb.open_file(fname, "a")
        h5f.create_array('/', 'arr1', [1, 2])
        h5f.create_group('/', 'group1')
        lgroup1 = self.h5file.root.lgroup1
        lgroup1_ = lgroup1.copy(h5f.root, 'lgroup1')
        self.assertIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup1', h5f)
        self.assertIn(lgroup1_, h5f)
        if common.verbose:
            print("Copied link:", lgroup1_, 'in:', lgroup1_._v_file.filename)
        h5f.close()
        Path(fname).unlink()

    def test11_direct_attribute_access(self):
        """Check direct get/set attributes via link-->target.attribute"""

        larr1 = self.h5file.get_node('/lgroup1/larr1')
        arr1 = self.h5file.get_node('/arr1')
        # get
        self.assertEqual(larr1.shape, (2,))
        self.assertEqual(larr1[:], [1, 2])
        # set
        larr1[0] = -1
        self.assertEqual(arr1[:], [-1, 2])

    def test12_access_child_node_attributes(self):
        """Check get/set attributes via link-->target.child.attribute"""

        lgroup1 = self.h5file.get_node('/lgroup1')
        arr2 = self.h5file.get_node('/group1/arr2')
        # get child attribute
        self.assertEqual(lgroup1.arr2[:], [1, 2, 3])
        # set child attribute
        lgroup1.arr2[0] = -1
        self.assertEqual(arr2[:], [-1, 2, 3])

    def test13_direct_attribute_access_via_chained_softlinks(self):
        """Check get/set access via link2-->link1-->target.child.attribute"""

        self.h5file.get_node('/lgroup1')
        arr2 = self.h5file.get_node('/group1/arr2')
        # multiple chained links
        l_lgroup1 = self.h5file.create_soft_link('/', 'l_lgroup1', '/lgroup1')
        # get child attribute
        self.assertEqual(l_lgroup1.arr2[:], [1, 2, 3])
        # set child attribute
        l_lgroup1.arr2[0] = -1
        self.assertEqual(arr2[:], [-1, 2, 3])

    def test14_child_of_softlink_to_group(self):
        """Create an array whose parent is a softlink to another group"""

        self.h5file.get_node('/group1')
        lgroup1 = self.h5file.get_node('/lgroup1')
        self.h5file.create_array(lgroup1, 'new_arr', obj=[1, 2, 3])
        new_arr2 = self.h5file.get_node('/group1/new_arr')
        self.assertEqual(new_arr2[:], [1, 2, 3])

    def test_str(self):
        s = str(self.h5file)
        self.assertEqual(len(re.findall(r'\(SoftLink\)', s)), 3)
        self.assertEqual(len(re.findall(r'\(dangling\)', s)), 0)

    def test_str_with_dangling_link(self):
        self.h5file.root.group1.arr2.remove()
        s = str(self.h5file)
        self.assertEqual(len(re.findall(r'\(SoftLink\)', s)), 3)
        self.assertEqual(len(re.findall(r'\(dangling\)', s)), 1)


# Test for external links
@common.unittest.skipIf(tb.file._FILE_OPEN_POLICY == 'strict',
                        'FILE_OPEN_POLICY = "strict"')
class ExternalLinkTestCase(common.TempFileMixin, common.PyTablesTestCase):

    def setUp(self):
        super().setUp()

        self.extfname = tempfile.mktemp(".h5")
        self.exth5file = tb.open_file(self.extfname, "w")
        self._createFile()

    def tearDown(self):
        """Remove ``extfname``."""

        extfname = self.extfname
        self.exth5file.close()
        super().tearDown()

        # open_files = tables.file._open_files
        # if self.extfname in open_files:
        #     #assert False
        #     for handler in open_files.get_handlers_by_name(self.extfname):
        #         handler.close()

        Path(extfname).unlink()   # comment this for debugging purposes only

    def _createFile(self):
        self.h5file.create_array('/', 'arr1', [1, 2])
        group1 = self.h5file.create_group('/', 'group1')
        self.h5file.create_array(group1, 'arr2', [1, 2, 3])

        # The external file
        extarr1 = self.exth5file.create_array('/', 'arr1', [1, 2])
        self.assertIsNotNone(extarr1)
        extgroup1 = self.exth5file.create_group('/', 'group1')
        extarr2 = self.exth5file.create_array(extgroup1, 'arr2', [1, 2, 3])

        # Create external links
        lgroup1 = self.h5file.create_external_link(
            '/', 'lgroup1', '%s:/group1' % self.extfname)
        self.assertIsNotNone(lgroup1)
        larr1 = self.h5file.create_external_link(
            group1, 'larr1', '%s:/arr1' % self.extfname)
        self.assertIsNotNone(larr1)
        larr2 = self.h5file.create_external_link('/', 'larr2', extarr2)
        self.assertIsNotNone(larr2)

        # Re-open the external file in 'r'ead-only mode
        self.exth5file.close()
        self.exth5file = tb.open_file(self.extfname, "r")

    def test00_create(self):
        """Creating soft links."""

        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.lgroup1())
        self._checkEqualityLeaf(self.exth5file.root.arr1,
                                self.h5file.root.group1.larr1())
        self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2,
                                self.h5file.root.larr2())

    def test01_open(self):
        """Opening a file with soft links."""

        self._reopen()
        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.lgroup1())
        self._checkEqualityLeaf(self.exth5file.root.arr1,
                                self.h5file.root.group1.larr1())
        self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2,
                                self.h5file.root.larr2())

    def test02_remove(self):
        """Removing an external link."""

        # Re-open the external file in 'a'ppend mode
        self.exth5file.close()
        self.exth5file = tb.open_file(self.extfname, "a")

        # First delete the referred link
        self.exth5file.root.arr1.remove()
        self.assertNotIn('/arr1', self.exth5file)

        # The external link should still be there (but dangling)
        if common.verbose:
            print("Dangling link:", self.h5file.root.group1.larr1)
        self.assertIn('/group1/larr1', self.h5file)

        # Remove the external link itself
        self.h5file.root.group1.larr1.remove()
        self.assertNotIn('/group1/larr1', self.h5file)

    def test03_copy(self):
        """Copying an external link."""

        # Copy the link into another location
        root = self.h5file.root
        lgroup1 = root.lgroup1
        lgroup2 = lgroup1.copy('/', 'lgroup2')
        self.assertIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self.assertIn('lgroup2', root._v_children)
        self.assertIn('lgroup2', root._v_links)
        if common.verbose:
            print("Copied link:", lgroup2)

        # Remove the first link
        lgroup1.remove()
        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test03_overwrite(self):
        """Overwrite an external link."""

        # Copy the link into another location
        root = self.h5file.root
        lgroup1 = root.lgroup1
        lgroup2 = lgroup1.copy('/', 'lgroup2')
        lgroup2 = lgroup1.copy('/', 'lgroup2', overwrite=True)
        self.assertIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self.assertIn('lgroup2', root._v_children)
        self.assertIn('lgroup2', root._v_links)
        if common.verbose:
            print("Copied link:", lgroup2)

        # Remove the first link
        lgroup1.remove()
        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test04_move(self):
        """Moving an external link."""

        # Move the link into another location
        lgroup1 = self.h5file.root.lgroup1
        group2 = self.h5file.create_group('/', 'group2')
        lgroup1.move(group2, 'lgroup2')
        lgroup2 = self.h5file.root.group2.lgroup2
        if common.verbose:
            print("Moved link:", lgroup2)
        self.assertNotIn('/lgroup1', self.h5file)
        self.assertIn('/group2/lgroup2', self.h5file)
        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.group2.lgroup2())

    def test05_rename(self):
        """Renaming an external link."""

        # Rename the link
        lgroup1 = self.h5file.root.lgroup1
        lgroup1.rename('lgroup2')
        lgroup2 = self.h5file.root.lgroup2
        if common.verbose:
            print("Moved link:", lgroup2)
        self.assertNotIn('/lgroup1', self.h5file)
        self.assertIn('/lgroup2', self.h5file)
        self._checkEqualityGroup(self.exth5file.root.group1,
                                 self.h5file.root.lgroup2())

    def test07_walkNodes(self):
        """Checking `walk_nodes` with `classname` option."""

        # Create a new soft link
        self.h5file.create_soft_link('/group1', 'lgroup3', './group3')
        links = [node._v_pathname for node in
                 self.h5file.walk_nodes('/', classname="Link")]
        if common.verbose:
            print("detected links (classname='Link'):", links)
        self.assertEqual(links, ['/larr2', '/lgroup1',
                                 '/group1/larr1', '/group1/lgroup3'])
        links = [node._v_pathname for node in
                 self.h5file.walk_nodes('/', classname="ExternalLink")]
        if common.verbose:
            print("detected links (classname='ExternalLink'):", links)
        self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1'])

    def test08__v_links(self):
        """Checking `Group._v_links`."""

        links = [node for node in self.h5file.root._v_links]
        if common.verbose:
            print("detected links (under root):", links)
        self.assertEqual(len(links), 2)
        links = [node for node in self.h5file.root.group1._v_links]
        if common.verbose:
            print("detected links (under /group1):", links)
        self.assertEqual(links, ['larr1'])

    def test09_umount(self):
        """Checking `umount()` method."""

        link = self.h5file.root.lgroup1
        self.assertIsNone(link.extfile)

        # Dereference a external node (and hence, 'mount' a file)
        enode = link()
        self.assertIsNotNone(enode)
        self.assertIsNotNone(link.extfile)

        # Umount the link
        link.umount()
        self.assertIsNone(link.extfile)

    def test10_copy_link_to_file(self):
        """Checking copying a link to another file."""

        h5fname2 = tempfile.mktemp(".h5")
        try:
            with tb.open_file(h5fname2, "a") as h5file2:
                h5file2.create_array('/', 'arr1', [1, 2])
                h5file2.create_group('/', 'group1')
                lgroup1 = self.h5file.root.lgroup1
                lgroup1_ = lgroup1.copy(h5file2.root, 'lgroup1')
                self.assertIn('/lgroup1', self.h5file)
                self.assertIn('/lgroup1', h5file2)
                self.assertIn(lgroup1_, h5file2)
                if common.verbose:
                    print("Copied link:", lgroup1_, 'in:',
                          lgroup1_._v_file.filename)
        finally:
            if Path(h5fname2).is_file():
                Path(h5fname2).unlink()

    def test11_copy_entire_file_with_hardlink_option(self):
        """Checking copying the entire file (that contains external links)
        in a similar way ptrepack does (with hardlink kwargs activated) """

        h5fname2 = tempfile.mktemp(".h5")
        try:
            with tb.open_file(h5fname2, "a") as h5file2:
                self.h5file.root._f_copy_children(h5file2.root, recursive=True, use_hardlinks=True)
                self.assertIn('/lgroup1', h5file2)
        finally:
            if Path(h5fname2).is_file():
                Path(h5fname2).unlink()


def suite():
    """Return a test suite consisting of all the test cases in the module."""

    theSuite = common.unittest.TestSuite()
    niter = 1
    # common.heavy = 1  # uncomment this only for testing purposes

    for i in range(niter):
        theSuite.addTest(common.unittest.makeSuite(HardLinkTestCase))
        theSuite.addTest(common.unittest.makeSuite(SoftLinkTestCase))
        theSuite.addTest(common.unittest.makeSuite(ExternalLinkTestCase))

    return theSuite


if __name__ == '__main__':
    import sys
    common.parse_argv(sys.argv)
    common.print_versions()
    common.unittest.main(defaultTest='suite')
