# -*- coding: utf-8 -*-
#
# Copyright © 2012 - 2018 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <https://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

"""Test for language manipulations."""
import gettext
from itertools import chain

from django.test import TestCase
from django.urls import reverse
from django.core.management import call_command
from django.utils.encoding import force_text

from six import StringIO

from weblate.lang.models import Language, Plural, get_plural_type
from weblate.lang import data
from weblate.langdata import languages
from weblate.trans.tests.test_models import BaseTestCase
from weblate.trans.tests.test_views import FixtureTestCase


class LanguagesTest(BaseTestCase):
    TEST_LANGUAGES = (
        (
            'cs_CZ',
            'cs',
            'ltr',
            '(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2',
            'Czech',
        ),
        (
            'cs (2)',
            'cs',
            'ltr',
            '(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2',
            'Czech',
        ),
        (
            'czech',
            'cs',
            'ltr',
            '(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2',
            'Czech',
        ),
        (
            'cs_CZ@hantec',
            'cs_CZ@hantec',
            'ltr',
            '(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2',
            'Czech (cs_CZ@hantec)',
        ),
        (
            'de-DE',
            'de',
            'ltr',
            'n != 1',
            'German',
        ),
        (
            'de_AT',
            'de_AT',
            'ltr',
            'n != 1',
            'Austrian German',
        ),
        (
            'de_CZ',
            'de_CZ',
            'ltr',
            'n != 1',
            'German (de_CZ)',
        ),
        (
            'portuguese_portugal',
            'pt_PT',
            'ltr',
            'n != 1',
            'Portuguese (Portugal)',
        ),
        (
            'pt-rBR',
            'pt_BR',
            'ltr',
            'n > 1',
            'Portuguese (Brazil)',
        ),
        (
            'sr+latn',
            'sr_Latn',
            'ltr',
            'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && '
            '(n%100<10 || n%100>=20) ? 1 : 2',
            'Serbian (latin)',
        ),
        (
            'sr_RS@latin',
            'sr_Latn',
            'ltr',
            'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && '
            '(n%100<10 || n%100>=20) ? 1 : 2',
            'Serbian (latin)',
        ),
        (
            'sr-RS@latin',
            'sr_Latn',
            'ltr',
            'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && '
            '(n%100<10 || n%100>=20) ? 1 : 2',
            'Serbian (latin)',
        ),
        (
            'sr_RS@latin',
            'sr_Latn',
            'ltr',
            'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && '
            '(n%100<10 || n%100>=20) ? 1 : 2',
            'Serbian (latin)',
        ),
        (
            'en_CA_MyVariant',
            'en_CA@myvariant',
            'ltr',
            'n != 1',
            'English (Canada)',
        ),
        (
            'en_CZ',
            'en_CZ',
            'ltr',
            'n != 1',
            'English (en_CZ)',
        ),
        (
            'zh_CN',
            'zh_Hans',
            'ltr',
            '0',
            'Chinese (Simplified)',
        ),
        (
            'zh-CN',
            'zh_Hans',
            'ltr',
            '0',
            'Chinese (Simplified)',
        ),
        (
            'zh_HANT',
            'zh_Hant',
            'ltr',
            '0',
            'Chinese (Traditional)',
        ),
        (
            'zh-HANT',
            'zh_Hant',
            'ltr',
            '0',
            'Chinese (Traditional)',
        ),
        (
            'zh-CN@test',
            'zh_CN@test',
            'ltr',
            '0',
            'Chinese (zh_CN@test)',
        ),
        (
            'zh-rCN',
            'zh_Hans',
            'ltr',
            '0',
            'Chinese (Simplified)',
        ),
        (
            'zh_rCN',
            'zh_Hans',
            'ltr',
            '0',
            'Chinese (Simplified)',
        ),
        (
            'zh_HK',
            'zh_Hant_HK',
            'ltr',
            '0',
            'Chinese (Hong Kong)',
        ),
        (
            'zh_Hant-rHK',
            'zh_Hant_HK',
            'ltr',
            '0',
            'Chinese (Hong Kong)',
        ),
        (
            'ar',
            'ar',
            'rtl',
            'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 '
            ': n%100>=11 ? 4 : 5',
            'Arabic',
        ),
        (
            'ar_AA',
            'ar',
            'rtl',
            'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 '
            ': n%100>=11 ? 4 : 5',
            'Arabic',
        ),
        (
            'ar_XX',
            'ar_XX',
            'rtl',
            'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 '
            ': n%100>=11 ? 4 : 5',
            'Arabic (ar_XX)',
        ),
        (
            'xx',
            'xx',
            'ltr',
            'n != 1',
            'xx (generated)',
        ),
    )

    def test_auto_create(self):
        """Test that auto create correctly handles languages"""
        for original, expected, direction, plural, name in self.TEST_LANGUAGES:
            # Create language
            lang = Language.objects.auto_get_or_create(original)
            # Check language code
            self.assertEqual(
                lang.code,
                expected,
                'Invalid code for {0}: {1}'.format(original, lang.code)
            )
            # Check direction
            self.assertEqual(
                lang.direction,
                direction,
                'Invalid direction for {0}'.format(original)
            )
            # Check plurals
            plural_obj = lang.plural_set.get(source=Plural.SOURCE_DEFAULT)
            self.assertEqual(
                plural_obj.equation,
                plural,
                'Invalid plural for {0} (expected {1}, got {2})'.format(
                    original, plural, plural_obj.equation,
                )
            )
            # Check whether html contains both language code and direction
            self.assertIn(direction, lang.get_html())
            self.assertIn(expected, lang.get_html())
            # Check name
            self.assertEqual(force_text(lang), name)


class CommandTest(TestCase):
    """Test for management commands."""
    def test_setuplang(self):
        call_command('setuplang')
        self.assertTrue(Language.objects.exists())

    def test_setuplang_noupdate(self):
        call_command('setuplang', update=False)
        self.assertTrue(Language.objects.exists())

    def test_list_languages(self):
        output = StringIO()
        call_command(
            'list_languages', 'cs',
            stdout=output
        )
        self.assertIn('Czech', output.getvalue())

    def test_list_languages_lower(self):
        output = StringIO()
        call_command(
            'list_languages', 'cs', lower=True,
            stdout=output
        )
        self.assertIn('Czech', output.getvalue())


class VerifyPluralsTest(TestCase):
    """In database plural form verification."""
    @staticmethod
    def all_data():
        return chain(languages.LANGUAGES, languages.EXTRAPLURALS)

    def test_valid(self):
        """Validate that we can name all plural equations"""
        for code, dummy, dummy, pluraleq in self.all_data():
            self.assertNotEqual(
                get_plural_type(code, pluraleq),
                data.PLURAL_UNKNOWN,
                'Can not guess plural type for {0} ({1})'.format(
                    code, pluraleq
                )
            )

    def test_equation(self):
        """Validate that all equations can be parsed by gettext"""
        # Verify we get an error on invalid syntax
        self.assertRaises(
            (SyntaxError, ValueError),
            gettext.c2py,
            'n==0 ? 1 2'
        )
        for code, dummy, nplurals, pluraleq in self.all_data():
            # Validate plurals can be parsed
            plural = gettext.c2py(pluraleq)
            # Get maximal plural
            calculated = max([plural(x) for x in range(200)]) + 1
            # Check it matches ours
            self.assertEqual(
                calculated,
                nplurals,
                'Invalid nplurals for {0}: {1} ({2}, {3})'.format(
                    code,
                    calculated,
                    nplurals,
                    pluraleq,
                )
            )


class LanguagesViewTest(FixtureTestCase):
    def test_languages(self):
        response = self.client.get(reverse('languages'))
        self.assertContains(response, 'Czech')

    def test_language(self):
        response = self.client.get(reverse(
            'show_language',
            kwargs={'lang': 'cs'}
        ))
        self.assertContains(response, 'Czech')
        self.assertContains(response, 'Test/Test')

    def test_language_br(self):
        response = self.client.get(reverse(
            'show_language',
            kwargs={'lang': 'br'}
        ))
        self.assertContains(response, 'Breton')
        # Example is listed
        self.assertContains(response, '1000000')

    def test_project_language(self):
        response = self.client.get(reverse(
            'project-language',
            kwargs={'lang': 'cs', 'project': 'test'}
        ))
        self.assertContains(response, 'Czech')
        self.assertContains(response, '/projects/test/test/cs/')

    def test_language_redirect(self):
        response = self.client.get(reverse(
            'show_language',
            kwargs={'lang': 'cs_CZ'}
        ))
        self.assertRedirects(
            response,
            reverse(
                'show_language',
                kwargs={'lang': 'cs'}
            )
        )

    def test_language_nonexisting(self):
        response = self.client.get(reverse(
            'show_language',
            kwargs={'lang': 'nonexisting'}
        ))
        self.assertEqual(response.status_code, 404)


class PluralsCompareTest(TestCase):
    def test_match(self):
        plural = Plural.objects.get(
            language__code='cs', source=Plural.SOURCE_DEFAULT
        )
        self.assertTrue(
            plural.same_plural(plural.number, plural.equation)
        )

    def test_formula(self):
        plural = Plural.objects.get(
            language__code='pt', source=Plural.SOURCE_DEFAULT
        )
        self.assertFalse(
            plural.same_plural(2, '(n != 1)')
        )

    def test_different_formula(self):
        plural = Plural.objects.get(
            language__code='pt', source=Plural.SOURCE_DEFAULT
        )
        self.assertTrue(
            plural.same_plural(2, '(n > 1)')
        )

    def test_different_count(self):
        plural = Plural.objects.get(
            language__code='lt', source=Plural.SOURCE_DEFAULT
        )
        self.assertFalse(
            plural.same_plural(
                4, '(n%10==1 ? 0 : n%10==1 && n%100!=11 ?'
                ' 1 : n %10>=2 && (n%100<10 || n%100>=20) ? 2 : 3)'
            )
        )

    def test_invalid(self):
        plural = Plural.objects.get(
            language__code='lt', source=Plural.SOURCE_DEFAULT
        )
        self.assertFalse(
            plural.same_plural(1, 'bogus')
        )


class PluralTest(TestCase):
    def test_examples(self):
        plural = Plural(number=2, equation='n!=1')
        self.assertEqual(
            plural.examples,
            {0: ['1'], 1: ['0', '2', '3', '4', '5', '6', '7', '8', '9', '10']}
        )

    def test_plurals(self):
        """Test whether plural form is correctly calculated."""
        plural = Plural.objects.get(language__code='cs')
        self.assertEqual(
            plural.plural_form,
            'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;'
        )

    def test_plural_names(self):
        plural = Plural.objects.get(language__code='cs')
        self.assertEqual(plural.get_plural_name(0), 'One')
        self.assertEqual(plural.get_plural_name(1), 'Few')
        self.assertEqual(plural.get_plural_name(2), 'Other')

    def test_plural_names_invalid(self):
        plural = Plural.objects.get(language__code='cs')
        plural.type = -1
        self.assertEqual(plural.get_plural_name(0), 'Singular')
        self.assertEqual(plural.get_plural_name(1), 'Plural')
        self.assertEqual(plural.get_plural_name(2), 'Plural form 2')

    def test_plural_labels(self):
        plural = Plural.objects.get(language__code='cs')
        label = plural.get_plural_label(0)
        self.assertIn('One', label)
        self.assertIn('1', label)
        label = plural.get_plural_label(1)
        self.assertIn('Few', label)
        self.assertIn('2, 3, 4', label)
        label = plural.get_plural_label(2)
        self.assertIn('Other', label)
        self.assertIn('5, 6, 7', label)

    def test_plural_type(self):
        language = Language.objects.get(code='cs')
        plural = Plural.objects.create(
            language=language,
            number=3,
            equation=(
                '(n%10==1 && n%100!=11 ? 0 : '
                'n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'
            ),
            source=Plural.SOURCE_GETTEXT,
        )
        self.assertEqual(plural.type, data.PLURAL_ONE_FEW_OTHER)
