#!/usr/bin/env python
#
# Copyright 2018 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Action to create goldfish device instances.

A Goldfish device is an emulated android device based on the android
emulator.
"""
import logging
import os
import re

from acloud import errors
from acloud.public.actions import base_device_factory
from acloud.public.actions import common_operations
from acloud.internal import constants
from acloud.internal.lib import android_build_client
from acloud.internal.lib import auth
from acloud.internal.lib import goldfish_compute_client
from acloud.internal.lib import utils


logger = logging.getLogger(__name__)

_EMULATOR_INFO_FILENAME = "emulator-info.txt"
_SYSIMAGE_INFO_FILENAME = "android-info.txt"
_VERSION_PATTERN = r"version-.*=(\d+)"


class GoldfishDeviceFactory(base_device_factory.BaseDeviceFactory):
    """A class that can produce a goldfish device.

    Attributes:
        _cfg: An AcloudConfig instance.
        _build_target: String, the build target, e.g. aosp_x86-eng.
        _build_id: String, Build id, e.g. "2263051", "P2804227"
        _emulator_build_target: String, the emulator build target, e.g. aosp_x86-eng.
        _emulator_build_id: String, emulator build id.
        _gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
        _blank_data_disk_size_gb: Integer, extra disk size
        _build_client: An AndroidBuildClient instance
        _branch: String, android branch name, e.g. git_master
        _emulator_branch: String, emulator branch name, e.g. "aosp-emu-master-dev"
    """
    LOG_FILES = ["/home/vsoc-01/emulator.log",
                 "/home/vsoc-01/log/logcat.log",
                 "/home/vsoc-01/log/adb.log",
                 "/var/log/daemon.log"]

    def __init__(self,
                 cfg,
                 build_target,
                 build_id,
                 emulator_build_target,
                 emulator_build_id,
                 kernel_build_id=None,
                 kernel_branch=None,
                 kernel_build_target=None,
                 gpu=None,
                 avd_spec=None,
                 tags=None,
                 branch=None,
                 emulator_branch=None):

        """Initialize.

        Args:
            cfg: An AcloudConfig instance.
            build_target: String, the build target, e.g. aosp_x86-eng.
            build_id: String, Build id, e.g. "2263051", "P2804227"
            emulator_build_target: String, the emulator build target, e.g. aosp_x86-eng.
            emulator_build_id: String, emulator build id.
            gpu: String, GPU to attach to the device or None. e.g. "nvidia-tesla-k80"
            avd_spec: An AVDSpec instance.
            tags: A list of tags to associate with the instance. e.g.
                  ["http-server", "https-server"]
            branch: String, branch of the emulator build target.
            emulator_branch: String, branch of the emulator.
        """

        self.credentials = auth.CreateCredentials(cfg)

        compute_client = goldfish_compute_client.GoldfishComputeClient(
            cfg, self.credentials)
        super().__init__(compute_client)

        # Private creation parameters
        self._cfg = cfg
        self._gpu = gpu
        self._avd_spec = avd_spec
        self._blank_data_disk_size_gb = cfg.extra_data_disk_size_gb
        self._extra_scopes = cfg.extra_scopes
        self._tags = tags

        # Configure clients
        self._build_client = android_build_client.AndroidBuildClient(
            self.credentials)

        # Get build info
        self.build_info = self._build_client.GetBuildInfo(
            build_target, build_id, branch)
        self.emulator_build_info = self._build_client.GetBuildInfo(
            emulator_build_target, emulator_build_id, emulator_branch)
        self.kernel_build_info = self._build_client.GetBuildInfo(
            kernel_build_target or cfg.kernel_build_target, kernel_build_id,
            kernel_branch)

    def GetBuildInfoDict(self):
        """Get build info dictionary.

        Returns:
          A build info dictionary
        """
        build_info_dict = {
            key: val for key, val in utils.GetDictItems(self.build_info) if val}

        build_info_dict.update(
            {"emulator_%s" % key: val
             for key, val in utils.GetDictItems(self.emulator_build_info) if val}
            )

        build_info_dict.update(
            {"kernel_%s" % key: val
             for key, val in utils.GetDictItems(self.kernel_build_info) if val}
            )

        return build_info_dict

    def CreateInstance(self):
        """Creates single configured goldfish device.

        Override method from parent class.

        Returns:
            String, the name of the created instance.
        """
        instance = self._compute_client.GenerateInstanceName(
            build_id=self.build_info.build_id,
            build_target=self.build_info.build_target)

        self._compute_client.CreateInstance(
            instance=instance,
            image_name=self._cfg.stable_goldfish_host_image_name,
            image_project=self._cfg.stable_goldfish_host_image_project,
            build_target=self.build_info.build_target,
            branch=self.build_info.branch,
            build_id=self.build_info.build_id,
            emulator_branch=self.emulator_build_info.branch,
            emulator_build_id=self.emulator_build_info.build_id,
            emulator_build_target=self.emulator_build_info.build_target,
            kernel_branch=self.kernel_build_info.branch,
            kernel_build_id=self.kernel_build_info.build_id,
            kernel_build_target=self.kernel_build_info.build_target,
            gpu=self._gpu,
            blank_data_disk_size_gb=self._blank_data_disk_size_gb,
            avd_spec=self._avd_spec,
            tags=self._tags,
            extra_scopes=self._extra_scopes,
            launch_args=self._cfg.launch_args)

        return instance


def ParseBuildInfo(filename):
    """Parse build id based on a substring.

    This will parse a file which contains build information to be used. For an
    emulator build, the file will contain the information about the
    corresponding stable system image build id. In emulator-info.txt, the file
    will contains the information about the corresponding stable emulator
    build id, for example "require version-emulator=5292001". In
    android-info.txt, the file will contains the information about a stable
    system image build id, for example
    "version-sysimage-git_pi-dev-sdk_gphone_x86_64-userdebug=4833817"

    Args:
        filename: Name of file to parse.

    Returns:
        Build id parsed from the file based on pattern
        Returns None if pattern not found in file
    """
    with open(filename) as build_info_file:
        for line in build_info_file:
            match = re.search(_VERSION_PATTERN, line)
            if match:
                return match.group(1)
    return None


def _FetchBuildIdFromFile(cfg, build_target, build_id, filename):
    """Parse and fetch build id from a file based on a pattern.

    Verify if one of the system image or emulator binary build id is missing.
    If found missing, then update according to the resource file.

    Args:
        cfg: An AcloudConfig instance.
        build_target: Target name.
        build_id: Build id, a string, e.g. "2263051", "P2804227"
        filename: Name of file containing the build info.

    Returns:
        A build id or None
    """
    build_client = android_build_client.AndroidBuildClient(
        auth.CreateCredentials(cfg))

    with utils.TempDir() as tempdir:
        temp_filename = os.path.join(tempdir, filename)
        build_client.DownloadArtifact(build_target,
                                      build_id,
                                      filename,
                                      temp_filename)

        return ParseBuildInfo(temp_filename)


#pylint: disable=too-many-locals
def CreateDevices(avd_spec=None,
                  cfg=None,
                  build_target=None,
                  build_id=None,
                  emulator_build_id=None,
                  emulator_branch=None,
                  emulator_build_target=None,
                  kernel_build_id=None,
                  kernel_branch=None,
                  kernel_build_target=None,
                  gpu=None,
                  num=1,
                  serial_log_file=None,
                  autoconnect=False,
                  branch=None,
                  tags=None,
                  report_internal_ip=False,
                  boot_timeout_secs=None):
    """Create one or multiple Goldfish devices.

    Args:
        avd_spec: An AVDSpec instance.
        cfg: An AcloudConfig instance.
        build_target: String, the build target, e.g. aosp_x86-eng.
        build_id: String, Build id, e.g. "2263051", "P2804227"
        branch: String, Branch name for system image.
        emulator_build_id: String, emulator build id.
        emulator_branch: String, emulator branch name.
        emulator_build_target: String, emulator build target.
        gpu: String, GPU to attach to the device or None. e.g. "nvidia-k80"
        kernel_build_id: Kernel build id, a string.
        kernel_branch: Kernel branch name, a string.
        kernel_build_target: Kernel build artifact, a string.
        num: Integer, Number of devices to create.
        serial_log_file: String, A path to a file where serial output should
                        be saved to.
        autoconnect: Boolean, Create ssh tunnel(s) and adb connect after device
                     creation.
        branch: String, Branch name for system image.
        tags: A list of tags to associate with the instance. e.g.
              ["http-server", "https-server"]
        report_internal_ip: Boolean to report the internal ip instead of
                            external ip.
        boot_timeout_secs: Integer, the maximum time in seconds used to
                           wait for the AVD to boot.

    Returns:
        A Report instance.
    """
    client_adb_port = None
    if avd_spec:
        cfg = avd_spec.cfg
        build_target = avd_spec.remote_image[constants.BUILD_TARGET]
        build_id = avd_spec.remote_image[constants.BUILD_ID]
        branch = avd_spec.remote_image[constants.BUILD_BRANCH]
        num = avd_spec.num
        emulator_build_id = avd_spec.emulator_build_id
        emulator_build_target = avd_spec.emulator_build_target
        gpu = avd_spec.gpu
        serial_log_file = avd_spec.serial_log_file
        autoconnect = avd_spec.autoconnect
        report_internal_ip = avd_spec.report_internal_ip
        client_adb_port = avd_spec.client_adb_port
        boot_timeout_secs = avd_spec.boot_timeout_secs

    if not emulator_build_target:
        emulator_build_target = cfg.emulator_build_target

    # If emulator_build_id and emulator_branch is None, retrieve emulator
    # build id from platform build emulator-info.txt artifact
    # Example: require version-emulator=5292001
    if not emulator_build_id and not emulator_branch:
        logger.info("emulator_build_id not provided. "
                    "Attempting to get %s from build %s/%s.", _EMULATOR_INFO_FILENAME,
                    build_id, build_target)
        emulator_build_id = _FetchBuildIdFromFile(cfg,
                                                  build_target,
                                                  build_id,
                                                  _EMULATOR_INFO_FILENAME)

    if not emulator_build_id:
        raise errors.CommandArgError("Emulator build id not found "
                                     "in %s" % _EMULATOR_INFO_FILENAME)

    # If build_id and branch is None, retrieve build_id from
    # emulator build android-info.txt artifact
    # Example: version-sysimage-git_pi-dev-sdk_gphone_x86_64-userdebug=4833817
    if not build_id and not branch:
        build_id = _FetchBuildIdFromFile(cfg,
                                         emulator_build_target,
                                         emulator_build_id,
                                         _SYSIMAGE_INFO_FILENAME)

    if not build_id:
        raise errors.CommandArgError("Emulator system image build id not found "
                                     "in %s" % _SYSIMAGE_INFO_FILENAME)
    logger.info(
        "Creating a goldfish device in project %s, build_target: %s, "
        "build_id: %s, emulator_bid: %s, emulator_branch: %s, kernel_build_id: %s, "
        "kernel_branch: %s, kernel_build_target: %s, GPU: %s, num: %s, "
        "serial_log_file: %s, "
        "autoconnect: %s", cfg.project, build_target, build_id,
        emulator_build_id, emulator_branch, kernel_build_id, kernel_branch,
        kernel_build_target, gpu, num, serial_log_file, autoconnect)

    device_factory = GoldfishDeviceFactory(
        cfg, build_target, build_id,
        emulator_build_target,
        emulator_build_id, gpu=gpu,
        avd_spec=avd_spec, tags=tags,
        branch=branch,
        emulator_branch=emulator_branch,
        kernel_build_id=kernel_build_id,
        kernel_branch=kernel_branch,
        kernel_build_target=kernel_build_target)

    return common_operations.CreateDevices("create_gf", cfg, device_factory,
                                           num, constants.TYPE_GF,
                                           report_internal_ip, autoconnect,
                                           serial_log_file, client_adb_port,
                                           boot_timeout_secs)
