import os
import re
import sys
import tempfile
import subprocess
from urllib.request import urlopen
from urllib.error import HTTPError, URLError

ARTIFACTS_URL = "https://autobuild.buildroot.org/artefacts/"
BASE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "../../.."))


def log_file_path(builddir, stage, logtofile=True):
    """Return path to log file"""
    return "{}-{}.log".format(builddir, stage) if logtofile else None


def open_log_file(builddir, stage, logtofile=True):
    """
    Open a file for logging and return its handler.
    If logtofile is True, returns sys.stdout. Otherwise opens a file
    with a suitable name in the build directory.
    """
    return open(log_file_path(builddir, stage, logtofile), 'a+') if logtofile else sys.stdout


def basepath(relpath=""):
    """Return the absolute path for a file or directory relative to the Buildroot top directory."""
    return os.path.join(BASE_DIR, relpath)


def filepath(relpath):
    return os.path.join(BASE_DIR, "support/testing", relpath)


def download(dldir, filename):
    finalpath = os.path.join(dldir, filename)
    if os.path.exists(finalpath):
        return finalpath

    if not os.path.exists(dldir):
        os.makedirs(dldir)

    tmpfile = tempfile.mktemp(dir=dldir)
    print("Downloading to {}".format(tmpfile))

    try:
        url_fh = urlopen(os.path.join(ARTIFACTS_URL, filename))
        with open(tmpfile, "w+b") as tmpfile_fh:
            tmpfile_fh.write(url_fh.read())
    except (HTTPError, URLError) as err:
        os.unlink(tmpfile)
        raise err

    print("Renaming from {} to {}".format(tmpfile, finalpath))
    os.rename(tmpfile, finalpath)
    return finalpath


def run_cmd_on_host(builddir, cmd):
    """Call subprocess.check_output and return the text output."""
    try:
        host_bin = os.path.join(builddir, "host", "bin")
        br_path = host_bin + os.pathsep + os.environ["PATH"]
        out = subprocess.check_output(cmd,
                                      cwd=builddir,
                                      env={
                                          "LANG": "C",
                                          "PATH": br_path
                                      },
                                      stderr=subprocess.STDOUT,
                                      text=True,
                                      universal_newlines=True)
    except subprocess.CalledProcessError as e:
        print(f"Command failed with return code {e.returncode}")
        print("=== STDOUT/STDERR ===")
        print(e.output)
        print("=====================")
        raise

    return out


def get_elf_arch_tag(builddir, prefix, fpath, tag):
    """
    Runs the cross readelf on 'fpath', then extracts the value of tag 'tag'.
    Example:
    >>> get_elf_arch_tag('output', 'arm-none-linux-gnueabi-',
                         'bin/busybox', 'Tag_CPU_arch')
    v5TEJ
    >>>
    """
    cmd = ["host/bin/{}-readelf".format(prefix),
           "-A", os.path.join("target", fpath)]
    out = run_cmd_on_host(builddir, cmd)
    regexp = re.compile(r"^  {}: (.*)$".format(tag))
    for line in out.splitlines():
        m = regexp.match(line)
        if not m:
            continue
        return m.group(1)
    return None


def get_file_arch(builddir, prefix, fpath):
    return get_elf_arch_tag(builddir, prefix, fpath, "Tag_CPU_arch")


def get_elf_prog_interpreter(builddir, prefix, fpath):
    """
    Runs the cross readelf on 'fpath' to extract the program interpreter
    name and returns it.
    Example:
    >>> get_elf_prog_interpreter('br-tests/TestExternalToolchainLinaroArm',
                                 'arm-linux-gnueabihf',
                                 'bin/busybox')
    /lib/ld-linux-armhf.so.3
    >>>
    """
    cmd = ["host/bin/{}-readelf".format(prefix),
           "-l", os.path.join("target", fpath)]
    out = run_cmd_on_host(builddir, cmd)
    regexp = re.compile(r"^ *\[Requesting program interpreter: (.*)\]$")
    for line in out.splitlines():
        m = regexp.match(line)
        if not m:
            continue
        return m.group(1)
    return None


def img_round_power2(img):
    """
    Rounds up the size of an image file to the next power of 2
    """
    sz = os.stat(img).st_size
    pow2 = 1
    while pow2 < sz:
        pow2 = pow2 << 1
    with open(img, 'ab') as f:
        f.truncate(pow2)
