#!/usr/bin/env python
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>


import errno
import json
import os
import tarfile
from functools import lru_cache
from io import BytesIO

from calibre.constants import cache_dir
from calibre.ptempfile import TemporaryDirectory
from calibre.utils.localization import lang_as_iso639_1
from calibre.utils.lock import ExclusiveFile
from calibre.utils.resources import get_path as P


def locale_map():
    ans = getattr(locale_map, 'ans', None)
    if ans is None:
        ans = locale_map.ans = {k.lower(): v for k, v in json.loads(P('hyphenation/locales.json', data=True)).items()}
    return ans


@lru_cache
def dictionary_name_for_locale(loc):
    loc = loc.lower().replace('-', '_')
    lmap = locale_map()
    if loc in lmap:
        return lmap[loc]
    parts = loc.split('_')
    if len(parts) > 2:
        loc = '_'.join(parts[:2])
        if loc in lmap:
            return lmap[loc]
    loc = lang_as_iso639_1(parts[0])
    if not loc:
        return
    if loc in lmap:
        return lmap[loc]
    if loc == 'en':
        return lmap['en_us']
    if loc == 'de':
        return lmap['de_de']
    if loc == 'es':
        return lmap['es_es']
    q = loc + '_'
    for k, v in lmap.items():
        if k.startswith(q):
            return v


@lru_cache(maxsize=2)
def expected_hash():
    return P('hyphenation/sha1sum', data=True, allow_user_override=False)


def extract_dicts(cache_path):
    dict_tarball = P('hyphenation/dictionaries.tar.xz', allow_user_override=False)
    with TemporaryDirectory(dir=cache_path) as tdir:
        try:
            from calibre_lzma.xz import decompress
        except ImportError:
            tf = tarfile.open(dict_tarball)
        else:
            buf = BytesIO()
            with open(dict_tarball, 'rb') as f:
                data = f.read()
            decompress(data, outfile=buf)
            buf.seek(0)
            tf = tarfile.TarFile(fileobj=buf)
        with tf:
            try:
                tf.extractall(tdir, filter='data')
            except TypeError:
                tf.extractall(tdir)
        with open(os.path.join(tdir, 'sha1sum'), 'wb') as f:
            f.write(expected_hash())
        dest = os.path.join(cache_path, 'f')
        with TemporaryDirectory(dir=cache_path) as trash:
            try:
                os.rename(dest, os.path.join(trash, 'f'))
            except OSError as err:
                if err.errno != errno.ENOENT:
                    raise
            os.rename(tdir, dest)
    is_cache_up_to_date.updated = True


def is_cache_up_to_date(cache_path):
    if getattr(is_cache_up_to_date, 'updated', False):
        return True
    try:
        with open(os.path.join(cache_path, 'f', 'sha1sum'), 'rb') as f:
            actual_hash = f.read()
        if actual_hash == expected_hash():
            is_cache_up_to_date.updated = True
            return True
    except OSError:
        pass
    return False


@lru_cache
def get_cache_path(cd):
    cache_path = os.path.join(cd, 'hyphenation')
    try:
        os.makedirs(cache_path)
    except OSError as err:
        if err.errno != errno.EEXIST:
            raise
    return cache_path


def path_to_dictionary(dictionary_name, cache_callback=None):
    cd = getattr(path_to_dictionary, 'cache_dir', None) or cache_dir()
    cache_path = get_cache_path(cd)
    with ExclusiveFile(os.path.join(cache_path, 'lock')):
        if not is_cache_up_to_date(cache_path):
            extract_dicts(cache_path)
            if cache_callback is not None:
                cache_callback()
    return os.path.join(cache_path, 'f', dictionary_name)
