"""Test file sequence discovery on disk."""

# "Future" Libraries
from __future__ import print_function

# Standard Libraries
import os
import unittest

# Third Party Libraries
import mock
from builtins import range
from future.utils import lrange

from . import (DirEntry, generate_entries, initialise_mock_scandir_data,
               mock_scandir_deep)
from .. import (__version__, get_parser, get_sequence, get_version, invert,
                validate_frame_sequence)
from ..sequences import FileSequence, FrameChunk, FrameSequence

###############################################################################
# class: TestSeqparseModule


class TestSeqparseModule(unittest.TestCase):
    """Test file discovery on the seqparse module."""

    _test_ext = "exr"
    _test_file_name = "TEST_DIR"
    _test_root = "test_dir"
    _singletons = ["singleton0.jpg", "singleton1.jpg"]

    def setUp(self):
        """Set up the test case."""
        pass

    @mock.patch("seqparse.seqparse.scandir")
    def test_singletons(self, mock_api_call):
        """Seqparse: Test file singleton discovery from disk location."""
        # Expected outputs ...
        output = [os.path.join(self._test_root, x) for x in self._singletons]

        entries = list()
        for file_name in output:
            entries.append(DirEntry(file_name))

        mock_api_call.return_value = iter(entries)

        parser = get_parser()
        parser.scan_path(self._test_root)

        file_names = parser.singletons

        self.assertIn(self._test_root, file_names)
        self.assertEqual(self._test_root, file_names[self._test_root].path)
        self.assertEqual(len(file_names), 1)
        self.assertEqual(
            len(file_names[self._test_root]), len(self._singletons))
        self.assertEqual(
            sorted(self._singletons), sorted(file_names[self._test_root]))

        # Check parser output ...
        self.assertEqual(sorted(map(str, parser.output())), output)

        # Test seqs_only option ...
        self.assertEqual(sorted(parser.output(seqs_only=True)), [])

    @mock.patch("seqparse.seqparse.scandir")
    def test_single_padded_file(self, mock_api_call):
        """Seqparse: Test single padded file sequence discovery."""
        frames = {4: [1]}

        # Expected outputs ...
        frame_seq_output = "0001"
        file_seq_output = ".".join(
            (self._test_file_name, frame_seq_output, self._test_ext))
        final_output = os.path.join(self._test_root, file_seq_output)

        input_entries = generate_entries(
            ext=self._test_ext,
            frames=frames,
            name=self._test_file_name,
            root=self._test_root)

        mock_api_call.return_value = iter(input_entries)

        parser = get_parser()
        parser.scan_path(self._test_root)

        data = parser.sequences

        test_output = list(parser.output())
        self.assertEqual(len(test_output), 1)
        self.assertEqual(str(test_output[0]), final_output)

        # Check the structure of the sequences property.
        self.assertIn(self._test_root, data)
        self.assertEqual(len(data), 1)
        self.assertIn(self._test_file_name, data[self._test_root])
        self.assertEqual(len(data[self._test_root]), 1)

        # Now check the file sequence itself.
        file_seq = data[self._test_root][self._test_file_name]

        test_output = list(file_seq.output())

        self.assertEqual(len(test_output), 1)
        self.assertEqual(str(test_output[0]), final_output)

        self.assertIn(self._test_ext, file_seq)
        self.assertEqual(len(file_seq), 1)
        self.assertTrue(4 in file_seq[self._test_ext])
        self.assertEqual(len(file_seq[self._test_ext]), 1)

        # And finally, the file sequence.
        file_seq = file_seq[self._test_ext][4]

        self.assertEqual(len(file_seq), len(frames[4]))
        self.assertEqual(str(file_seq), final_output)

    @mock.patch("seqparse.seqparse.scandir")
    def test_simple_sequence(self, mock_api_call):
        """Seqparse: Test simple file sequence discovery."""
        frames = {4: [0, 1, 2, 3, 4]}

        # Expected outputs ...
        frame_seq_output = "0000-0004"
        file_seq_output = ".".join(
            (self._test_file_name, frame_seq_output, self._test_ext))
        final_output = os.path.join(self._test_root, file_seq_output)

        input_entries = generate_entries(
            ext=self._test_ext,
            frames=frames,
            name=self._test_file_name,
            root=self._test_root)

        mock_api_call.return_value = iter(input_entries)

        parser = get_parser()
        parser.scan_path(self._test_root)

        data = parser.sequences

        test_output = list(parser.output())
        self.assertEqual(len(test_output), 1)
        self.assertEqual(str(test_output[0]), final_output)

        # Check the structure of the sequences property.
        self.assertIn(self._test_root, data)
        self.assertEqual(len(data), 1)
        self.assertIn(self._test_file_name, data[self._test_root])
        self.assertEqual(len(data[self._test_root]), 1)

        # Now check the file sequence itself.
        file_seq = data[self._test_root][self._test_file_name]

        test_output = list(file_seq.output())

        self.assertEqual(len(test_output), 1)
        self.assertEqual(str(test_output[0]), final_output)

        self.assertIn(self._test_ext, file_seq)
        self.assertEqual(len(file_seq), 1)
        self.assertTrue(4 in file_seq[self._test_ext])
        self.assertEqual(len(file_seq[self._test_ext]), 1)

        # And finally, the file sequence.
        file_seq = file_seq[self._test_ext][4]

        self.assertEqual(len(file_seq), len(frames[4]))
        self.assertEqual(str(file_seq), final_output)

    @mock.patch("seqparse.seqparse.scandir")
    def test_complex_sequence(self, mock_api_call):
        """Seqparse: Test complex file sequence discovery."""
        frames = {
            1: [5, 6, 7, 8, 114, 199, 2000],
            3: [8, 9, 10, 12],
            4: [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 101]
        }

        input_entries = generate_entries(
            ext=self._test_ext,
            frames=frames,
            name=self._test_file_name,
            root=self._test_root)

        # Expected output frame sequences. Note how frames 114, 199 move to the
        # "pad 3" group and 2000 moves to the "pad 4" group!
        output_seqs = {
            1: "5-8",
            3: "008-010,012,114,199",
            4: "0000-0006,0008-0012x2,0101,2000"
        }

        # Expected final output (where "/" is os.sep):
        # test_dir/TEST_DIR.5-8.exr
        # test_dir/TEST_DIR.008-010,012,114,199.exr
        # test_dir/TEST_DIR.0000-0006,0008-0012x2,0101,2000.exr

        mock_api_call.return_value = iter(input_entries)

        parser = get_parser()
        parser.scan_path(self._test_root)

        final_output = list()
        for pad, seq_frames in sorted(output_seqs.items()):
            bits = (self._test_file_name, seq_frames, self._test_ext)
            output_seqs[pad] = os.path.join(self._test_root, ".".join(bits))
            final_output.append(output_seqs[pad])

        data = parser.sequences

        # Check the structure of the sequences property.
        self.assertIn(self._test_root, data)
        self.assertEqual(len(data), 1)
        self.assertIn(self._test_file_name, data[self._test_root])
        self.assertEqual(len(data[self._test_root]), 1)

        # Now check the file sequence itself.
        file_seq = data[self._test_root][self._test_file_name]

        test_output = list(file_seq.output())
        self.assertEqual(len(test_output), 3)
        self.assertEqual(list(map(str, test_output)), final_output)

        self.assertIn(self._test_ext, file_seq)
        self.assertEqual(len(file_seq), 1)
        self.assertEqual(set(file_seq[self._test_ext]), set(output_seqs))

        # And finally, the file sequences.
        for pad in sorted(output_seqs):
            self.assertEqual(output_seqs[pad],
                             str(file_seq[self._test_ext][pad]))

    @mock.patch("seqparse.seqparse.scandir")
    def test_nested_sequences(self, mock_api_call):
        """Seqparse: Test file sequence discovery in nested directories."""
        mock_api_call.side_effect = mock_scandir_deep

        print("\n\n  SEQUENCES\n  ---------")
        initialise_mock_scandir_data(self._test_root)
        parser = get_parser()
        parser.scan_path(self._test_root)
        for seq in parser.output():
            print(" ", seq)

        print("\n  MAX LEVELS\n  ----------")
        for max_levels in range(-1, 4):
            initialise_mock_scandir_data(self._test_root)
            parser = get_parser()
            parser.scan_path(self._test_root, max_levels=max_levels)

            expected_seqs = max_levels + 2
            if max_levels == -1:
                expected_seqs = 5

            seqs = list(parser.output())
            blurb = "  o max_levels == {:d}: {:d} ({:d} expected) entries"
            print(blurb.format(max_levels, len(seqs), expected_seqs))

            for seq in seqs:
                print("    -", seq)
            self.assertEqual(len(seqs), expected_seqs)

        print("\n  MIN LEVELS\n  ----------")
        for min_levels in range(-1, 4):
            initialise_mock_scandir_data(self._test_root)
            parser = get_parser()
            parser.scan_path(self._test_root, min_levels=min_levels)

            expected_seqs = 3 - min_levels
            if min_levels == -1:
                expected_seqs = 5

            seqs = list(parser.output())
            blurb = "  o min_levels == {:d}: {:d} ({:d} expected) entries"
            print(blurb.format(min_levels, len(seqs), expected_seqs))

            for seq in seqs:
                print("    -", seq)
            self.assertEqual(len(seqs), expected_seqs)

        print("")

    def test_valid_frame_sequences(self):
        """Seqparse: Test validity of simple frame ranges."""
        good_frame_seqs = [
            "0001", ",0001", "0001,", "0001-0001", "0001-0001x0",
            "0001-0003x3", "0001,0003", "0001,,0003", "0001-0010",
            "0001-0010x0", "0001-0011x2", "0001-0012x2", "0001-0005,0007-0010",
            "0001-0005x2,0007-0010", "0001-0005,0007-0011x2",
            "0001-0005,0006,0008-0012x2", "0001,0003-0007,0009-0015x2",
            "3,1,5,7", "01-05,03-07"
        ]
        bad_frame_seqs = [
            "-0001", "0001-", "0001x2", "x2", "0001,0003x2", "0001-0005x",
            "0010-0001", "x", ",", ",,", ""
        ]

        print("\n\n  GOOD SEQUENCES\n  --------------")
        for frame_seq in good_frame_seqs:
            output = validate_frame_sequence(frame_seq)
            print('  o {!r} --> {!r}'.format(frame_seq, output))
            self.assertTrue(output)

        print("\n  BAD SEQUENCES\n  -------------")
        for frame_seq in bad_frame_seqs:
            print('  o {!r}'.format(frame_seq))
            self.assertFalse(validate_frame_sequence(frame_seq))

        print("")

    def test_add_file_sequence(self):
        """Seqparse: Test file sequence addition via seqparse.add_file."""
        input_file = ".".join((self._test_file_name, "0005", self._test_ext))
        input_file = os.path.join(self._test_root, input_file)

        # Expected outputs ...
        input_frame_seq = "0000-0004"
        output_frame_seq = "0000-0005"
        input_file_seq = ".".join(
            (self._test_file_name, input_frame_seq, self._test_ext))
        input_file_seq = os.path.join(self._test_root, input_file_seq)
        output_file_seq = ".".join(
            (self._test_file_name, output_frame_seq, self._test_ext))
        output_file_seq = os.path.join(self._test_root, output_file_seq)

        print("\n\n  INPUT FILES\n  -----------")
        print("  o", input_file_seq)
        print("  o", input_file)

        parser = get_parser()
        parser.add_file(input_file_seq)
        parser.add_file(input_file)

        output = list(parser.output())

        print("\n  OUTPUT FILES\n  ------------")
        for line in output:
            print("  o", line)

        print("\n  EXPECTED OUTPUT\n  ---------------")
        print("  o", output_file_seq)
        print("")

        self.assertEqual(len(output), 1)
        self.assertEqual(str(output[0]), output_file_seq)

        input_frame_seq = "0000-0002,,0003-0005"
        input_file_seq = ".".join(
            (self._test_file_name, input_frame_seq, self._test_ext))
        input_file_seq = os.path.join(self._test_root, input_file_seq)

        print("\n  INPUT FILES\n  -----------")
        print("  o", input_file_seq)
        print("  o", input_file)

        parser = get_parser()
        parser.add_file(input_file_seq)
        parser.add_file(input_file)

        output = list(parser.output())

        print("\n  OUTPUT FILES\n  ------------")
        for line in output:
            print("  o", line)

        print("\n  EXPECTED OUTPUT\n  ---------------")
        print("  o", output_file_seq)
        print("")

        self.assertEqual(len(output), 1)
        self.assertEqual(str(output[0]), output_file_seq)

    @mock.patch("seqparse.seqparse.scandir")
    def test_inversion(self, mock_api_call):
        """Seqparse: Test usage of the "missing" option in Seqparse.output."""
        file_path = os.path.join(self._test_root, self._test_file_name)
        chunk_in = FrameChunk(first=1, last=11, step=2, pad=4)
        fseq = FileSequence(
            name=file_path, ext=self._test_ext, frames=chunk_in)

        input_entries = [DirEntry(x) for x in fseq]

        mock_api_call.return_value = input_entries

        chunk_out = FrameChunk(first=2, last=10, step=2, pad=4)
        expected = FileSequence(
            name=file_path, ext=self._test_ext, frames=chunk_out)

        parser = get_parser()
        parser.scan_path(self._test_root)
        inverted = list(parser.output(missing=True))

        self.assertEqual(len(inverted), 1)

        print("\n\n  SEQUENCE\n  --------")
        print("  input files:   ", fseq)
        print("  expected files:", expected)
        print("  inverted files:", inverted[0])

        self.assertEqual(str(inverted[0]), str(expected))

        fseq = FileSequence(
            name=file_path, ext=self._test_ext, frames=[1, 2, 3, 4, 6], pad=4)

        input_entries = [DirEntry(x) for x in fseq]

        mock_api_call.return_value = input_entries

        expected = FileSequence(
            name=file_path, ext=self._test_ext, frames=[5], pad=4)

        parser = get_parser()
        parser.scan_path(self._test_root)
        inverted = list(parser.output(missing=True))

        self.assertEqual(len(inverted), 1)

        print("\n\n  SEQUENCE\n  --------")
        print("  input files:   ", fseq)
        print("  expected files:", expected)
        print("  inverted files:", inverted[0])

        self.assertEqual(str(inverted[0]), str(expected))

    @mock.patch("seqparse.seqparse.scandir")
    def test_scan_options(self, mock_api_call):
        """Seqparse: Make sure scan_options works as expected."""
        frames = {4: (1, 2, 3, 4, 6)}
        input_entries = generate_entries(
            name="test", ext="py", frames=frames, root=self._test_root)

        input_entries.extend(
            generate_entries(
                name=".test", ext="py", frames=frames, root=self._test_root))

        input_entries.append(
            DirEntry(os.path.join(self._test_root, "pony.py")))

        mock_api_call.return_value = input_entries

        parser = get_parser()
        parser.scan_options["stat"] = True
        parser.scan_path(self._test_root)
        output = list(parser.output())
        expected = [
            os.path.join(self._test_root, "test.0001-0004,0006.py"),
            os.path.join(self._test_root, "pony.py")
        ]

        self.assertEqual(len(output), 2)
        self.assertEqual(list(map(str, output)), expected)
        self.assertEqual(output[0].ctime, 1490908340)
        self.assertEqual(output[0].mtime, 1490908305)
        self.assertEqual(output[0].size, 36520)

        parser = get_parser()
        parser.scan_options["all"] = True
        parser.scan_path(self._test_root)
        output = list(parser.output())
        expected = [
            os.path.join(self._test_root, ".test.0001-0004,0006.py"),
            os.path.join(self._test_root, "test.0001-0004,0006.py"),
            os.path.join(self._test_root, "pony.py")
        ]

        self.assertEqual(len(output), 3)
        self.assertEqual(list(map(str, output)), expected)

    def test_api_calls(self):
        """Seqparse: Test API calls at root of module."""
        chunk = FrameChunk(first=1, last=7, step=2, pad=4)
        seq = get_sequence(lrange(1, 8, 2), pad=4)
        self.assertTrue(isinstance(seq, FrameSequence))
        self.assertEqual(str(seq), "0001-0007x2")

        expected = FrameChunk(first=2, last=6, step=2, pad=4)
        inverted = invert(chunk)
        self.assertEqual(str(inverted), str(expected))
        inverted = invert(seq)
        self.assertEqual(str(inverted), str(expected))

        with self.assertRaises(TypeError):
            invert(get_parser())

        self.assertEqual(get_version(), __version__)

[((37, 5, 37, 44), 'mock.patch', 'mock.patch', ({(37, 16, 37, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((68, 5, 68, 44), 'mock.patch', 'mock.patch', ({(68, 16, 68, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((121, 5, 121, 44), 'mock.patch', 'mock.patch', ({(121, 16, 121, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((174, 5, 174, 44), 'mock.patch', 'mock.patch', ({(174, 16, 174, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((237, 5, 237, 44), 'mock.patch', 'mock.patch', ({(237, 16, 237, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((377, 5, 377, 44), 'mock.patch', 'mock.patch', ({(377, 16, 377, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((429, 5, 429, 44), 'mock.patch', 'mock.patch', ({(429, 16, 429, 43): '"""seqparse.seqparse.scandir"""'}, {}), "('seqparse.seqparse.scandir')", False, 'import mock\n'), ((77, 23, 77, 69), 'os.path.join', 'os.path.join', ({(77, 36, 77, 51): 'self._test_root', (77, 53, 77, 68): 'file_seq_output'}, {}), '(self._test_root, file_seq_output)', False, 'import os\n'), ((130, 23, 130, 69), 'os.path.join', 'os.path.join', ({(130, 36, 130, 51): 'self._test_root', (130, 53, 130, 68): 'file_seq_output'}, {}), '(self._test_root, file_seq_output)', False, 'import os\n'), ((250, 26, 250, 38), 'builtins.range', 'range', ({(250, 32, 250, 34): '(-1)', (250, 36, 250, 37): '(4)'}, {}), '(-1, 4)', False, 'from builtins import range\n'), ((268, 26, 268, 38), 'builtins.range', 'range', ({(268, 32, 268, 34): '(-1)', (268, 36, 268, 37): '(4)'}, {}), '(-1, 4)', False, 'from builtins import range\n'), ((318, 21, 318, 62), 'os.path.join', 'os.path.join', ({(318, 34, 318, 49): 'self._test_root', (318, 51, 318, 61): 'input_file'}, {}), '(self._test_root, input_file)', False, 'import os\n'), ((325, 25, 325, 70), 'os.path.join', 'os.path.join', ({(325, 38, 325, 53): 'self._test_root', (325, 55, 325, 69): 'input_file_seq'}, {}), '(self._test_root, input_file_seq)', False, 'import os\n'), ((328, 26, 328, 72), 'os.path.join', 'os.path.join', ({(328, 39, 328, 54): 'self._test_root', (328, 56, 328, 71): 'output_file_seq'}, {}), '(self._test_root, output_file_seq)', False, 'import os\n'), ((354, 25, 354, 70), 'os.path.join', 'os.path.join', ({(354, 38, 354, 53): 'self._test_root', (354, 55, 354, 69): 'input_file_seq'}, {}), '(self._test_root, input_file_seq)', False, 'import os\n'), ((380, 20, 380, 71), 'os.path.join', 'os.path.join', ({(380, 33, 380, 48): 'self._test_root', (380, 50, 380, 70): 'self._test_file_name'}, {}), '(self._test_root, self._test_file_name)', False, 'import os\n'), ((41, 18, 41, 50), 'os.path.join', 'os.path.join', ({(41, 31, 41, 46): 'self._test_root', (41, 48, 41, 49): 'x'}, {}), '(self._test_root, x)', False, 'import os\n'), ((450, 12, 450, 67), 'os.path.join', 'os.path.join', ({(450, 25, 450, 40): 'self._test_root', (450, 42, 450, 66): '"""test.0001-0004,0006.py"""'}, {}), "(self._test_root, 'test.0001-0004,0006.py')", False, 'import os\n'), ((451, 12, 451, 52), 'os.path.join', 'os.path.join', ({(451, 25, 451, 40): 'self._test_root', (451, 42, 451, 51): '"""pony.py"""'}, {}), "(self._test_root, 'pony.py')", False, 'import os\n'), ((465, 12, 465, 68), 'os.path.join', 'os.path.join', ({(465, 25, 465, 40): 'self._test_root', (465, 42, 465, 67): '""".test.0001-0004,0006.py"""'}, {}), "(self._test_root, '.test.0001-0004,0006.py')", False, 'import os\n'), ((466, 12, 466, 67), 'os.path.join', 'os.path.join', ({(466, 25, 466, 40): 'self._test_root', (466, 42, 466, 66): '"""test.0001-0004,0006.py"""'}, {}), "(self._test_root, 'test.0001-0004,0006.py')", False, 'import os\n'), ((467, 12, 467, 52), 'os.path.join', 'os.path.join', ({(467, 25, 467, 40): 'self._test_root', (467, 42, 467, 51): '"""pony.py"""'}, {}), "(self._test_root, 'pony.py')", False, 'import os\n'), ((476, 27, 476, 42), 'future.utils.lrange', 'lrange', ({(476, 34, 476, 35): '1', (476, 37, 476, 38): '8', (476, 40, 476, 41): '2'}, {}), '(1, 8, 2)', False, 'from future.utils import lrange\n'), ((441, 21, 441, 61), 'os.path.join', 'os.path.join', ({(441, 34, 441, 49): 'self._test_root', (441, 51, 441, 60): '"""pony.py"""'}, {}), "(self._test_root, 'pony.py')", False, 'import os\n')]