#!/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.
"""Common code used by acloud setup tools."""

from __future__ import print_function
import logging
import re
import subprocess

from acloud import errors
from acloud.internal.lib import utils


logger = logging.getLogger(__name__)

PKG_INSTALL_CMD = "sudo apt-get --assume-yes install %s"
APT_CHECK_CMD = "LANG=en_US.UTF-8 LANGUAGE=en_US:en apt-cache policy %s"
_INSTALLED_RE = re.compile(r"(.*\s*Installed:)(?P<installed_ver>.*\s?)")
_CANDIDATE_RE = re.compile(r"(.*\s*Candidate:)(?P<candidate_ver>.*\s?)")


def CheckCmdOutput(cmd, print_cmd=True, **kwargs):
    """Helper function to run subprocess.check_output.

    This function will return the command output for parsing the result and will
    raise Error if command return code was non-zero.

    Args:
        cmd: String, the cmd string.
        print_cmd: True to print cmd to stdout.
        kwargs: Other option args to subprocess.

    Returns:
        Return cmd output as a byte string.
        If the return code was non-zero it raises a CalledProcessError.
    """
    if print_cmd:
        print("Run command: %s" % cmd)

    logger.debug("Run command: %s", cmd)
    return utils.CheckOutput(cmd, **kwargs)


def InstallPackage(pkg):
    """Install package.

    Args:
        pkg: String, the name of package.

    Raises:
        PackageInstallError: package is not installed.
    """
    try:
        print(CheckCmdOutput(PKG_INSTALL_CMD % pkg,
                             shell=True,
                             stderr=subprocess.STDOUT))
    except subprocess.CalledProcessError as cpe:
        logger.error("Package install for %s failed: %s", pkg, cpe.output)
        raise errors.PackageInstallError(
            "Could not install package [" + pkg + "], :" + str(cpe.output))

    if not PackageInstalled(pkg, compare_version=False):
        raise errors.PackageInstallError(
            "Package was not detected as installed after installation [" +
            pkg + "]")


def IsPackageInAptList(pkg_name):
    """Check if the package is apt packages list.

    Args:
        pkg_name: String, the package name.

    Returns:
        True if package is in apt packages list.
    """
    try:
        pkg_info = CheckCmdOutput(
            APT_CHECK_CMD % pkg_name,
            print_cmd=False,
            shell=True,
            stderr=subprocess.STDOUT)
        if pkg_info:
            return True
        return False
    except subprocess.CalledProcessError as error:
        # Unable locate package name on repository.
        return False


def PackageInstalled(pkg_name, compare_version=True):
    """Check if the package is installed or not.

    This method will validate that the specified package is installed
    (via apt cache policy) and check if the installed version is up-to-date.

    Args:
        pkg_name: String, the package name.
        compare_version: Boolean, True to compare version.

    Returns:
        True if package is installed.and False if not installed or
        the pre-installed package is not the same version as the repo candidate
        version.

    Raises:
        UnableToLocatePkgOnRepositoryError: Unable to locate package on repository.
    """
    try:
        pkg_info = CheckCmdOutput(
            APT_CHECK_CMD % pkg_name,
            print_cmd=False,
            shell=True,
            stderr=subprocess.STDOUT)

        logger.debug("Check package install status")
        logger.debug(pkg_info)
    except subprocess.CalledProcessError as error:
        # Unable locate package name on repository.
        raise errors.UnableToLocatePkgOnRepositoryError(
            "Could not find package [" + pkg_name + "] on repository, :" +
            str(error.output) + ", have you forgotten to run 'apt update'?")

    installed_ver = None
    candidate_ver = None
    for line in pkg_info.splitlines():
        match = _INSTALLED_RE.match(line)
        if match:
            installed_ver = match.group("installed_ver").strip()
            continue
        match = _CANDIDATE_RE.match(line)
        if match:
            candidate_ver = match.group("candidate_ver").strip()
            continue

    # package isn't installed
    if installed_ver == "(none)":
        logger.debug("Package is not installed, status is (none)")
        return False
    # couldn't find the package
    if not (installed_ver and candidate_ver):
        logger.debug("Version info not found [installed: %s ,candidate: %s]",
                     installed_ver,
                     candidate_ver)
        return False
    # TODO(148116924):Setup process should ask user to update package if the
    # minimax required version is specified.
    if compare_version and installed_ver != candidate_ver:
        logger.warning("Package %s version at %s, expected %s",
                       pkg_name, installed_ver, candidate_ver)
    return True