#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2012 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

"""A script to check the website for new versions of Ubuntu One."""

from __future__ import print_function

try:
    from urllib.request import urlopen
    from urllib.parse import urljoin
except ImportError:
    from urllib2 import urlopen
    from urlparse import urljoin

import argparse
import hashlib
import json
import os
import subprocess
import sys
import tempfile

from dirspec.utils import get_program_path

from ubuntuone.controlpanel.logger import setup_logging


logger = setup_logging("updater")

U1SDTOOL_EXECUTABLE = 'u1sdtool'
DARWIN_APP_NAMES = {U1SDTOOL_EXECUTABLE: 'U1SDTool.app'}


if sys.platform == "darwin":
    from Cocoa import NSRunningApplication
    # IDs to terminate: Leave syncdaemon off this list, it is
    # terminated separately.
    BUNDLE_IDS = ["com.ubuntu.sso.login-qt",
                  "com.ubuntu.sso.ssl-cert",
                  "com.ubuntu.sso.login",
                  "com.ubuntu.one.proxy-tunnel",
                  "com.ubuntu.one.u1sdtool",
                  "com.ubuntu.one.updater",
                  "com.ubuntu.one.controlpanel",
                  "com.ubuntu.one.menu"]


def _do_download(url):
    """Download a URL and return the data it contains."""
    resource = urlopen(url)
    logger.info("Downloading {}".format(url))
    data = resource.read()
    logger.info("Downloaded {}".format(url))
    return data


def _save_download(url, checksum):
    """Given URL data, save it to a file and return the file name.

    The resource is saved to a file which is the responsibility of callers
    to cleanup. It was created with tempfile.mkstemp."""
    data = _do_download(url)

    md5 = hashlib.md5()
    md5.update(data)
    digest = md5.hexdigest()
    if checksum != digest:
        logger.error("Checksum mismatch. Expected {}, computed {}".format(
                     checksum, digest))
        raise Exception("Checksum mismatch")

    fd, name = tempfile.mkstemp()
    with os.fdopen(fd, "wb") as fobj:
        fobj.write(data)

    return name


def run_install(url, checksum):
    try:
        args = [_save_download(url, checksum)]
        if sys.platform == 'darwin':
            args.insert(0, 'open')
        subprocess.Popen(args)
    except Exception as exc:
        logger.error("Unable to install {}: {}".format(url, exc))
        return False
    return True


def check(args):
    """Check a URL and return True if a new version is available."""
    error = (False, "", "")     # Default to no new release.

    try:
        data = _do_download(args.url)
    except Exception as exc:
        logger.error("Download failed: {}".format(exc))
        return error

    try:
        info = json.loads(data)
        available = info[args.platform][args.release]
        avail_ver = available["version"]
        upgrade_available = avail_ver > args.current
        if upgrade_available:
            logger.info("A new {} version is available at {}.".format(
                        args.release, available["url"]))
        elif avail_ver == args.current:
            logger.info("Version {} is the latest {}.".format(args.current,
                                                              args.release))
        else:
            logger.info("Installed version {} newer than advertised"
                  " available version {}.".format(args.current, avail_ver))

    except Exception as exc:
        # Catch everything here.
        # KeyError is most likely, but we can't let this take us down.
        logger.error("Exception while reading {}: {}, {}".format(
                     args.url, exc.__class__, exc.message))
        return error

    return upgrade_available, available["url"], available["checksum"]


def kill_running_processes():
    """Kill any running u1 processes."""
    if sys.platform != "darwin":
        return
    logger.info("Killing running processes")

    try:
        u1sdtoolpath = get_program_path(U1SDTOOL_EXECUTABLE,
                                        app_names=DARWIN_APP_NAMES)
    except Exception as e:
        logger.error("exception getting u1sdtool's path: {}".format(e))
    else:
        try:
            out = subprocess.check_output([u1sdtoolpath, '-q'])
            logger.info("u1sdtool said: {}".format(out))
        except Exception as e:
            logger.error("exception running {} -q: {}".format(u1sdtoolpath, e))

    for id in BUNDLE_IDS:
        NSRA = NSRunningApplication
        apps = NSRA.runningApplicationsWithBundleIdentifier_(id)
        logger.info("id: {} apps: {}".format(id, apps))

        for app in apps:
            app.terminate()


def main(*args):
    """Return 1 if the action was successful, 0 if not.

    The default option is to check that an update is available.
    Adding the --install option will download and run
    the update if available."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--install", action="store_true")
    parser.add_argument("--platform", type=str, default=sys.platform)
    parser.add_argument("--release", type=str, required=True)
    parser.add_argument("--current", type=int, required=True)
    parser.add_argument("--url", type=str, required=True)

    args = parser.parse_args()

    new_available, file, checksum = check(args)
    if not args.install:
        return 1 if new_available else 0

    if args.install and new_available:
        installer = urljoin(args.url, file)
        logger.info("Downloading and installing {}".format(installer))
        kill_running_processes()
        return 1 if run_install(installer, checksum) else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[:]))
