# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this,
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks.
from __future__ import print_function

import errno
import os
import stat
import subprocess
import sys

# These are the platform and build-tools versions for building
# mobile/android, respectively. Try to keep these in synch with the
# build system and Mozilla's automation.
ANDROID_TARGET_SDK = '23'
ANDROID_BUILD_TOOLS_VERSION = '23.0.3'

# These are the "Android packages" needed for building Firefox for Android.
# Use |android list sdk --extended| to see these identifiers.
ANDROID_PACKAGES = [
    'tools',
    'platform-tools',
    'build-tools-%s' % ANDROID_BUILD_TOOLS_VERSION,
    'android-%s' % ANDROID_TARGET_SDK,
    'extra-google-m2repository',
    'extra-android-m2repository',
]

ANDROID_NDK_EXISTS = '''
Looks like you have the Android NDK installed at:
%s
'''

ANDROID_SDK_EXISTS = '''
Looks like you have the Android SDK installed at:
%s
We will install all required Android packages.
'''

NOT_INSTALLING_ANDROID_PACKAGES = '''
It looks like you already have the following Android packages:
%s
No need to update!
'''

INSTALLING_ANDROID_PACKAGES = '''
We are now installing the following Android packages:
%s
You may be prompted to agree to the Android license. You may see some of
output as packages are downloaded and installed.
'''

MISSING_ANDROID_PACKAGES = '''
We tried to install the following Android packages:
%s
But it looks like we couldn't install:
%s
Install these Android packages manually and run this bootstrapper again.
'''

MOBILE_ANDROID_MOZCONFIG_TEMPLATE = '''
Paste the lines between the chevrons (>>> and <<<) into your mozconfig file:

<<<
# Build Firefox for Android:
ac_add_options --enable-application=mobile/android
ac_add_options --target=arm-linux-androideabi

# With the following Android SDK and NDK:
ac_add_options --with-android-sdk="%s"
ac_add_options --with-android-ndk="%s"
>>>
'''

MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE = '''
Paste the lines between the chevrons (>>> and <<<) into your mozconfig file:

<<<
# Build Firefox for Android Artifact Mode:
ac_add_options --enable-application=mobile/android
ac_add_options --target=arm-linux-androideabi
ac_add_options --enable-artifact-builds

# With the following Android SDK:
ac_add_options --with-android-sdk="%s"

# Write build artifacts to:
mk_add_options MOZ_OBJDIR=./objdir-frontend
>>>
'''


def check_output(*args, **kwargs):
    """Run subprocess.check_output even if Python doesn't provide it."""
    from base import BaseBootstrapper
    fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output)

    return fn(*args, **kwargs)


def list_missing_android_packages(android_tool, packages):
    '''
    Use the given |android| tool to return the sub-list of Android
    |packages| given that are not installed.
    '''
    missing = []

    # There's no obvious way to see what's been installed already,
    # but packages that are installed don't appear in the list of
    # available packages.
    lines = check_output([android_tool,
                          'list', 'sdk', '--no-ui', '--extended']).splitlines()

    # Lines look like: 'id: 59 or "extra-google-simulators"'
    for line in lines:
        is_id_line = False
        try:
            is_id_line = line.startswith("id:")
        except:
            # Some lines contain non-ASCII characters.  Ignore them.
            pass
        if not is_id_line:
            continue

        for package in packages:
            if '"%s"' % package in line:
                # Not installed!
                missing.append(package)

    return missing


def install_mobile_android_sdk_or_ndk(url, path):
    '''
    Fetch an Android SDK or NDK from |url| and unpack it into
    the given |path|.

    We expect wget to be installed and found on the system path.

    We use, and wget respects, https.  We could also include SHAs for a
    small improvement in the integrity guarantee we give. But this script is
    bootstrapped over https anyway, so it's a really minor improvement.

    We use |wget --continue| as a cheap cache of the downloaded artifacts,
    writing into |path|/mozboot.  We don't yet clean the cache; it's better
    to waste disk and not require a long re-download than to wipe the cache
    prematurely.
    '''

    old_path = os.getcwd()
    try:
        download_path = os.path.join(path, 'mozboot')
        try:
            os.makedirs(download_path)
        except OSError as e:
            if e.errno == errno.EEXIST and os.path.isdir(download_path):
                pass
            else:
                raise

        os.chdir(download_path)
        subprocess.check_call(['wget', '--continue', url])
        file = url.split('/')[-1]

        os.chdir(path)
        abspath = os.path.join(download_path, file)
        if file.endswith('.tar.gz') or file.endswith('.tgz'):
            cmd = ['tar', 'zxf', abspath]
        elif file.endswith('.tar.bz2'):
            cmd = ['tar', 'jxf', abspath]
        elif file.endswith('.zip'):
            cmd = ['unzip', '-q', abspath]
        elif file.endswith('.bin'):
            # Execute the .bin file, which unpacks the content.
            mode = os.stat(path).st_mode
            os.chmod(abspath, mode | stat.S_IXUSR)
            cmd = [abspath]
        else:
            raise NotImplementedError("Don't know how to unpack file: %s" % file)

        print('Unpacking %s...' % abspath)

        with open(os.devnull, "w") as stdout:
            # These unpack commands produce a ton of output; ignore it.  The
            # .bin files are 7z archives; there's no command line flag to quiet
            # output, so we use this hammer.
            subprocess.check_call(cmd, stdout=stdout)

        print('Unpacking %s... DONE' % abspath)

    finally:
        os.chdir(old_path)


def ensure_android_sdk_and_ndk(path, sdk_path, sdk_url, ndk_path, ndk_url, artifact_mode):
    '''
    Ensure the Android SDK and NDK are found at the given paths.  If not, fetch
    and unpack the SDK and/or NDK from the given URLs into |path|.
    '''

    # It's not particularly bad to overwrite the NDK toolchain, but it does take
    # a while to unpack, so let's avoid the disk activity if possible.  The SDK
    # may prompt about licensing, so we do this first.
    # Check for Android NDK only if we are not in artifact mode.
    if not artifact_mode:
        if os.path.isdir(ndk_path):
            print(ANDROID_NDK_EXISTS % ndk_path)
        else:
            install_mobile_android_sdk_or_ndk(ndk_url, path)

    # We don't want to blindly overwrite, since we use the |android| tool to
    # install additional parts of the Android toolchain.  If we overwrite,
    # we lose whatever Android packages the user may have already installed.
    if os.path.isdir(sdk_path):
        print(ANDROID_SDK_EXISTS % sdk_path)
    else:
        install_mobile_android_sdk_or_ndk(sdk_url, path)


def ensure_android_packages(android_tool, packages=None):
    '''
    Use the given android tool (like 'android') to install required Android
    packages.
    '''

    if not packages:
        packages = ANDROID_PACKAGES

    # Bug 1171232: The |android| tool behaviour has changed; we no longer can
    # see what packages are installed easily.  Force installing everything until
    # we find a way to actually see the missing packages.
    missing = packages
    if not missing:
        print(NOT_INSTALLING_ANDROID_PACKAGES % ', '.join(packages))
        return

    # This tries to install all the required Android packages.  The user
    # may be prompted to agree to the Android license.
    print(INSTALLING_ANDROID_PACKAGES % ', '.join(missing))
    subprocess.check_call([android_tool,
                           'update', 'sdk', '--no-ui', '--all',
                           '--filter', ','.join(missing)])

    # Bug 1171232: The |android| tool behaviour has changed; we no longer can
    # see what packages are installed easily.  Don't check until we find a way
    # to actually verify.
    failing = []
    if failing:
        raise Exception(MISSING_ANDROID_PACKAGES % (', '.join(missing), ', '.join(failing)))


def suggest_mozconfig(sdk_path=None, ndk_path=None, artifact_mode=False):
    if artifact_mode:
        print(MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE % (sdk_path))
    else:
        print(MOBILE_ANDROID_MOZCONFIG_TEMPLATE % (sdk_path, ndk_path))


def android_ndk_url(os_name, ver='r11b'):
    # Produce a URL like 'https://dl.google.com/android/repository/android-ndk-r11b-linux-x86_64.zip
    base_url = 'https://dl.google.com/android/repository/android-ndk'

    if sys.maxsize > 2**32:
        arch = 'x86_64'
    else:
        arch = 'x86'

    return '%s-%s-%s-%s.zip' % (base_url, ver, os_name, arch)
