#!/usr/bin/env python3
# encoding: utf-8

# Unit tests for decopy
# Copyright (C) 2016 Margarita Manterola <marga@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

import unittest

from decopy import cmdoptions
from decopy import tree
from decopy import dep5
from decopy.datatypes import CopyrightHolder, YearRange


class TestFileInfo(unittest.TestCase):

    def setUp(self):
        # Create one root with one dir with one file
        self.r = tree.RootInfo(root='root')
        self.d = tree.DirInfo(name='dir', parent=self.r)
        self.f = tree.FileInfo(name='file', parent=self.d)

    def testFullname(self):
        self.assertEqual(self.r.fullname, '')
        self.assertEqual(self.d.fullname, 'dir')
        self.assertEqual(self.f.fullname, 'dir/file')

    def testGetLicenses(self):
        # Set a license for the directory
        self.d.add_licenses({'GPL'}, 'COPYING')
        # The license for the file should be the one from the directory
        self.assertEqual(self.f.get_licenses(), {'GPL'})

        # Set a different license for the file
        self.f.add_licenses({'BSD'})
        # Now the license is the one for the file
        self.assertEqual(self.f.get_licenses(), {'BSD'})

        # Add a second license to the file
        self.f.add_licenses({'PHP'})
        # Now it should have both licenses
        self.assertEqual(self.f.get_licenses(), {'BSD', 'PHP'})

    def testSplittingPath(self):
        # Set a license for the directory
        self.d.add_licenses({'GPL'}, 'COPYING')
        # Create a child directory with a different license
        self.o = tree.DirInfo(name='other', parent=self.d)
        self.o.add_licenses({'BSD'}, 'COPYING')

        # First test with splitting on license
        options = cmdoptions.process_options(['--no-progress', '--split-on-license'])
        self.assertEqual(self.d.get_splitting_path(options), 'dir')
        self.assertEqual(self.o.get_splitting_path(options), 'dir/other')

        # Then test without splitting on license
        options = cmdoptions.process_options(['--no-progress', '--no-split-on-license'])
        self.assertEqual(self.d.get_splitting_path(options), '')
        self.assertEqual(self.o.get_splitting_path(options), '')

        # Create a debian directory, with another license
        self.deb = tree.DirInfo(name='debian', parent=self.r)
        self.deb.add_licenses({'LGPL'}, 'debian/copyright')
        options = cmdoptions.process_options(
            ['--no-progress', '--split-debian', '--no-split-on-license'])
        self.assertEqual(self.deb.get_splitting_path(options), 'debian')

        options = cmdoptions.process_options(
            ['--no-progress', '--split-debian', '--split-on-license'])
        self.assertEqual(self.deb.get_splitting_path(options), 'debian')

        options = cmdoptions.process_options(
            ['--no-progress', '--no-split-debian', '--split-on-license'])
        self.assertEqual(self.deb.get_splitting_path(options), 'debian')

        options = cmdoptions.process_options(
            ['--no-progress', '--no-split-debian', '--no-split-on-license'])
        self.assertEqual(self.deb.get_splitting_path(options), '')

    def testTally(self):
        self.assertEqual(self.f.get_first_untallied(), self.r)
        self.d.tally()
        self.assertEqual(self.f.get_first_untallied(), self.f)

    def testIncluded(self):
        self.assertEqual(self.f.included, False)
        # Remove the cache
        self.f.included = None
        self.d.included = True
        self.assertEqual(self.f.included, True)


class TestRootInfo(unittest.TestCase):

    def _check_expected_files(self, filetree, expected_files):
        '''Check that the parsed filetree has the expected files.'''
        self.longMessage = False
        for filename in expected_files:
            with self.subTest(msg='Checking "{}"'.format(filename)):
                try:
                    name = filetree[filename].fullname
                except IndexError:
                    name = ''
                self.assertEqual(name, filename,
                                 msg='missing file: "{}"'.format(filename))

        for fileinfo in filetree:
            with self.subTest(msg='Checking "{}"'.format(fileinfo.fullname)):
                self.assertTrue(
                    fileinfo.fullname in expected_files,
                    msg='unexpected file: "{}"'.format(fileinfo.fullname))
        self.longMessage = True

    def testSimpleTree(self):
        '''Parses a tree with licensed files (testdata/one).'''

        options = cmdoptions.process_options(
            ['--no-progress', '--root', 'tests/testdata/one'])
        expected_files = {
            '': 'Inherited(GPL-2+)',
            'COPYING': 'GPL-2+',
            'art.txt': 'Artistic-1',
            'bsd.txt': 'BSD-3-clause',
            'empty.txt': 'Inherited(GPL-2+)',
            'no.txt': 'Inherited(GPL-2+)',
            'paren.txt': 'GPL-3+',
        }

        # First build the tree and check that it has the files and nothing else
        filetree = tree.RootInfo.build(options)
        self._check_expected_files(filetree, expected_files)

        # Now process the licenses
        filetree.process(options)

        # Check that the licenses match our expectations
        for filename, license_ in expected_files.items():
            with self.subTest(msg='Checking "{}"'.format(filename)):
                self.assertEqual(
                    filetree[filename].get_license_key(), license_,
                    msg='license mismatch for: "{}"'.format(filename))

    def testCopyrightedTree(self):
        '''Parses a tree with licensed & copyrighted files (testdata/two).'''

        options = cmdoptions.process_options(
            ['--no-progress', '--root', 'tests/testdata/two'])
        expected_files = {
            '': ['Inherited(GPL-2+)', ()],
            'COPYING': ['GPL-2+', ()],
            'art.txt': ['Artistic-1', ('Jane Doe',)],
            'bsd.txt': ['BSD-3-clause', ('John Doe',)],
            'empty.txt': ['Inherited(GPL-2+)', ()],
            'no.txt': ['Inherited(GPL-2+)', ('Juan Pérez', 'Juana López')],
            'docbook.txt': ['Inherited(GPL-2+)', ()],
        }

        # First build the tree and check that it has the files and nothing else
        filetree = tree.RootInfo.build(options)
        self._check_expected_files(filetree, expected_files)

        # Now process the licenses
        filetree.process(options)

        # Check that the licenses and copyrights match our expectations
        for filename, values in expected_files.items():
            license_, copyright_ = values
            with self.subTest(msg='Checking "{}"'.format(filename)):
                self.assertEqual(
                    filetree[filename].get_license_key(), license_,
                    msg='license mismatch for: "{}"'.format(filename))

                self.assertEqual(
                    filetree[filename].get_copyright_key(),
                    tuple(sorted(copyright_)),
                    msg='copyright mismatch for: "{}"'.format(filename))

    def testDebianizedTree(self):
        '''Parses a tree that contains a debian/copyright (testdata/three).'''

        options = cmdoptions.process_options(
            ['--no-progress', '--root', 'tests/testdata/three'])
        expected_files = {
            '': ['Inherited(GPL-2+)', ()],
            'COPYING': ['GPL-2+', ()],
            'src': ['Inherited(GPL-2+)', ()],
            'src/do-something.m': ['MIT/X11', ('clara@example.com',)],
            'src/do-something.py': ['MIT/X11', ('clara@example.com',)],
            'src/main.py': ['GPL-2+', ('janedoe@example.com',)],
            'src/module.py': ['GPL-2+', ('janedoe@example.com',)],
            'acinclude.m4': ['GPL-2+', ('Free Software Foundation, Inc',)],
            'data': ['Inherited(GPL-2+)', ()],
            'data/file.txt': ['MIT/X11', ('clara@example.com',)],
            'debian': ['Inherited(GPL-2+)', ()],
            'debian/rules': ['LGPL-2+', ()],
            'debian/compat': ['LGPL-2+', ()],
            'debian/copyright': ['LGPL-2+', ()],
        }

        # First build the tree and check that it has the files and nothing else
        filetree = tree.RootInfo.build(options)
        self._check_expected_files(filetree, expected_files)

        # Instantiate and process the copyright
        copyright_ = dep5.Copyright.build(filetree, options)
        copyright_.process(filetree)

        # Now process the licenses in the filetree
        filetree.process(options)

        # Check that the licenses and copyrights match our expectations
        for filename, values in expected_files.items():
            license_, copyright_ = values
            with self.subTest(msg='Checking "{}"'.format(filename)):
                self.assertEqual(
                    filetree[filename].get_license_key(), license_,
                    msg='license mismatch for: "{}"'.format(filename))

                self.assertEqual(
                    filetree[filename].get_copyright_key(),
                    tuple(sorted(copyright_)),
                    msg='copyright mismatch for: "{}"'.format(filename))

    def testTreeExclude(self):
        '''Parse a tree with an exclusion expresion (testdata/three).'''

        options = cmdoptions.process_options(
            ['--no-progress', '--root', 'tests/testdata/three', '-X', 'src.*'])
        expected_files = {
            '': ['Inherited(GPL-2+)', ()],
            'COPYING': ['GPL-2+', ()],
            'src': ['Inherited(GPL-2+)', ()],
            'src/do-something.m': ['Inherited(GPL-2+)', ()],
            'src/do-something.py': ['Inherited(GPL-2+)', ()],
            'src/main.py': ['Inherited(GPL-2+)', ()],
            'src/module.py': ['Inherited(GPL-2+)', ()],
            'acinclude.m4': ['GPL-2+', ('Free Software Foundation, Inc',)],
            'data': ['Inherited(GPL-2+)', ()],
            'data/file.txt': ['MIT/X11', ('clara@example.com',)],
            'debian': ['Inherited(GPL-2+)', ()],
            'debian/rules': ['Inherited(GPL-2+)', ()],
            'debian/compat': ['Inherited(GPL-2+)', ()],
            'debian/copyright': ['Inherited(GPL-2+)', ()],
        }

        # First build the tree and check that it has the files and nothing else
        filetree = tree.RootInfo.build(options)
        self._check_expected_files(filetree, expected_files)

        # Don't process the debian/copyright, so that we can test just the
        # parsed tree.

        # Now process the licenses in the filetree. Excluded files won't get
        # processed, so they get inherited values.
        filetree.process(options)

        # Check that the licenses and copyrights match our expectations
        for filename, values in expected_files.items():
            license_, copyright_ = values
            with self.subTest(msg='Checking "{}"'.format(filename)):
                self.assertEqual(
                    filetree[filename].get_license_key(), license_,
                    msg='license mismatch for: "{}"'.format(filename))

                self.assertEqual(
                    filetree[filename].get_copyright_key(),
                    tuple(sorted(copyright_)),
                    msg='copyright mismatch for: "{}"'.format(filename))


class TestFileGroup(unittest.TestCase):

    def setUp(self):
        self.group = tree.FileGroup()

    def testGetPatternsEmptyGroup(self):
        self.assertEqual(self.group.get_patterns(), [])

    def testGetPatternsOneFile(self):
        fileinfo = tree.FileInfo(None, 'foo')
        self.group.add_file(fileinfo)
        self.assertEqual(self.group.get_patterns(), ['foo'])

    def testGetPatternsOneDirOneFile(self):
        dirinfo = tree.DirInfo(None, 'dir')
        fileinfo = tree.FileInfo(dirinfo, 'foo')
        self.group.add_file(fileinfo)
        self.assertEqual(self.group.get_patterns(), ['dir/*'])

    def testGetPatternsSeparateGroups(self):
        dirinfo = tree.DirInfo(None, 'dir')
        foo = tree.FileInfo(dirinfo, 'foo')
        bar = tree.FileInfo(dirinfo, 'bar')
        self.group.add_file(foo)
        other = tree.FileGroup()
        other.add_file(bar)
        self.assertEqual(self.group.get_patterns(), ['dir/*'])
        self.assertEqual(other.get_patterns(), ['dir/bar'])

    def testGetPatternsRoot(self):
        dirinfo = tree.RootInfo('root')
        fileinfo = tree.FileInfo(dirinfo, 'foo')
        self.group.add_file(fileinfo)
        self.assertEqual(self.group.get_patterns(), ['*'])

    def testGetPatternsSeparateGroupsMoreFiles(self):
        dirinfo = tree.DirInfo(None, 'dir')
        foo = tree.FileInfo(dirinfo, 'foo')
        bar = tree.FileInfo(dirinfo, 'bar')
        baz = tree.FileInfo(dirinfo, 'baz')
        self.group.add_file(foo)
        other = tree.FileGroup()
        other.add_file(bar)
        other.add_file(baz)
        self.assertEqual(self.group.get_patterns(), ['dir/*'])
        self.assertEqual(other.get_patterns(), ['dir/bar', 'dir/baz'])

    def testCommonPathEmpty(self):
        self.assertEqual(self.group.commonpath(), '')

    def testCommonPathOneFile(self):
        fileinfo = tree.FileInfo(None, 'foo')
        self.group.add_file(fileinfo)
        self.assertEqual(self.group.commonpath(), 'foo')

    def testCommonPathOneDirTwoFiles(self):
        dirinfo = tree.DirInfo(None, 'dir')
        foo = tree.FileInfo(dirinfo, 'foo')
        bar = tree.FileInfo(dirinfo, 'bar')
        self.group.add_file(foo)
        self.group.add_file(bar)
        self.assertEqual(self.group.commonpath(), 'dir')


class TestCopyrightGroup(unittest.TestCase):

    def setUp(self):
        self.group = tree.CopyrightGroup()

    def testAddTwoPeople(self):
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange(2015, 2015)))
        self.group.add(
            CopyrightHolder('John Doe', 'john@example.com', YearRange(2016, 2016)))

        self.assertEqual(list(self.group.sorted_members()),
                         ['2015, Jane Doe <jane@example.com>',
                          '2016, John Doe <john@example.com>'])

    def testMergeNames(self):
        self.group.add(
            CopyrightHolder('Jane', 'jane@example.com', YearRange()))
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange()))

        self.assertEqual(list(self.group.sorted_members()),
                         ['Jane <jane@example.com>'])

    def testMergeEmails(self):
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange()))
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@mailinator.com', YearRange()))

        self.assertEqual(list(self.group.sorted_members()),
                         ['Jane Doe <jane@example.com>',
                          'Jane Doe <jane@mailinator.com>'])

    def testMergeYears(self):
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange(2010, 2011)))
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange(2015, 2016)))

        self.assertEqual(list(self.group.sorted_members()),
                         ['2010-2016, Jane Doe <jane@example.com>'])

    def testMergeNamesWithYears(self):
        self.group.add(
            CopyrightHolder('Jane Doe', 'jane@example.com', YearRange(1999, 1999)))
        self.group.add(
            CopyrightHolder('Janine Doe', 'jane@example.com',
                            YearRange(2016, 2016)))

        self.assertEqual(list(self.group.sorted_members()),
                         ['1999-2016, Janine Doe <jane@example.com>'])


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