"""
Tests for pathlib.types._JoinablePath
"""

import unittest
import threading
from test.support import threading_helper

from .support import is_pypi
from .support.lexical_path import LexicalPath

if is_pypi:
    from pathlib_abc import _PathParser, _JoinablePath
else:
    from pathlib.types import _PathParser, _JoinablePath


class JoinTestBase:
    def test_is_joinable(self):
        p = self.cls()
        self.assertIsInstance(p, _JoinablePath)

    def test_parser(self):
        self.assertIsInstance(self.cls.parser, _PathParser)

    def test_constructor(self):
        P = self.cls
        p = P('a')
        self.assertIsInstance(p, P)
        P()
        P('a', 'b', 'c')
        P('/a', 'b', 'c')
        P('a/b/c')
        P('/a/b/c')

    def test_with_segments(self):
        class P(self.cls):
            def __init__(self, *pathsegments, session_id):
                super().__init__(*pathsegments)
                self.session_id = session_id

            def with_segments(self, *pathsegments):
                return type(self)(*pathsegments, session_id=self.session_id)
        p = P('foo', 'bar', session_id=42)
        self.assertEqual(42, (p / 'foo').session_id)
        self.assertEqual(42, ('foo' / p).session_id)
        self.assertEqual(42, p.joinpath('foo').session_id)
        self.assertEqual(42, p.with_name('foo').session_id)
        self.assertEqual(42, p.with_stem('foo').session_id)
        self.assertEqual(42, p.with_suffix('.foo').session_id)
        self.assertEqual(42, p.with_segments('foo').session_id)
        self.assertEqual(42, p.parent.session_id)
        for parent in p.parents:
            self.assertEqual(42, parent.session_id)

    def test_join(self):
        P = self.cls
        sep = self.cls.parser.sep
        p = P(f'a{sep}b')
        pp = p.joinpath('c')
        self.assertEqual(pp, P(f'a{sep}b{sep}c'))
        self.assertIs(type(pp), type(p))
        pp = p.joinpath('c', 'd')
        self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
        pp = p.joinpath(f'{sep}c')
        self.assertEqual(pp, P(f'{sep}c'))

    def test_div(self):
        # Basically the same as joinpath().
        P = self.cls
        sep = self.cls.parser.sep
        p = P(f'a{sep}b')
        pp = p / 'c'
        self.assertEqual(pp, P(f'a{sep}b{sep}c'))
        self.assertIs(type(pp), type(p))
        pp = p / f'c{sep}d'
        self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
        pp = p / 'c' / 'd'
        self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
        pp = 'c' / p / 'd'
        self.assertEqual(pp, P(f'c{sep}a{sep}b{sep}d'))
        pp = p/ f'{sep}c'
        self.assertEqual(pp, P(f'{sep}c'))

    def test_full_match(self):
        P = self.cls
        # Simple relative pattern.
        self.assertTrue(P('b.py').full_match('b.py'))
        self.assertFalse(P('a/b.py').full_match('b.py'))
        self.assertFalse(P('/a/b.py').full_match('b.py'))
        self.assertFalse(P('a.py').full_match('b.py'))
        self.assertFalse(P('b/py').full_match('b.py'))
        self.assertFalse(P('/a.py').full_match('b.py'))
        self.assertFalse(P('b.py/c').full_match('b.py'))
        # Wildcard relative pattern.
        self.assertTrue(P('b.py').full_match('*.py'))
        self.assertFalse(P('a/b.py').full_match('*.py'))
        self.assertFalse(P('/a/b.py').full_match('*.py'))
        self.assertFalse(P('b.pyc').full_match('*.py'))
        self.assertFalse(P('b./py').full_match('*.py'))
        self.assertFalse(P('b.py/c').full_match('*.py'))
        # Multi-part relative pattern.
        self.assertTrue(P('ab/c.py').full_match('a*/*.py'))
        self.assertFalse(P('/d/ab/c.py').full_match('a*/*.py'))
        self.assertFalse(P('a.py').full_match('a*/*.py'))
        self.assertFalse(P('/dab/c.py').full_match('a*/*.py'))
        self.assertFalse(P('ab/c.py/d').full_match('a*/*.py'))
        # Absolute pattern.
        self.assertTrue(P('/b.py').full_match('/*.py'))
        self.assertFalse(P('b.py').full_match('/*.py'))
        self.assertFalse(P('a/b.py').full_match('/*.py'))
        self.assertFalse(P('/a/b.py').full_match('/*.py'))
        # Multi-part absolute pattern.
        self.assertTrue(P('/a/b.py').full_match('/a/*.py'))
        self.assertFalse(P('/ab.py').full_match('/a/*.py'))
        self.assertFalse(P('/a/b/c.py').full_match('/a/*.py'))
        # Multi-part glob-style pattern.
        self.assertTrue(P('a').full_match('**'))
        self.assertTrue(P('c.py').full_match('**'))
        self.assertTrue(P('a/b/c.py').full_match('**'))
        self.assertTrue(P('/a/b/c.py').full_match('**'))
        self.assertTrue(P('/a/b/c.py').full_match('/**'))
        self.assertTrue(P('/a/b/c.py').full_match('/a/**'))
        self.assertTrue(P('/a/b/c.py').full_match('**/*.py'))
        self.assertTrue(P('/a/b/c.py').full_match('/**/*.py'))
        self.assertTrue(P('/a/b/c.py').full_match('/a/**/*.py'))
        self.assertTrue(P('/a/b/c.py').full_match('/a/b/**/*.py'))
        self.assertTrue(P('/a/b/c.py').full_match('/**/**/**/**/*.py'))
        self.assertFalse(P('c.py').full_match('**/a.py'))
        self.assertFalse(P('c.py').full_match('c/**'))
        self.assertFalse(P('a/b/c.py').full_match('**/a'))
        self.assertFalse(P('a/b/c.py').full_match('**/a/b'))
        self.assertFalse(P('a/b/c.py').full_match('**/a/b/c'))
        self.assertFalse(P('a/b/c.py').full_match('**/a/b/c.'))
        self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**'))
        self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**'))
        self.assertFalse(P('a/b/c.py').full_match('/a/b/c.py/**'))
        self.assertFalse(P('a/b/c.py').full_match('/**/a/b/c.py'))
        # Matching against empty path
        self.assertFalse(P('').full_match('*'))
        self.assertTrue(P('').full_match('**'))
        self.assertFalse(P('').full_match('**/*'))
        # Matching with empty pattern
        self.assertTrue(P('').full_match(''))
        self.assertTrue(P('.').full_match('.'))
        self.assertFalse(P('/').full_match(''))
        self.assertFalse(P('/').full_match('.'))
        self.assertFalse(P('foo').full_match(''))
        self.assertFalse(P('foo').full_match('.'))

    def test_parts(self):
        # `parts` returns a tuple.
        sep = self.cls.parser.sep
        P = self.cls
        p = P(f'a{sep}b')
        parts = p.parts
        self.assertEqual(parts, ('a', 'b'))
        # When the path is absolute, the anchor is a separate part.
        p = P(f'{sep}a{sep}b')
        parts = p.parts
        self.assertEqual(parts, (sep, 'a', 'b'))

    @threading_helper.requires_working_threading()
    def test_parts_multithreaded(self):
        P = self.cls

        NUM_THREADS = 10
        NUM_ITERS = 10

        for _ in range(NUM_ITERS):
            b = threading.Barrier(NUM_THREADS)
            path = P('a') / 'b' / 'c' / 'd' / 'e'
            expected = ('a', 'b', 'c', 'd', 'e')

            def check_parts():
                b.wait()
                self.assertEqual(path.parts, expected)

            threads = [threading.Thread(target=check_parts) for _ in range(NUM_THREADS)]
            with threading_helper.start_threads(threads):
                pass

    def test_parent(self):
        # Relative
        P = self.cls
        p = P('a/b/c')
        self.assertEqual(p.parent, P('a/b'))
        self.assertEqual(p.parent.parent, P('a'))
        self.assertEqual(p.parent.parent.parent, P(''))
        self.assertEqual(p.parent.parent.parent.parent, P(''))
        # Anchored
        p = P('/a/b/c')
        self.assertEqual(p.parent, P('/a/b'))
        self.assertEqual(p.parent.parent, P('/a'))
        self.assertEqual(p.parent.parent.parent, P('/'))
        self.assertEqual(p.parent.parent.parent.parent, P('/'))

    def test_parents(self):
        # Relative
        P = self.cls
        p = P('a/b/c')
        par = p.parents
        self.assertEqual(len(par), 3)
        self.assertEqual(par[0], P('a/b'))
        self.assertEqual(par[1], P('a'))
        self.assertEqual(par[2], P(''))
        self.assertEqual(par[-1], P(''))
        self.assertEqual(par[-2], P('a'))
        self.assertEqual(par[-3], P('a/b'))
        self.assertEqual(par[0:1], (P('a/b'),))
        self.assertEqual(par[:2], (P('a/b'), P('a')))
        self.assertEqual(par[:-1], (P('a/b'), P('a')))
        self.assertEqual(par[1:], (P('a'), P('')))
        self.assertEqual(par[::2], (P('a/b'), P('')))
        self.assertEqual(par[::-1], (P(''), P('a'), P('a/b')))
        self.assertEqual(list(par), [P('a/b'), P('a'), P('')])
        with self.assertRaises(IndexError):
            par[-4]
        with self.assertRaises(IndexError):
            par[3]
        with self.assertRaises(TypeError):
            par[0] = p
        # Anchored
        p = P('/a/b/c')
        par = p.parents
        self.assertEqual(len(par), 3)
        self.assertEqual(par[0], P('/a/b'))
        self.assertEqual(par[1], P('/a'))
        self.assertEqual(par[2], P('/'))
        self.assertEqual(par[-1], P('/'))
        self.assertEqual(par[-2], P('/a'))
        self.assertEqual(par[-3], P('/a/b'))
        self.assertEqual(par[0:1], (P('/a/b'),))
        self.assertEqual(par[:2], (P('/a/b'), P('/a')))
        self.assertEqual(par[:-1], (P('/a/b'), P('/a')))
        self.assertEqual(par[1:], (P('/a'), P('/')))
        self.assertEqual(par[::2], (P('/a/b'), P('/')))
        self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b')))
        self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')])
        with self.assertRaises(IndexError):
            par[-4]
        with self.assertRaises(IndexError):
            par[3]

    def test_anchor(self):
        P = self.cls
        sep = self.cls.parser.sep
        self.assertEqual(P('').anchor, '')
        self.assertEqual(P(f'a{sep}b').anchor, '')
        self.assertEqual(P(sep).anchor, sep)
        self.assertEqual(P(f'{sep}a{sep}b').anchor, sep)

    def test_name(self):
        P = self.cls
        self.assertEqual(P('').name, '')
        self.assertEqual(P('/').name, '')
        self.assertEqual(P('a/b').name, 'b')
        self.assertEqual(P('/a/b').name, 'b')
        self.assertEqual(P('a/b.py').name, 'b.py')
        self.assertEqual(P('/a/b.py').name, 'b.py')

    def test_suffix(self):
        P = self.cls
        self.assertEqual(P('').suffix, '')
        self.assertEqual(P('.').suffix, '')
        self.assertEqual(P('..').suffix, '')
        self.assertEqual(P('/').suffix, '')
        self.assertEqual(P('a/b').suffix, '')
        self.assertEqual(P('/a/b').suffix, '')
        self.assertEqual(P('/a/b/.').suffix, '')
        self.assertEqual(P('a/b.py').suffix, '.py')
        self.assertEqual(P('/a/b.py').suffix, '.py')
        self.assertEqual(P('a/.hgrc').suffix, '')
        self.assertEqual(P('/a/.hgrc').suffix, '')
        self.assertEqual(P('a/.hg.rc').suffix, '.rc')
        self.assertEqual(P('/a/.hg.rc').suffix, '.rc')
        self.assertEqual(P('a/b.tar.gz').suffix, '.gz')
        self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
        self.assertEqual(P('a/trailing.dot.').suffix, '.')
        self.assertEqual(P('/a/trailing.dot.').suffix, '.')
        self.assertEqual(P('a/..d.o.t..').suffix, '.')
        self.assertEqual(P('a/inn.er..dots').suffix, '.dots')
        self.assertEqual(P('photo').suffix, '')
        self.assertEqual(P('photo.jpg').suffix, '.jpg')

    def test_suffixes(self):
        P = self.cls
        self.assertEqual(P('').suffixes, [])
        self.assertEqual(P('.').suffixes, [])
        self.assertEqual(P('/').suffixes, [])
        self.assertEqual(P('a/b').suffixes, [])
        self.assertEqual(P('/a/b').suffixes, [])
        self.assertEqual(P('/a/b/.').suffixes, [])
        self.assertEqual(P('a/b.py').suffixes, ['.py'])
        self.assertEqual(P('/a/b.py').suffixes, ['.py'])
        self.assertEqual(P('a/.hgrc').suffixes, [])
        self.assertEqual(P('/a/.hgrc').suffixes, [])
        self.assertEqual(P('a/.hg.rc').suffixes, ['.rc'])
        self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc'])
        self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz'])
        self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
        self.assertEqual(P('a/trailing.dot.').suffixes, ['.dot', '.'])
        self.assertEqual(P('/a/trailing.dot.').suffixes, ['.dot', '.'])
        self.assertEqual(P('a/..d.o.t..').suffixes, ['.o', '.t', '.', '.'])
        self.assertEqual(P('a/inn.er..dots').suffixes, ['.er', '.', '.dots'])
        self.assertEqual(P('photo').suffixes, [])
        self.assertEqual(P('photo.jpg').suffixes, ['.jpg'])

    def test_stem(self):
        P = self.cls
        self.assertEqual(P('..').stem, '..')
        self.assertEqual(P('').stem, '')
        self.assertEqual(P('/').stem, '')
        self.assertEqual(P('a/b').stem, 'b')
        self.assertEqual(P('a/b.py').stem, 'b')
        self.assertEqual(P('a/.hgrc').stem, '.hgrc')
        self.assertEqual(P('a/.hg.rc').stem, '.hg')
        self.assertEqual(P('a/b.tar.gz').stem, 'b.tar')
        self.assertEqual(P('a/trailing.dot.').stem, 'trailing.dot')
        self.assertEqual(P('a/..d.o.t..').stem, '..d.o.t.')
        self.assertEqual(P('a/inn.er..dots').stem, 'inn.er.')
        self.assertEqual(P('photo').stem, 'photo')
        self.assertEqual(P('photo.jpg').stem, 'photo')

    def test_with_name(self):
        P = self.cls
        self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml'))
        self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml'))
        self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml'))
        self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
        self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
        self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
        self.assertRaises(ValueError, P('a/b').with_name, '/c')
        self.assertRaises(ValueError, P('a/b').with_name, 'c/')
        self.assertRaises(ValueError, P('a/b').with_name, 'c/d')

    def test_with_stem(self):
        P = self.cls
        self.assertEqual(P('a/b').with_stem('d'), P('a/d'))
        self.assertEqual(P('/a/b').with_stem('d'), P('/a/d'))
        self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py'))
        self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py'))
        self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz'))
        self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d.'))
        self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d.'))
        self.assertRaises(ValueError, P('foo.gz').with_stem, '')
        self.assertRaises(ValueError, P('/a/b/foo.gz').with_stem, '')
        self.assertRaises(ValueError, P('a/b').with_stem, '/c')
        self.assertRaises(ValueError, P('a/b').with_stem, 'c/')
        self.assertRaises(ValueError, P('a/b').with_stem, 'c/d')

    def test_with_suffix(self):
        P = self.cls
        self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz'))
        self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz'))
        self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz'))
        self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz'))
        # Stripping suffix.
        self.assertEqual(P('a/b.py').with_suffix(''), P('a/b'))
        self.assertEqual(P('/a/b').with_suffix(''), P('/a/b'))
        # Single dot
        self.assertEqual(P('a/b').with_suffix('.'), P('a/b.'))
        self.assertEqual(P('/a/b').with_suffix('.'), P('/a/b.'))
        self.assertEqual(P('a/b.py').with_suffix('.'), P('a/b.'))
        self.assertEqual(P('/a/b.py').with_suffix('.'), P('/a/b.'))
        # Path doesn't have a "filename" component.
        self.assertRaises(ValueError, P('').with_suffix, '.gz')
        self.assertRaises(ValueError, P('/').with_suffix, '.gz')
        # Invalid suffix.
        self.assertRaises(ValueError, P('a/b').with_suffix, 'gz')
        self.assertRaises(ValueError, P('a/b').with_suffix, '/')
        self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz')
        self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d')
        self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d')
        self.assertRaises(ValueError, P('a/b').with_suffix, './.d')
        self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.')
        self.assertRaises(TypeError, P('a/b').with_suffix, None)

    def test_relative_to(self):
        P = self.cls
        p = P('a/b')
        self.assertEqual(p.relative_to(P('')), P('a', 'b'))
        self.assertEqual(p.relative_to(P('a')), P('b'))
        self.assertEqual(p.relative_to(P('a/b')), P(''))
        self.assertEqual(p.relative_to(P(''), walk_up=True), P('a', 'b'))
        self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b'))
        self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P(''))
        self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('..', 'b'))
        self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..'))
        self.assertEqual(p.relative_to(P('c'), walk_up=True), P('..', 'a', 'b'))
        self.assertRaises(ValueError, p.relative_to, P('c'))
        self.assertRaises(ValueError, p.relative_to, P('a/b/c'))
        self.assertRaises(ValueError, p.relative_to, P('a/c'))
        self.assertRaises(ValueError, p.relative_to, P('/a'))
        self.assertRaises(ValueError, p.relative_to, P('../a'))
        self.assertRaises(ValueError, p.relative_to, P('a/..'))
        self.assertRaises(ValueError, p.relative_to, P('/a/..'))
        self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True)
        self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True)
        self.assertRaises(ValueError, p.relative_to, P('../a'), walk_up=True)
        self.assertRaises(ValueError, p.relative_to, P('a/..'), walk_up=True)
        self.assertRaises(ValueError, p.relative_to, P('/a/..'), walk_up=True)
        class Q(self.cls):
            __eq__ = object.__eq__
            __hash__ = object.__hash__
        q = Q('a/b')
        self.assertTrue(q.relative_to(q))
        self.assertRaises(ValueError, q.relative_to, Q(''))
        self.assertRaises(ValueError, q.relative_to, Q('a'))
        self.assertRaises(ValueError, q.relative_to, Q('a'), walk_up=True)
        self.assertRaises(ValueError, q.relative_to, Q('a/b'))
        self.assertRaises(ValueError, q.relative_to, Q('c'))

    def test_is_relative_to(self):
        P = self.cls
        p = P('a/b')
        self.assertTrue(p.is_relative_to(P('')))
        self.assertTrue(p.is_relative_to(P('a')))
        self.assertTrue(p.is_relative_to(P('a/b')))
        self.assertFalse(p.is_relative_to(P('c')))
        self.assertFalse(p.is_relative_to(P('a/b/c')))
        self.assertFalse(p.is_relative_to(P('a/c')))
        self.assertFalse(p.is_relative_to(P('/a')))
        class Q(self.cls):
            __eq__ = object.__eq__
            __hash__ = object.__hash__
        q = Q('a/b')
        self.assertTrue(q.is_relative_to(q))
        self.assertFalse(q.is_relative_to(Q('')))
        self.assertFalse(q.is_relative_to(Q('a')))
        self.assertFalse(q.is_relative_to(Q('a/b')))
        self.assertFalse(q.is_relative_to(Q('c')))


class LexicalPathJoinTest(JoinTestBase, unittest.TestCase):
    cls = LexicalPath


if not is_pypi:
    from pathlib import PurePath, Path

    class PurePathJoinTest(JoinTestBase, unittest.TestCase):
        cls = PurePath

    class PathJoinTest(JoinTestBase, unittest.TestCase):
        cls = Path


if __name__ == "__main__":
    unittest.main()
