#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013 the BabelFish authors. All rights reserved.
# Use of this source code is governed by the 3-clause BSD license
# that can be found in the LICENSE file.
#
from __future__ import unicode_literals
import re
import sys
import pickle
from unittest import TestCase, TestSuite, TestLoader, TextTestRunner
from pkg_resources import resource_stream  # @UnresolvedImport
from babelfish import (LANGUAGES, Language, Country, Script, language_converters, country_converters,
    LanguageReverseConverter, LanguageConvertError, LanguageReverseError, CountryReverseError)


if sys.version_info[:2] <= (2, 6):
    _MAX_LENGTH = 80

    def safe_repr(obj, short=False):
        try:
            result = repr(obj)
        except Exception:
            result = object.__repr__(obj)
        if not short or len(result) < _MAX_LENGTH:
            return result
        return result[:_MAX_LENGTH] + ' [truncated]...'

    class _AssertRaisesContext(object):
        """A context manager used to implement TestCase.assertRaises* methods."""

        def __init__(self, expected, test_case, expected_regexp=None):
            self.expected = expected
            self.failureException = test_case.failureException
            self.expected_regexp = expected_regexp

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_value, tb):
            if exc_type is None:
                try:
                    exc_name = self.expected.__name__
                except AttributeError:
                    exc_name = str(self.expected)
                raise self.failureException(
                    "{0} not raised".format(exc_name))
            if not issubclass(exc_type, self.expected):
                # let unexpected exceptions pass through
                return False
            self.exception = exc_value  # store for later retrieval
            if self.expected_regexp is None:
                return True

            expected_regexp = self.expected_regexp
            if isinstance(expected_regexp, basestring):
                expected_regexp = re.compile(expected_regexp)
            if not expected_regexp.search(str(exc_value)):
                raise self.failureException('"%s" does not match "%s"' %
                         (expected_regexp.pattern, str(exc_value)))
            return True

    class _Py26FixTestCase(object):
        def assertIsNone(self, obj, msg=None):
            """Same as self.assertTrue(obj is None), with a nicer default message."""
            if obj is not None:
                standardMsg = '%s is not None' % (safe_repr(obj),)
                self.fail(self._formatMessage(msg, standardMsg))

        def assertIsNotNone(self, obj, msg=None):
            """Included for symmetry with assertIsNone."""
            if obj is None:
                standardMsg = 'unexpectedly None'
                self.fail(self._formatMessage(msg, standardMsg))

        def assertIn(self, member, container, msg=None):
            """Just like self.assertTrue(a in b), but with a nicer default message."""
            if member not in container:
                standardMsg = '%s not found in %s' % (safe_repr(member),
                                                      safe_repr(container))
                self.fail(self._formatMessage(msg, standardMsg))

        def assertNotIn(self, member, container, msg=None):
            """Just like self.assertTrue(a not in b), but with a nicer default message."""
            if member in container:
                standardMsg = '%s unexpectedly found in %s' % (safe_repr(member),
                                                            safe_repr(container))
                self.fail(self._formatMessage(msg, standardMsg))

        def assertIs(self, expr1, expr2, msg=None):
            """Just like self.assertTrue(a is b), but with a nicer default message."""
            if expr1 is not expr2:
                standardMsg = '%s is not %s' % (safe_repr(expr1),
                                                 safe_repr(expr2))
                self.fail(self._formatMessage(msg, standardMsg))

        def assertIsNot(self, expr1, expr2, msg=None):
            """Just like self.assertTrue(a is not b), but with a nicer default message."""
            if expr1 is expr2:
                standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),)
                self.fail(self._formatMessage(msg, standardMsg))

else:
    class _Py26FixTestCase(object):
        pass


class TestScript(TestCase, _Py26FixTestCase):
    def test_wrong_script(self):
        self.assertRaises(ValueError, lambda: Script('Azer'))

    def test_eq(self):
        self.assertEqual(Script('Latn'), Script('Latn'))

    def test_ne(self):
        self.assertNotEqual(Script('Cyrl'), Script('Latn'))

    def test_hash(self):
        self.assertEqual(hash(Script('Hira')), hash('Hira'))

    def test_pickle(self):
        self.assertEqual(pickle.loads(pickle.dumps(Script('Latn'))), Script('Latn'))


class TestCountry(TestCase, _Py26FixTestCase):
    def test_wrong_country(self):
        self.assertRaises(ValueError, lambda: Country('ZZ'))

    def test_eq(self):
        self.assertEqual(Country('US'), Country('US'))

    def test_ne(self):
        self.assertNotEqual(Country('GB'), Country('US'))
        self.assertIsNotNone(Country('US'))

    def test_hash(self):
        self.assertEqual(hash(Country('US')), hash('US'))

    def test_pickle(self):
        for country in [Country('GB'), Country('US')]:
            self.assertEqual(pickle.loads(pickle.dumps(country)), country)

    def test_converter_name(self):
        self.assertEqual(Country('US').name, 'UNITED STATES')
        self.assertEqual(Country.fromname('UNITED STATES'), Country('US'))
        self.assertEqual(Country.fromcode('UNITED STATES', 'name'), Country('US'))
        self.assertRaises(CountryReverseError, lambda: Country.fromname('ZZZZZ'))
        self.assertEqual(len(country_converters['name'].codes), 249)


class TestLanguage(TestCase, _Py26FixTestCase):
    def test_languages(self):
        self.assertEqual(len(LANGUAGES), 7874)

    def test_wrong_language(self):
        self.assertRaises(ValueError, lambda: Language('zzz'))

    def test_unknown_language(self):
        self.assertEqual(Language('zzzz', unknown='und'), Language('und'))

    def test_converter_alpha2(self):
        self.assertEqual(Language('eng').alpha2, 'en')
        self.assertEqual(Language.fromalpha2('en'), Language('eng'))
        self.assertEqual(Language.fromcode('en', 'alpha2'), Language('eng'))
        self.assertRaises(LanguageReverseError, lambda: Language.fromalpha2('zz'))
        self.assertRaises(LanguageConvertError, lambda: Language('aaa').alpha2)
        self.assertEqual(len(language_converters['alpha2'].codes), 184)

    def test_converter_alpha3b(self):
        self.assertEqual(Language('fra').alpha3b, 'fre')
        self.assertEqual(Language.fromalpha3b('fre'), Language('fra'))
        self.assertEqual(Language.fromcode('fre', 'alpha3b'), Language('fra'))
        self.assertRaises(LanguageReverseError, lambda: Language.fromalpha3b('zzz'))
        self.assertRaises(LanguageConvertError, lambda: Language('aaa').alpha3b)
        self.assertEqual(len(language_converters['alpha3b'].codes), 418)

    def test_converter_alpha3t(self):
        self.assertEqual(Language('fra').alpha3t, 'fra')
        self.assertEqual(Language.fromalpha3t('fra'), Language('fra'))
        self.assertEqual(Language.fromcode('fra', 'alpha3t'), Language('fra'))
        self.assertRaises(LanguageReverseError, lambda: Language.fromalpha3t('zzz'))
        self.assertRaises(LanguageConvertError, lambda: Language('aaa').alpha3t)
        self.assertEqual(len(language_converters['alpha3t'].codes), 418)

    def test_converter_name(self):
        self.assertEqual(Language('eng').name, 'English')
        self.assertEqual(Language.fromname('English'), Language('eng'))
        self.assertEqual(Language.fromcode('English', 'name'), Language('eng'))
        self.assertRaises(LanguageReverseError, lambda: Language.fromname('Zzzzzzzzz'))
        self.assertEqual(len(language_converters['name'].codes), 7874)

    def test_converter_scope(self):
        self.assertEqual(language_converters['scope'].codes, set(['I', 'S', 'M']))
        self.assertEqual(Language('eng').scope, 'individual')
        self.assertEqual(Language('und').scope, 'special')

    def test_converter_type(self):
        self.assertEqual(language_converters['type'].codes, set(['A', 'C', 'E', 'H', 'L', 'S']))
        self.assertEqual(Language('eng').type, 'living')
        self.assertEqual(Language('und').type, 'special')

    def test_converter_opensubtitles(self):
        self.assertEqual(Language('fra').opensubtitles, Language('fra').alpha3b)
        self.assertEqual(Language('por', 'BR').opensubtitles, 'pob')
        self.assertEqual(Language.fromopensubtitles('fre'), Language('fra'))
        self.assertEqual(Language.fromopensubtitles('pob'), Language('por', 'BR'))
        self.assertEqual(Language.fromopensubtitles('pb'), Language('por', 'BR'))
        # Montenegrin is not recognized as an ISO language (yet?) but for now it is
        # unofficially accepted as Serbian from Montenegro
        self.assertEqual(Language.fromopensubtitles('mne'), Language('srp', 'ME'))
        self.assertEqual(Language.fromcode('pob', 'opensubtitles'), Language('por', 'BR'))
        self.assertRaises(LanguageReverseError, lambda: Language.fromopensubtitles('zzz'))
        self.assertRaises(LanguageConvertError, lambda: Language('aaa').opensubtitles)
        self.assertEqual(len(language_converters['opensubtitles'].codes), 607)

        # test with all the LANGUAGES from the opensubtitles api
        # downloaded from: http://www.opensubtitles.org/addons/export_languages.php
        f = resource_stream('babelfish', 'data/opensubtitles_languages.txt')
        f.readline()
        for l in f:
            idlang, alpha2, _, upload_enabled, web_enabled = l.decode('utf-8').strip().split('\t')
            if not int(upload_enabled) and not int(web_enabled):
                # do not test LANGUAGES that are too esoteric / not widely available
                continue
            self.assertEqual(Language.fromopensubtitles(idlang).opensubtitles, idlang)
            if alpha2:
                self.assertEqual(Language.fromopensubtitles(idlang), Language.fromopensubtitles(alpha2))
        f.close()

    def test_converter_opensubtitles_codes(self):
        for code in language_converters['opensubtitles'].from_opensubtitles.keys():
            self.assertIn(code, language_converters['opensubtitles'].codes)

    def test_fromietf_country_script(self):
        language = Language.fromietf('fra-FR-Latn')
        self.assertEqual(language.alpha3, 'fra')
        self.assertEqual(language.country, Country('FR'))
        self.assertEqual(language.script, Script('Latn'))

    def test_fromietf_country_no_script(self):
        language = Language.fromietf('fra-FR')
        self.assertEqual(language.alpha3, 'fra')
        self.assertEqual(language.country, Country('FR'))
        self.assertIsNone(language.script)

    def test_fromietf_no_country_no_script(self):
        language = Language.fromietf('fra-FR')
        self.assertEqual(language.alpha3, 'fra')
        self.assertEqual(language.country, Country('FR'))
        self.assertIsNone(language.script)

    def test_fromietf_no_country_script(self):
        language = Language.fromietf('fra-Latn')
        self.assertEqual(language.alpha3, 'fra')
        self.assertIsNone(language.country)
        self.assertEqual(language.script, Script('Latn'))

    def test_fromietf_alpha2_language(self):
        language = Language.fromietf('fr-Latn')
        self.assertEqual(language.alpha3, 'fra')
        self.assertIsNone(language.country)
        self.assertEqual(language.script, Script('Latn'))

    def test_fromietf_wrong_language(self):
        self.assertRaises(ValueError, lambda: Language.fromietf('xyz-FR'))

    def test_fromietf_wrong_country(self):
        self.assertRaises(ValueError, lambda: Language.fromietf('fra-YZ'))

    def test_fromietf_wrong_script(self):
        self.assertRaises(ValueError, lambda: Language.fromietf('fra-FR-Wxyz'))

    def test_eq(self):
        self.assertEqual(Language('eng'), Language('eng'))

    def test_ne(self):
        self.assertNotEqual(Language('fra'), Language('eng'))
        self.assertIsNotNone(Language('fra'))

    def test_nonzero(self):
        self.assertFalse(bool(Language('und')))
        self.assertTrue(bool(Language('eng')))

    def test_language_hasattr(self):
        self.assertTrue(hasattr(Language('fra'), 'alpha3'))
        self.assertTrue(hasattr(Language('fra'), 'alpha2'))
        self.assertFalse(hasattr(Language('bej'), 'alpha2'))

    def test_country_hasattr(self):
        self.assertTrue(hasattr(Country('US'), 'name'))
        self.assertTrue(hasattr(Country('FR'), 'alpha2'))
        self.assertFalse(hasattr(Country('BE'), 'none'))

    def test_country(self):
        self.assertEqual(Language('por', 'BR').country, Country('BR'))
        self.assertEqual(Language('eng', Country('US')).country, Country('US'))

    def test_eq_with_country(self):
        self.assertEqual(Language('eng', 'US'), Language('eng', Country('US')))

    def test_ne_with_country(self):
        self.assertNotEqual(Language('eng', 'US'), Language('eng', Country('GB')))

    def test_script(self):
        self.assertEqual(Language('srp', script='Latn').script, Script('Latn'))
        self.assertEqual(Language('srp', script=Script('Cyrl')).script, Script('Cyrl'))

    def test_eq_with_script(self):
        self.assertEqual(Language('srp', script='Latn'), Language('srp', script=Script('Latn')))

    def test_ne_with_script(self):
        self.assertNotEqual(Language('srp', script='Latn'), Language('srp', script=Script('Cyrl')))

    def test_eq_with_country_and_script(self):
        self.assertEqual(Language('srp', 'SR', 'Latn'), Language('srp', Country('SR'), Script('Latn')))

    def test_ne_with_country_and_script(self):
        self.assertNotEqual(Language('srp', 'SR', 'Latn'), Language('srp', Country('SR'), Script('Cyrl')))

    def test_hash(self):
        self.assertEqual(hash(Language('fra')), hash('fr'))
        self.assertEqual(hash(Language('ace')), hash('ace'))
        self.assertEqual(hash(Language('por', 'BR')), hash('pt-BR'))
        self.assertEqual(hash(Language('srp', script='Cyrl')), hash('sr-Cyrl'))
        self.assertEqual(hash(Language('eng', 'US', 'Latn')), hash('en-US-Latn'))

    def test_pickle(self):
        for lang in [Language('fra'),
                     Language('eng', 'US'),
                     Language('srp', script='Latn'),
                     Language('eng', 'US', 'Latn')]:
            self.assertEqual(pickle.loads(pickle.dumps(lang)), lang)

    def test_str(self):
        self.assertEqual(Language.fromietf(str(Language('eng', 'US', 'Latn'))), Language('eng', 'US', 'Latn'))
        self.assertEqual(Language.fromietf(str(Language('fra', 'FR'))), Language('fra', 'FR'))
        self.assertEqual(Language.fromietf(str(Language('bel'))), Language('bel'))

    def test_register_converter(self):
        class TestConverter(LanguageReverseConverter):
            def __init__(self):
                self.to_test = {'fra': 'test1', 'eng': 'test2'}
                self.from_test = {'test1': 'fra', 'test2': 'eng'}

            def convert(self, alpha3, country=None, script=None):
                if alpha3 not in self.to_test:
                    raise LanguageConvertError(alpha3, country, script)
                return self.to_test[alpha3]

            def reverse(self, test):
                if test not in self.from_test:
                    raise LanguageReverseError(test)
                return (self.from_test[test], None)
        language = Language('fra')
        self.assertFalse(hasattr(language, 'test'))
        language_converters['test'] = TestConverter()
        self.assertTrue(hasattr(language, 'test'))
        self.assertIn('test', language_converters)
        self.assertEqual(Language('fra').test, 'test1')
        self.assertEqual(Language.fromtest('test2').alpha3, 'eng')
        del language_converters['test']
        self.assertNotIn('test', language_converters)
        self.assertRaises(KeyError, lambda: Language.fromtest('test1'))
        self.assertRaises(AttributeError, lambda: Language('fra').test)


def suite():
    suite = TestSuite()
    suite.addTest(TestLoader().loadTestsFromTestCase(TestScript))
    suite.addTest(TestLoader().loadTestsFromTestCase(TestCountry))
    suite.addTest(TestLoader().loadTestsFromTestCase(TestLanguage))
    return suite


if __name__ == '__main__':
    TextTestRunner().run(suite())
