import os
import stat
import time
import xbmc
import shutil
import urllib2
import threading
import traceback
import subprocess
from quasar.logger import log
from quasar.osarch import PLATFORM
from quasar.config import QUASARD_HOST
from quasar.addon import ADDON, ADDON_ID
from quasar.util import notify, system_information, getLocalizedString

def ensure_exec_perms(file_):
    st = os.stat(file_)
    os.chmod(file_, st.st_mode | stat.S_IEXEC)
    return file_

def android_get_current_appid():
    with open("/proc/%d/cmdline" % os.getpid()) as fp:
        return fp.read().rstrip("\0")

def get_quasard_checksum(path):
    with open(path) as fp:
        fp.seek(-40, os.SEEK_END)  # we put a sha1 there
        return fp.read()

def get_quasar_binary():
    binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")

    binary_dir = os.path.join(ADDON.getAddonInfo("path"), "resources", "bin", "%(os)s_%(arch)s" % PLATFORM)
    if PLATFORM["os"] == "android":
        log.info("Detected binary folder: %s" % binary_dir)
        binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy")
        if os.path.exists(binary_dir_legacy):
            binary_dir = binary_dir_legacy
        log.info("Using binary folder: %s" % binary_dir)
        app_id = android_get_current_appid()
        xbmc_data_path = os.path.join("/data", "data", app_id)
        dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID, "bin", "%(os)s_%(arch)s" % PLATFORM)
    else:
        dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")), "bin", "%(os)s_%(arch)s" % PLATFORM)

    try:
        binary_dir = binary_dir.decode("latin1")
        dest_binary_dir = dest_binary_dir.decode("latin1")
    except UnicodeEncodeError:
        log.info("Unable to decode: binary_dir=%s dest_binary_dir=%s" % (repr(binary_dir), repr(dest_binary_dir)))

    binary_path = os.path.join(binary_dir, binary)
    dest_binary_path = os.path.join(dest_binary_dir, binary)

    if not os.path.exists(binary_path):
        notify(getLocalizedString(30103))
        system_information()
        return False, False

    if not os.path.exists(dest_binary_path) or get_quasard_checksum(dest_binary_path) != get_quasard_checksum(binary_path):
        log.info("Updating quasar daemon...")
        try:
            os.makedirs(dest_binary_dir)
        except OSError:
            pass
        try:
            shutil.rmtree(dest_binary_dir)
        except OSError as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            pass
        try:
            shutil.copytree(binary_dir, dest_binary_dir)
        except OSError as e:
            log.error("Unable to copy to destination path for update: %s" % e)
            notify(getLocalizedString(30227))
            system_information()
            pass

    # Clean stale files in the directory, as this can cause headaches on
    # Android when they are unreachable
    dest_files = set(os.listdir(dest_binary_dir))
    orig_files = set(os.listdir(binary_dir))
    log.info("Deleting stale files %s" % (dest_files - orig_files))
    for file_ in (dest_files - orig_files):
        path = os.path.join(dest_binary_dir, file_)
        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.remove(path)

    return dest_binary_dir, ensure_exec_perms(dest_binary_path)

def clear_fd_inherit_flags():
    # Ensure the spawned quasar binary doesn't inherit open files from Kodi
    # which can break things like addon updates. [WINDOWS ONLY]
    from ctypes import windll

    HANDLE_RANGE = xrange(0, 65536)
    HANDLE_FLAG_INHERIT = 1
    FILE_TYPE_DISK = 1

    for hd in HANDLE_RANGE:
        if windll.kernel32.GetFileType(hd) == FILE_TYPE_DISK:
            if not windll.kernel32.SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0):
                log.error("Error clearing inherit flag, disk file handle %x" % hd)

def start_quasard(**kwargs):
    # Make sure all other quasard instances are closed
    shutdown()

    quasar_dir, quasar_binary = get_quasar_binary()

    if quasar_dir is False or quasar_binary is False:
        return False

    SW_HIDE = 0
    STARTF_USESHOWWINDOW = 1

    args = [quasar_binary]
    kwargs["cwd"] = quasar_dir

    if PLATFORM["os"] == "windows":
        si = subprocess.STARTUPINFO()
        si.dwFlags = STARTF_USESHOWWINDOW
        si.wShowWindow = SW_HIDE
        clear_fd_inherit_flags()
        kwargs["startupinfo"] = si
    else:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "%s:%s" % (quasar_dir, env.get("LD_LIBRARY_PATH", ""))
        kwargs["env"] = env
        kwargs["close_fds"] = True

    return subprocess.Popen(args, **kwargs)

def shutdown():
    try:
        urllib2.urlopen(QUASARD_HOST + "/shutdown")
    except:
        pass

def wait_for_abortRequested(proc, monitor):
    monitor.closing.wait()
    log.info("quasard: exiting quasard daemon")
    try:
        proc.terminate()
    except OSError:
        pass  # Process already exited, nothing to terminate
    log.info("quasard: quasard daemon exited")

def quasard_thread(monitor):
    crash_count = 0
    try:
        while not xbmc.abortRequested:
            log.info("quasard: starting quasard")
            proc = start_quasard(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            if not proc:
                break
            threading.Thread(target=wait_for_abortRequested, args=[proc, monitor]).start()

            if PLATFORM["os"] == "windows":
                while proc.poll() is None:
                    log.info(proc.stdout.readline())
            else:
                # Kodi hangs on some Android (sigh...) systems when doing a blocking
                # read. We count on the fact that Quasar daemon flushes its log
                # output on \n, creating a pretty clean output
                import fcntl
                import select
                fd = proc.stdout.fileno()
                fl = fcntl.fcntl(fd, fcntl.F_GETFL)
                fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
                while proc.poll() is None:
                    try:
                        to_read, _, _ = select.select([proc.stdout], [], [])
                        for ro in to_read:
                            line = ro.readline()
                            if line == "":  # write end is closed
                                break
                            log.info(line)
                    except IOError:
                        time.sleep(1)  # nothing to read, sleep

            if proc.returncode == 0 or xbmc.abortRequested:
                break

            crash_count += 1
            notify(getLocalizedString(30100), time=3000)
            xbmc.executebuiltin("Dialog.Close(all, true)")
            system_information()
            time.sleep(3)
            if crash_count >= 3:
                notify(getLocalizedString(30110), time=3000)
                break

    except Exception as e:
        notify("%s: %s" % (getLocalizedString(30226), repr(e).encode('utf-8')))
        map(log.error, traceback.format_exc().split("\n"))
        raise
