# -*- coding: utf-8 -*- #
# Copyright 2019 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.

"""Helpers to separate release tracks in declarative commands."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import copy

from googlecloudsdk.calliope import base

ALL_TRACKS = [t.id for t in base.ReleaseTrack.AllValues()]
RELEASE_TRACKS = 'release_tracks'
GROUP = 'group'


class DoesNotExistForTrackError(Exception):
  pass


# This function should be kept in sync with function in
# cloud/sdk/surface_specs/release_tracks.py
def _SetValuesForTrack(obj, track):
  """Recursively modify an object to have only values for the provided track.

  Args:
    obj: The object to modify.
    track: The track to extract the values for.
  Returns:
    The modified object
  Raises:
    DoesNotExistForTrackError: if the object does not exist for the track.
  """
  if isinstance(obj, dict):
    is_group = GROUP in obj
    # Check if it exists for this track, and raise an Exception if it doesn't.
    if RELEASE_TRACKS in obj:
      if track not in obj[RELEASE_TRACKS]:
        raise DoesNotExistForTrackError()
      del obj[RELEASE_TRACKS]
    # Copy tracked properties for this track to the object itself.
    if track in obj:
      for key, value in obj[track].items():
        obj[key] = value
    # Remove all tracked properties
    for track_key in ALL_TRACKS:
      if track_key in obj:
        del obj[track_key]
    # Recursively update all children.
    # Remove them if they don't exist for the track.
    for key, child in list(obj.items()):
      try:
        _SetValuesForTrack(child, track)
      except DoesNotExistForTrackError:
        del obj[key]
    if is_group and not obj:
      # All of the children have been omitted for an arg group nested under the
      # `group` key.
      raise DoesNotExistForTrackError()
  elif isinstance(obj, list):
    # Recursively update all children.
    # Remove them if they don't exist for the track.
    children = list(obj)
    obj[:] = []
    for child in children:
      try:
        obj.append(_SetValuesForTrack(child, track))
      except DoesNotExistForTrackError:
        pass
  return obj


def SeparateDeclarativeCommandTracks(command_impls):
  """Separate combined track definitions.

  If a file does not specify tracks, the same implementation may be used for
  all track implementations the command is present in.

  Args:
    command_impls: A single or list of declarative command implementation(s).
  Yields:
    One implementation for each distinct track implmentation in a file.
  """
  if not isinstance(command_impls, list):
    command_impls = [command_impls]
  for impl in command_impls:
    release_tracks = impl.get(RELEASE_TRACKS, [])
    if not release_tracks:
      yield impl
    else:
      for track in release_tracks:
        track_impl = copy.deepcopy(impl)
        try:
          _SetValuesForTrack(track_impl, track)
        except DoesNotExistForTrackError:
          # The implementation doesn't have any keys left.
          # continue
          pass
        track_impl[RELEASE_TRACKS] = [track]
        yield track_impl
