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

# Unit tests for decopy
# Copyright (C) 2016 Maximiliano Curia <maxy@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 json
import os
import unittest

try:
    import regex as re
except ImportError:
    import re

from decopy import matchers


class TestMatchers(unittest.TestCase):

    def testCleanComments(self):
        test = ('\n'
                'c Fortran\n'
                'C Idem\n'
                '  /* C // C++ */\n'
                ';; Lisp\n'
                '  <!-- HTML -->\n'
                'dnl m4\n'
                '  * continuation *\n'
                '\n')

        result = matchers.clean_comments(test)
        self.assertEqual(result, ' Fortran Idem C C++ Lisp HTML m4 '
                         'continuation ')

    def testParseCopyright(self):
        line = 'Copyright: Foo Bar <foo@example.com>'
        continuation = None
        copyrights, continuation = matchers.parse_copyright(line, continuation)
        self.assertEqual(len(copyrights), 1)
        self.assertEqual(copyrights[0], 'Foo Bar <foo@example.com>')
        line = '           2050- Baz <baz@example.com>'
        copyrights, continuation = matchers.parse_copyright(line, continuation)
        self.assertEqual(len(copyrights), 1)
        self.assertEqual(copyrights[0], '2050- Baz <baz@example.com>')
        line = 'Copyright (c) William Boo, (c) La Momia'
        copyrights, continuation = matchers.parse_copyright(line, continuation)
        self.assertEqual(len(copyrights), 2)
        self.assertEqual(copyrights[0], 'William Boo')
        self.assertEqual(copyrights[1], 'La Momia')

    def testParseHolders(self):
        text = (' /* Copyright Foo Bar\n'
                '  *  Baz */\n'
                ' // Copyright (c) Lorem, (c) Ipsum')
        holders = matchers.parse_holders(text)
        self.assertEqual([holder.person for holder in holders],
                         ['Foo Bar', 'Baz', 'Lorem', 'Ipsum'])

    def testParseHoldersMultiline(self):
        # Positive
        text = (' /* This software is Copyright:                 *\n'
                '  *  2018, Testy McTestface <testy@example.com> *\n'
                '  *  2017, Boaty McBoatface <boaty@example.com> */')
        holders = matchers.parse_holders(text)
        self.assertEqual([(str(h.years), h.name, h.email) for h in holders],
                         [('2018', 'Testy McTestface', 'testy@example.com'),
                          ('2017', 'Boaty McBoatface', 'boaty@example.com')])
        # Negative
        text = (' /* This software is free software:             *\n'
                '  *  2018, Testy McTestface <testy@example.com> *\n'
                '  *  2017, Boaty McBoatface <boaty@example.com> */')
        holders = matchers.parse_holders(text)
        self.assertEqual([(str(h.years), h.name, h.email) for h in holders],
                         [])

    def testParseHoldersMultilineYears(self):

        # Positive
        text = (' /* The software and docs is Copyright 1984, 1985, 1986 *\n'
                '  * 1987 Testy McTestface <testy@example.com>           *\n'
                '  * 1988 Boaty McBoatface <boaty@example.com>           */')
        holders = matchers.parse_holders(text)
        self.assertEqual(
            [(str(h.years), h.name, h.email) for h in holders],
            [('1984-1987', 'Testy McTestface', 'testy@example.com'),
             ('1988', 'Boaty McBoatface', 'boaty@example.com')])

        # Negative
        text = (' /* The software and docs is Copyright 1984, 1985, 1986 *\n'
                '  *                                                     *\n'
                '  * 1987 Testy McTestface <testy@example.com>           *\n'
                '  * 1988 Boaty McBoatface <boaty@example.com>           */')
        holders = matchers.parse_holders(text)
        self.assertEqual(
            [(str(h.years), h.name, h.email) for h in holders],
            [('1987', 'Testy McTestface', 'testy@example.com'),
             ('1988', 'Boaty McBoatface', 'boaty@example.com')])

    def _check_license_match(self, license_, matcher, expected, ignore=None):

        for key in ('licenseText', 'standardLicenseHeader',
                    'licenseExceptionText'):
            if key not in license_ or not license_[key]:
                continue
            if ignore and key in ignore:
                continue
            text = license_[key]
            result = matcher(text)
            self.assertEqual(result, expected,
                             msg='with key={}'.format(key))

    def _check_license_match_re(self, license_, matcher, expected):

        for key in ('licenseText', 'standardLicenseHeader',
                    'licenseExceptionText'):
            if key not in license_ or not license_[key]:
                continue
            text = license_[key]
            result = matcher(text)
            matches = re.match(expected, result)
            self.assertTrue(
                matches, msg='with key={}, result ({}) doesn\'t match with '
                'the expected value ({})\n{}'.format(
                    key, result, expected, text))

    @staticmethod
    def _find_licenses_matcher(text):
        licenses = matchers.find_licenses(
            matchers.clean_comments(text))
        return ' or '.join(sorted(licenses))

    def _open_license_and_check(self, filename, matcher, expected, ignore=None):

        with open(os.path.join('tests/testdata/licenses', filename)) as fp:
            license_ = json.load(fp)
        return self._check_license_match(license_, matcher, expected, ignore)

    def _open_license_and_check_re(self, filename, matcher, expected):

        with open(os.path.join('tests/testdata/licenses', filename)) as fp:
            license_ = json.load(fp)
        return self._check_license_match_re(license_, matcher, expected)

    def testParseBsd(self):

        def _bsd_matcher(text):
            return matchers.parse_bsd(
                matchers.clean_comments(text),
                None,
                'BSD')

        self._open_license_and_check('BSD-2-Clause.json',
                                     _bsd_matcher,
                                     'BSD-2-clause')
        self._open_license_and_check('BSD-2-Clause-FreeBSD.json',
                                     _bsd_matcher,
                                     'BSD-2-clause-FreeBSD')
        self._open_license_and_check('BSD-2-Clause-NetBSD.json',
                                     _bsd_matcher,
                                     'BSD-2-clause-NetBSD')
        self._open_license_and_check('BSD-3-Clause.json',
                                     _bsd_matcher,
                                     'BSD-3-clause')
        self._open_license_and_check('BSD-3-Clause-LBNL.json',
                                     _bsd_matcher,
                                     'BSD-3-clause')
        self._open_license_and_check('BSD-3-Clause-Clear.json',
                                     _bsd_matcher,
                                     'BSD-3-clause')
        self._open_license_and_check('BSD-3-Clause-No-Nuclear-License.json',
                                     _bsd_matcher,
                                     'BSD-3-clause-no-nuclear')
        self._open_license_and_check('BSD-3-Clause-No-Nuclear-License-2014.json',
                                     _bsd_matcher,
                                     'BSD-3-clause-no-nuclear')
        self._open_license_and_check('BSD-3-Clause-No-Nuclear-Warranty.json',
                                     _bsd_matcher,
                                     'BSD-3-clause-no-nuclear')
        self._open_license_and_check('BSD-4-Clause.json',
                                     _bsd_matcher,
                                     'BSD-4-clause')
        self._open_license_and_check('BSD-4-Clause-UC.json',
                                     _bsd_matcher,
                                     'BSD-4-clause')
        self._open_license_and_check('BSD-Protection.json',
                                     _bsd_matcher,
                                     'BSD-protection')
        self._open_license_and_check('BSD-Source-Code.json',
                                     _bsd_matcher,
                                     'BSD-source-code')
        self._open_license_and_check('BSD-3-Clause-Attribution.json',
                                     _bsd_matcher,
                                     'BSD-3-clause-attribution')
        self._open_license_and_check('0BSD.json',
                                     _bsd_matcher,
                                     '0BSD')

    def testParseCCBY(self):

        def _cc_by_matcher(text):
            return matchers.parse_cc_by(
                matchers.clean_comments(text),
                None,
                'CC-BY')

        self._open_license_and_check('CC-BY-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-1')
        self._open_license_and_check('CC-BY-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-2')
        self._open_license_and_check('CC-BY-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-2.5')
        self._open_license_and_check('CC-BY-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-3')
        self._open_license_and_check('CC-BY-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-4')
        self._open_license_and_check('CC-BY-NC-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-1')
        self._open_license_and_check('CC-BY-NC-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-2')
        self._open_license_and_check('CC-BY-NC-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-2.5')
        self._open_license_and_check('CC-BY-NC-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-3')
        self._open_license_and_check('CC-BY-NC-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-4')
        self._open_license_and_check('CC-BY-NC-ND-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-ND-1')
        self._open_license_and_check('CC-BY-NC-ND-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-ND-2')
        self._open_license_and_check('CC-BY-NC-ND-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-ND-2.5')
        self._open_license_and_check('CC-BY-NC-ND-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-ND-3')
        self._open_license_and_check('CC-BY-NC-ND-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-ND-4')
        self._open_license_and_check('CC-BY-NC-SA-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-SA-1')
        self._open_license_and_check('CC-BY-NC-SA-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-SA-2')
        self._open_license_and_check('CC-BY-NC-SA-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-SA-2.5')
        self._open_license_and_check('CC-BY-NC-SA-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-SA-3')
        self._open_license_and_check('CC-BY-NC-SA-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-NC-SA-4')
        self._open_license_and_check('CC-BY-ND-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-ND-1')
        self._open_license_and_check('CC-BY-ND-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-ND-2')
        self._open_license_and_check('CC-BY-ND-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-ND-2.5')
        self._open_license_and_check('CC-BY-ND-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-ND-3')
        self._open_license_and_check('CC-BY-ND-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-ND-4')
        self._open_license_and_check('CC-BY-SA-1.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-SA-1')
        self._open_license_and_check('CC-BY-SA-2.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-SA-2')
        self._open_license_and_check('CC-BY-SA-2.5.json',
                                     _cc_by_matcher,
                                     'CC-BY-SA-2.5')
        self._open_license_and_check('CC-BY-SA-3.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-SA-3')
        self._open_license_and_check('CC-BY-SA-4.0.json',
                                     _cc_by_matcher,
                                     'CC-BY-SA-4')

        self._open_license_and_check('CC0-1.0.json',
                                     self._find_licenses_matcher,
                                     'CC0')

    def testParseGPL(self):

        def _gpl_matcher(text):
            return matchers.parse_gpl(
                matchers.clean_comments(text),
                None,
                'GPL')

        self._open_license_and_check('AGPL-1.0.json',
                                     self._find_licenses_matcher,
                                     'AGPL-1')
        self._open_license_and_check_re('AGPL-3.0.json',
                                        self._find_licenses_matcher,
                                        r'AGPL-3\+?$')
        self._open_license_and_check_re('GPL-1.0.json',
                                        self._find_licenses_matcher,
                                        r'GPL-1\+?$')
        self._open_license_and_check('GPL-1.0+.json',
                                     self._find_licenses_matcher,
                                     'GPL-1+')
        self._open_license_and_check_re('GPL-2.0.json',
                                        self._find_licenses_matcher,
                                        r'GPL-2\+?$')
        self._open_license_and_check('GPL-2.0+.json',
                                     self._find_licenses_matcher,
                                     'GPL-2+')
        self._open_license_and_check_re('GPL-3.0.json',
                                        self._find_licenses_matcher,
                                        r'GPL(?:-3\+)?$')
        self._open_license_and_check('GPL-3.0+.json',
                                     self._find_licenses_matcher,
                                     'GPL-3+')

        self._open_license_and_check('GPL-2.0-with-autoconf-exception.json',
                                     _gpl_matcher,
                                     'GPL with AutoConf exception')
        self._open_license_and_check('GPL-2.0-with-bison-exception.json',
                                     _gpl_matcher,
                                     'GPL with Bison exception')
        self._open_license_and_check('GPL-2.0-with-classpath-exception.json',
                                     _gpl_matcher,
                                     'GPL with ClassPath exception')
        self._open_license_and_check('GPL-2.0-with-GCC-exception.json',
                                     _gpl_matcher,
                                     'GPL with GCC exception')

        self._open_license_and_check('GPL-3.0-with-autoconf-exception.json',
                                     _gpl_matcher,
                                     'GPL-3 with AutoConf exception')
        self._open_license_and_check('GPL-3.0-with-GCC-exception.json',
                                     _gpl_matcher,
                                     'GPL-3 with GCC exception')

        self._open_license_and_check('389-exception.json',
                                     _gpl_matcher,
                                     'GPL-2 with 389 exception')

        self._open_license_and_check('Autoconf-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with AutoConf exception')
        self._open_license_and_check('Autoconf-exception-3.0.json',
                                     _gpl_matcher,
                                     'GPL-3 with AutoConf exception')
        self._open_license_and_check('Bison-exception-2.2.json',
                                     _gpl_matcher,
                                     'GPL with Bison exception')
        self._open_license_and_check('Classpath-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with ClassPath exception')
        self._open_license_and_check('CLISP-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL-2 with CLISP exception')
        self._open_license_and_check('DigiRule-FOSS-exception.json',
                                     _gpl_matcher,
                                     'GPL with DigiRule-FOSS exception')
        self._open_license_and_check('eCos-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with eCos-2 exception')
        self._open_license_and_check('Fawkes-Runtime-exception.json',
                                     _gpl_matcher,
                                     'GPL with ClassPath_MIF exception')
        self._open_license_and_check('FLTK-exception.json',
                                     _gpl_matcher,
                                     'GPL with FLTK exception')
        self._open_license_and_check('Font-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with Font exception')
        self._open_license_and_check('freertos-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with FreeRTOS exception')
        self._open_license_and_check('GCC-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with GCC exception')
        self._open_license_and_check('GCC-exception-3.1.json',
                                     _gpl_matcher,
                                     'GPL-3 with GCC exception')
        self._open_license_and_check('gnu-javamail-exception.json',
                                     _gpl_matcher,
                                     'GPL with GNU-JavaMail exception')
        self._open_license_and_check('i2p-gpl-java-exception.json',
                                     _gpl_matcher,
                                     'GPL with i2p-Java exception')
        self._open_license_and_check('Libtool-exception.json',
                                     _gpl_matcher,
                                     'GPL with LibTool exception')
        self._open_license_and_check('LZMA-exception.json',
                                     _gpl_matcher,
                                     'GPL with LZMA exception')
        self._open_license_and_check('mif-exception.json',
                                     _gpl_matcher,
                                     'GPL with MIF exception')
        self._open_license_and_check('Nokia-Qt-exception-1.1.json',
                                     _gpl_matcher,
                                     'GPL-2.1 with Qt1.1 exception')
        self._open_license_and_check('OCCT-exception-1.0.json',
                                     _gpl_matcher,
                                     'GPL-2.1 with OCCT-1 exception')
        self._open_license_and_check('openvpn-openssl-exception.json',
                                     _gpl_matcher,
                                     'GPL with openvpn-openssl exception')
        # handled in the find licenses part
        # FIXME: ?
        self._open_license_and_check('Qwt-exception-1.0.json',
                                     self._find_licenses_matcher,
                                     'LGPL-2.1 with Qwt-1 exception')
        self._open_license_and_check('u-boot-exception-2.0.json',
                                     _gpl_matcher,
                                     'GPL with u-boot exception')
        self._open_license_and_check('WxWindows-exception-3.1.json',
                                     _gpl_matcher,
                                     'GPL with WxWindows-3.1+ exception')

    def testParseLGPL(self):

        self._open_license_and_check_re('LGPL-2.0.json',
                                        self._find_licenses_matcher,
                                        r'LGPL-2\+?')
        self._open_license_and_check('LGPL-2.0+.json',
                                     self._find_licenses_matcher,
                                     'LGPL-2+')
        self._open_license_and_check_re('LGPL-2.1.json',
                                        self._find_licenses_matcher,
                                        r'LGPL-2.1\+?')
        self._open_license_and_check('LGPL-2.1+.json',
                                     self._find_licenses_matcher,
                                     'LGPL-2.1+')
        self._open_license_and_check_re('LGPL-3.0.json',
                                        self._find_licenses_matcher,
                                        r'LGPL-3\+?')
        self._open_license_and_check('LGPL-3.0+.json',
                                     self._find_licenses_matcher,
                                     'LGPL-3+')
        self._open_license_and_check('LGPLLR.json',
                                     self._find_licenses_matcher,
                                     'LGPLLR')

    def testParseGFDL(self):

        self._open_license_and_check('GFDL-1.1.json',
                                     self._find_licenses_matcher,
                                     'GFDL-1.1')
        self._open_license_and_check('GFDL-1.2.json',
                                     self._find_licenses_matcher,
                                     'GFDL-NIV-1.2')
        self._open_license_and_check('GFDL-1.3.json',
                                     self._find_licenses_matcher,
                                     'GFDL-NIV-1.3')

    def testParseLPPL(self):

        self._open_license_and_check('LPPL-1.0.json',
                                     self._find_licenses_matcher,
                                     'LPPL-1+')
        self._open_license_and_check('LPPL-1.1.json',
                                     self._find_licenses_matcher,
                                     'LPPL-1.1+')
        self._open_license_and_check('LPPL-1.2.json',
                                     self._find_licenses_matcher,
                                     'LPPL-1.2+')
        self._open_license_and_check('LPPL-1.3a.json',
                                     self._find_licenses_matcher,
                                     'LPPL-1.3+')
        self._open_license_and_check('LPPL-1.3c.json',
                                     self._find_licenses_matcher,
                                     'LPPL-1.3+')

    def testParseMIT(self):

        self._open_license_and_check('MIT.json',
                                     self._find_licenses_matcher,
                                     'Expat')
        self._open_license_and_check('MIT-advertising.json',
                                     self._find_licenses_matcher,
                                     'MIT-advertising')
        self._open_license_and_check('MIT-CMU.json',
                                     self._find_licenses_matcher,
                                     'MIT-CMU')
        self._open_license_and_check('MIT-enna.json',
                                     self._find_licenses_matcher,
                                     'MIT-enna')
        self._open_license_and_check('MIT-feh.json',
                                     self._find_licenses_matcher,
                                     'MIT-feh')
        self._open_license_and_check('MITNFA.json',
                                     self._find_licenses_matcher,
                                     'MITNFA')

    def testParseZPL(self):

        self._open_license_and_check('ZPL-1.1.json',
                                     self._find_licenses_matcher,
                                     'ZPL-1.1')
        self._open_license_and_check('ZPL-2.0.json',
                                     self._find_licenses_matcher,
                                     'ZPL-2')
        self._open_license_and_check('ZPL-2.1.json',
                                     self._find_licenses_matcher,
                                     'ZPL-2.1')


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