import logging
import time

from Queue import Empty

from .reporting import EventTypes as et
from .tasks import DownloadTask, PrepareAutoUpdateTask
from .util import assert_


logger = logging.getLogger(__name__)


def check_and_process_auto_updates(connector, tags, current_version, usage_reporter):
    # cleanup old residue. This might fail with
    # an exception - but that's ok, because it means
    # there is an old prepped update lying around
    # that could cause havoc by deleting e.g. Live.exe
    connector.cleanup_residue()

    logger.info("Checking for auto updates from %s", current_version)
    usage_reporter.log(et.CHECK_FOR_UPDATES, current_version=current_version, tags=tags)

    versions_to_deltas = connector.check_for_auto_updates(tags, current_version, None)

    if not versions_to_deltas:
        logger.info("No update found; terminating")
        usage_reporter.log(et.NO_UPDATES_AVAILABLE)
        return []

    # for now, we only allow *one* version, the topmost,
    # to be returned here!
    assert_(
        lambda: len(versions_to_deltas.keys()) == 1,
        "more than one version to upgrade to returned!",
    )

    to_version = versions_to_deltas.keys()[0]
    logger.info("Found update to %s", to_version)
    usage_reporter.log(et.UPDATE_FOUND, version=to_version)

    ipc_channel = connector.live_ipc_channel

    if ipc_channel:
        ipc_channel.send("update_available", to_version)

        if connector.wait_for_confirmation:
            logger.info(
                "Queued 'update_available' message. Waiting for response from Live..."
            )
            update_confirmed = wait_for_confirmation_from_live(ipc_channel)

            if not update_confirmed:
                return []

    logger.info("Update confirmed. Starting download...")

    update_info = connector.load_update_info()

    # prepare default structures
    update_info["prepared_version"] = None

    from_versions = update_info.setdefault("versions", {})
    delta_infos = update_info.setdefault("delta_infos", {})

    to_versions = from_versions.setdefault(current_version, {})

    # we don't want this to be a skipped version
    if to_version in to_versions:
        vi = to_versions[to_version]
        if vi["status"] == "skipped":
            return []

    deltas = versions_to_deltas[to_version]

    assert_(lambda: len(deltas), "There must really be deltas associated with a version")

    # store the version-update specific
    # information, especially that the
    # version update is not yet available

    to_versions[to_version] = dict(
        deltas=[delta["name"] for delta in deltas], status="downloading"
    )

    download_tasks = []

    delta_dir = connector.auto_updates_base() / "deltas"

    if not delta_dir.exists():
        delta_dir.mkdir()

    for d in deltas:
        deltaname = d.pop("name")
        checksum, version = d["checksum"], d["version"]
        delta_infos[deltaname] = d

        delta = delta_dir / deltaname
        if delta.exists() and delta.md5() == checksum:
            logger.debug("Found and verified %r", delta)
        else:
            logger.debug("Task to download %r", delta)
            if delta.exists():
                delta.remove()
            download_tasks.append(DownloadTask(connector, delta, checksum))

    connector.save_update_info(update_info)

    return download_tasks + [
        PrepareAutoUpdateTask(connector, current_version, to_version, download_tasks)
    ]


def wait_for_confirmation_from_live(ipc_channel):
    """
    Block until a `continue` or `cancel` message is sent from Live

    Return `True` if `continue` or `False` if `cancel`
    """
    while True:
        time.sleep(1.0)
        message = ipc_channel.receive_message()

        if message == "cancel":
            logger.info("Update canceled by Live; terminating")
            return False
        elif message == "continue":
            return True
