# -*- coding: utf-8 -*-

########################################################################
#
# License: BSD
# Created: 2009-11-24
# Author: Francesc Alted - faltet@pytables.org
#
# $Id$
#
########################################################################

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

import os
import re
import tempfile

import tables
from tables.tests import common
from tables.tests.common import unittest
from tables.tests.common import PyTablesTestCase as TestCase


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

    def setUp(self):
        super(HardLinkTestCase, self).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, TestCase):

    def setUp(self):
        super(SoftLinkTestCase, self).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 = tables.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()
        os.remove(fname)

    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"""

        lgroup1 = 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"""

        group1 = self.h5file.get_node('/group1')
        lgroup1 = self.h5file.get_node('/lgroup1')
        new_arr = 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
@unittest.skipIf(tables.file._FILE_OPEN_POLICY == 'strict',
                 'FILE_OPEN_POLICY = "strict"')
class ExternalLinkTestCase(common.TempFileMixin, TestCase):

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

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

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

        extfname = self.extfname
        self.exth5file.close()
        super(ExternalLinkTestCase, self).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()

        os.remove(extfname)   # 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 = tables.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 = tables.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 tables.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 os.path.exists(h5fname2):
                os.remove(h5fname2)


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

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

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

    return theSuite


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


## Local Variables:
## mode: python
## py-indent-offset: 4
## tab-width: 4
## fill-column: 72
## End:
