# -*- coding: utf-8 -*- #
# Copyright 2018 Google LLC. All Rights Reserved.
#
# 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.
"""Command for diffing image packages."""


from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.containeranalysis import util as containeranalysis_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute.images.packages import exceptions
from googlecloudsdk.command_lib.compute.images.packages import filter_utils
from googlecloudsdk.command_lib.compute.images.packages import flags as package_flags


class Diff(base.ListCommand):
  """ Displays the version differences of packages between two images."""

  @staticmethod
  def Args(parser):
    parser.display_info.AddFormat("""\
       table(
          name:label=PACKAGE,
          version_base:label=VERSION_BASE,
          revision_base:label=REVISION_BASE,
          version_diff:label=VERSION_DIFF,
          revision_diff:label=REVISION_DIFF
        )""")
    # Add resource flags.
    package_flags.AddResourceArgs(parser)
    # Add filter flags.
    package_flags.AddShowAddedPackagesFlag(parser)
    package_flags.AddShowRemovedPackagesFlag(parser)
    package_flags.AddShowUpdatedPackagesFlag(parser)

    Diff._parser = parser

  def _GetVersions(self, image_packages, image_name):
    package_versions = {}
    for occurrence in image_packages:
      package_name = occurrence.installation.name
      versions = []
      for location in occurrence.installation.location:
        versions.append((location.version.name, location.version.revision))
      package_versions[package_name] = versions

    if not package_versions:
      raise exceptions.ImagePackagesInfoUnavailableException(image_name)

    return package_versions

  def _GetDiff(self, args, package_versions_base, package_versions_diff):
    all_package_names = set(package_versions_base.keys()).union(
        set(package_versions_diff.keys()))

    show_all_diff_packages = True
    if (args.show_added_packages or args.show_removed_packages or
        args.show_updated_packages):
      show_all_diff_packages = False

    diff = []
    empty = ('-', '-')
    for package_name in all_package_names:
      versions_base = package_versions_base.get(package_name, [])
      versions_diff = package_versions_diff.get(package_name, [])
      if set(versions_base) != set(versions_diff):
        len_base = len(versions_base)
        len_diff = len(versions_diff)
        if (show_all_diff_packages or
            (args.show_added_packages and len_base == 0 and len_diff != 0) or
            (args.show_removed_packages and len_base != 0 and len_diff == 0) or
            (args.show_updated_packages and len_base != 0 and len_diff != 0)):
          for idx in range(max(len_base, len_diff)):
            version_base, revision_base = versions_base[idx] if (
                idx < len_base) else empty
            version_diff, revision_diff = versions_diff[idx] if (
                idx < len_diff) else empty
            package_diff = {
                'name': package_name,
                'version_base': version_base,
                'revision_base': revision_base,
                'version_diff': version_diff,
                'revision_diff': revision_diff
            }
            diff.append(package_diff)

    return sorted(diff, key=lambda package_diff: package_diff['name'])

  def Run(self, args):
    """Yields the differences of packages between two images."""
    # If not specified, both base project and diff project are the user project.

    base_image_ref = args.CONCEPTS.base_image.Parse()
    diff_image_ref = args.CONCEPTS.diff_image.Parse()

    # Use GA to construct the compute API holder since the containeranalysis
    # API always call compute v1 API to refer the compute resources.
    holder = base_classes.ComputeApiHolder(base.ReleaseTrack.GA)
    resource_filter_base = filter_utils.GetFilter(base_image_ref, holder)
    resource_filter_diff = filter_utils.GetFilter(diff_image_ref, holder)

    image_packages_base = containeranalysis_util.MakeOccurrenceRequest(
        project_id=base_image_ref.project, resource_filter=resource_filter_base,
        occurrence_filter=None, resource_urls=None)

    image_packages_diff = containeranalysis_util.MakeOccurrenceRequest(
        project_id=diff_image_ref.project, resource_filter=resource_filter_diff,
        occurrence_filter=None, resource_urls=None)

    package_versions_base = self._GetVersions(image_packages_base,
                                              args.base_image)
    package_versions_diff = self._GetVersions(image_packages_diff,
                                              args.diff_image)

    return self._GetDiff(args, package_versions_base, package_versions_diff)
