import urllib
import urllib2
import simplejson
import unittest

_GOOGLE_LANGUAGES = {
    'AFRIKAANS': 'af',
    'ALBANIAN': 'sq',
    'AMHARIC': 'am',
    'ARABIC': 'ar',
    'ARMENIAN': 'hy',
    'AZERBAIJANI': 'az',
    'BASQUE': 'eu',
    'BELARUSIAN': 'be',
    'BENGALI': 'bn',
    'BIHARI': 'bh',
    'BULGARIAN': 'bg',
    'BURMESE': 'my',
    'CATALAN': 'ca',
    'CHEROKEE': 'chr',
    'CHINESE': 'zh',
    'CHINESE_SIMPLIFIED': 'zh-CN',
    'CHINESE_TRADITIONAL': 'zh-TW',
    'CROATIAN': 'hr',
    'CZECH': 'cs',
    'DANISH': 'da',
    'DHIVEHI': 'dv',
    'DUTCH': 'nl',
    'ENGLISH': 'en',
    'ESPERANTO': 'eo',
    'ESTONIAN': 'et',
    'FILIPINO': 'tl',
    'FINNISH': 'fi',
    'FRENCH': 'fr',
    'GALICIAN': 'gl',
    'GEORGIAN': 'ka',
    'GERMAN': 'de',
    'GREEK': 'el',
    'GUARANI': 'gn',
    'GUJARATI': 'gu',
    'HEBREW': 'iw',
    'HINDI': 'hi',
    'HUNGARIAN': 'hu',
    'ICELANDIC': 'is',
    'INDONESIAN': 'id',
    'INUKTITUT': 'iu',
    'ITALIAN': 'it',
    'JAPANESE': 'ja',
    'KANNADA': 'kn',
    'KAZAKH': 'kk',
    'KHMER': 'km',
    'KOREAN': 'ko',
    'KURDISH': 'ku',
    'KYRGYZ': 'ky',
    'LAOTHIAN': 'lo',
    'LATVIAN': 'lv',
    'LITHUANIAN': 'lt',
    'MACEDONIAN': 'mk',
    'MALAY': 'ms',
    'MALAYALAM': 'ml',
    'MALTESE': 'mt',
    'MARATHI': 'mr',
    'MONGOLIAN': 'mn',
    'NEPALI': 'ne',
    'NORWEGIAN': 'no',
    'ORIYA': 'or',
    'PASHTO': 'ps',
    'PERSIAN': 'fa',
    'POLISH': 'pl',
    'PORTUGUESE': 'pt-PT',
    'PUNJABI': 'pa',
    'ROMANIAN': 'ro',
    'RUSSIAN': 'ru',
    'SANSKRIT': 'sa',
    'SERBIAN': 'sr',
    'SINDHI': 'sd',
    'SINHALESE': 'si',
    'SLOVAK': 'sk',
    'SLOVENIAN': 'sl',
    'SPANISH': 'es',
    'SWAHILI': 'sw',
    'SWEDISH': 'sv',
    'TAJIK': 'tg',
    'TAMIL': 'ta',
    'TAGALOG': 'tl',
    'TELUGU': 'te',
    'THAI': 'th',
    'TIBETAN': 'bo',
    'TURKISH': 'tr',
    'UKRAINIAN': 'uk',
    'URDU': 'ur',
    'UZBEK': 'uz',
    'UIGHUR': 'ug',
    'VIETNAMESE': 'vi',
}

_GOOGLE_DETECT_LANGUAGE_BASE_URL = \
    'http://ajax.googleapis.com/ajax/services/language/detect'

_GOOGLE_TRANSLATE_BASE_URL = \
    'http://ajax.googleapis.com/ajax/services/language/translate'
_GOOGLE_CHUNK_SIZE = 500  # Max num of characters that google API accepts

_GOOGLE_TRANSLATE_WEBPAGE_URL = 'https://translate.google.com/translate'

###################
# Language name to google format
###################


class LanguageDoesNotMatchError(Exception):
    pass


def _google_lang(lang):
    ret = lang

    # remove sublang
    if ret.find('-') != -1:
        ret = ret[:ret.find('-')]

    # if it is a key, get the value
    if ret.upper() in _GOOGLE_LANGUAGES.keys():
        ret = _GOOGLE_LANGUAGES[ret.upper()]

    # make sure the language is in _GOOGLE_LANGUAGES.values()
    if ret not in _GOOGLE_LANGUAGES.values():
        raise LanguageDoesNotMatchError(lang)

    return ret

###################
# Split into chunks
###################


class ChunkSizeTooSmallError(Exception):
    pass


def _get_chunk_limit(text, splitters, max_size):
    if len(text) <= max_size:
        return len(text)

    for spl in splitters:
        idx = text.rfind(spl, 0, max_size)
        if idx != -1:
            return idx

    raise ChunkSizeTooSmallError('the chunk size is too small')


def _gen_chunks(text, splitters, max_size):
    rest = text

    while rest != '':
        idx = _get_chunk_limit(rest, splitters, max_size) + 1
        chunk, rest = rest[:idx], rest[idx:]
        yield chunk


def _get_chunks(text, splitters, max_size):
    # splitters should be ordered by importance (first is the most important)
    return list(_gen_chunks(text, splitters, max_size))

###################
# Caching
###################


class ImmutableDict(dict):
    '''A hashable dict.'''

    def __init__(self, *args, **kwds):
        dict.__init__(self, *args, **kwds)

    def __setitem__(self, key, value):
        raise NotImplementedError("dict is immutable")

    def __delitem__(self, key):
        raise NotImplementedError("dict is immutable")

    def clear(self):
        raise NotImplementedError("dict is immutable")

    def setdefault(self, k, default=None):
        raise NotImplementedError("dict is immutable")

    def popitem(self):
        raise NotImplementedError("dict is immutable")

    def update(self, other):
        raise NotImplementedError("dict is immutable")

    def __hash__(self):
        return hash(tuple(self.iteritems()))


class Memoize:
    def __init__(self, function):
        self._cache = {}
        self._callable = function

    def __call__(self, *args, **kwds):
        cache = self._cache
        key = self._get_key(*args, **kwds)
        try:
            return cache[key]
        except KeyError:
            cached_value = cache[key] = self._callable(*args, **kwds)
            return cached_value

    def _get_key(self, *args, **kwds):
        return kwds and (args, ImmutableDict(kwds)) or args


def cache_method(function):
    return Memoize(function)


###################
# Language detection and translation
###################


class ServiceUnavailableError(Exception):
    pass


class ServiceError(Exception):
    pass


def _detect_lang(text):
    # build url
    params = [('v', '1.0'), ('q', text.encode('utf-8'))]
    url = _GOOGLE_DETECT_LANGUAGE_BASE_URL + '?' + urllib.urlencode(params)

    # call
    page = urllib2.urlopen(url)

    # unpack the data
    result = simplejson.load(page)
    if result is None:
        raise ServiceUnavailableError('detection service unavailable')
    elif result['responseStatus'] != 200:
        raise ServiceError(result['responseDetails'])
    return result['responseData']['language']


def _translate(text, dest_lang, src_lang):
    # build url
    params = [('v', '1.0'),
              ('langpair', '%s|%s' % (src_lang, dest_lang)),
              ('q', text.encode('utf-8'))]
    url = _GOOGLE_TRANSLATE_BASE_URL + '?' + urllib.urlencode(params)

    # call
    page = urllib2.urlopen(url)

    # unpack the data
    result = simplejson.load(page)
    if result is None:
        raise ServiceUnavailableError('translation service unavailable')
    elif result['responseStatus'] != 200:
        raise ServiceError(result['responseDetails'])
    return result['responseData']['translatedText']

###################
# Interface
###################


@cache_method
def translate(text, dest_lang, src_lang=None):
    try:
        # split into chunks
        chunks = _get_chunks(text, '.!?, ', _GOOGLE_CHUNK_SIZE)

        # match the destination language for the call
        dest_lang = _google_lang(dest_lang)

        # match the source language for the call
        if src_lang is None:
            src_lang = _detect_lang(chunks[0])
        else:
            try:
                src_lang = _google_lang(src_lang)
            except LanguageDoesNotMatchError:
                # if wrong src_lang try to detect it
                src_lang = _detect_lang(chunks[0])

        # translate every chunk
        ret = ''
        for chunk in chunks:
            ret += _translate(chunk, dest_lang, src_lang)
        return ret
    except (ChunkSizeTooSmallError, LanguageDoesNotMatchError,
            ServiceUnavailableError, ServiceError):
        # on error return the original text
        return text


def translate_url(url, dest_lang, src_lang):
    url = url.encode('utf-8')
    params = [('hl', dest_lang),
              ('sl', src_lang),
              ('tl', dest_lang),
              ('u', url)]
    url = _GOOGLE_TRANSLATE_WEBPAGE_URL + '?' + urllib.urlencode(params)
    return url


###################
# Unit tests
###################

class TestTranslation(unittest.TestCase):
    def test__google_lang(self):
        self.assertEqual(_google_lang('English'), 'en')
        self.assertEqual(_google_lang('en-us'), 'en')
        self.assertRaises(LanguageDoesNotMatchError, _google_lang, 'cal')

    def test__detect_lang(self):
        self.assertEqual(_detect_lang('Ciao mondo'), 'it')
        self.assertEqual(_detect_lang('Hello world'), 'en')
        self.assertEqual(_detect_lang('Buna ziua'), 'ro')

    def test__translate(self):
        self.assertEqual(_translate('Ciao mondo', 'en', 'it'),
                         'Hello world')
        self.assertEqual(_translate('Buna ziua lume', 'en', 'ro'),
                         'Hello world')

    def test_translate(self):
        self.assertEqual(translate('Ciao mondo', 'en'), 'Hello world')
        self.assertEqual(translate('Buna ziua lume', 'en'), 'Hello world')
        translate(u'Frauen sind anders, M\xc3\xa4nner auch \xc2\x96 ein'
                  u' Thema f\xc3\xbcr die Umweltpolitik?', 'en')


class TestTranslateURL(unittest.TestCase):
    def test_translate_url(self):
        self.assertEqual(translate_url('https://google.com', 'it', 'en'),
                         'https://translate.google.com/translate'
                         '?hl=it&sl=en&tl=it&u=https%3A%2F%2Fgoogle.com')

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