import errno
import logging
import os
import sys
from typing import Iterator
from urllib.request import urlopen

log = logging.getLogger(__name__)


def iter_data_dirs(check_writable: bool = False) -> Iterator[str]:
    try:
        yield os.environ["PYAV_TESTDATA_DIR"]
    except KeyError:
        pass

    if os.name == "nt":
        yield os.path.join(sys.prefix, "pyav", "datasets")
        return

    bases = [
        "/usr/local/share",
        "/usr/local/lib",
        "/usr/share",
        "/usr/lib",
    ]

    # Prefer the local virtualenv.
    if hasattr(sys, "real_prefix"):
        bases.insert(0, sys.prefix)

    for base in bases:
        dir_ = os.path.join(base, "pyav", "datasets")
        if check_writable:
            if os.path.exists(dir_):
                if not os.access(dir_, os.W_OK):
                    continue
            else:
                if not os.access(base, os.W_OK):
                    continue
        yield dir_

    yield os.path.join(os.path.expanduser("~"), ".pyav", "datasets")


def cached_download(url: str, name: str) -> str:
    """Download the data at a URL, and cache it under the given name.

    The file is stored under `pyav/test` with the given name in the directory
    :envvar:`PYAV_TESTDATA_DIR`, or the first that is writeable of:

    - the current virtualenv
    - ``/usr/local/share``
    - ``/usr/local/lib``
    - ``/usr/share``
    - ``/usr/lib``
    - the user's home

    """

    clean_name = os.path.normpath(name)
    if clean_name != name:
        raise ValueError(f"{name} is not normalized.")

    for dir_ in iter_data_dirs():
        path = os.path.join(dir_, name)
        if os.path.exists(path):
            return path

    dir_ = next(iter_data_dirs(True))
    path = os.path.join(dir_, name)

    log.info(f"Downloading {url} to {path}")

    response = urlopen(url)
    if response.getcode() != 200:
        raise ValueError(f"HTTP {response.getcode()}")

    dir_ = os.path.dirname(path)
    try:
        os.makedirs(dir_)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise

    tmp_path = path + ".tmp"
    with open(tmp_path, "wb") as fh:
        while True:
            chunk = response.read(8196)
            if chunk:
                fh.write(chunk)
            else:
                break

    os.rename(tmp_path, path)

    return path


def fate(name: str) -> str:
    """Download and return a path to a sample from the FFmpeg test suite.

    Data is handled by :func:`cached_download`.

    See the `FFmpeg Automated Test Environment <https://www.ffmpeg.org/fate.html>`_

    """
    return cached_download(
        "http://fate.ffmpeg.org/fate-suite/" + name,
        os.path.join("fate-suite", name.replace("/", os.path.sep)),
    )


def curated(name: str) -> str:
    """Download and return a path to a sample that is curated by the PyAV developers.

    Data is handled by :func:`cached_download`.

    """
    return cached_download(
        "https://pyav.org/datasets/" + name,
        os.path.join("pyav-curated", name.replace("/", os.path.sep)),
    )
