# -*- coding: utf-8 -*- #
# Copyright 2014 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 setting whether to auto-delete a disk."""

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

from apitools.base.py import encoding

from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute.instances import flags
from googlecloudsdk.core import log
from six.moves import zip


class SetDiskAutoDelete(base.UpdateCommand):
  """Set auto-delete behavior for disks.

    *${command}* is used to configure the auto-delete behavior for disks
  attached to Google Compute Engine virtual machines. When
  auto-delete is on, the persistent disk is deleted when the
  instance it is attached to is deleted.
  """

  detailed_help = {
      'EXAMPLES': """
          To enable auto-delete for a disk named 'my-disk' on an instance
          named 'my-instance', run:

            $ {command} my-instance --auto-delete --disk=my-disk

          To enable auto-delete for a device named 'my-device' on an instance
          named 'my-instance', run:

            $ {command} my-instance --auto-delete --device-name=my-device
          """,
  }

  @staticmethod
  def Args(parser):
    flags.INSTANCE_ARG.AddArgument(parser)

    parser.add_argument(
        '--auto-delete',
        action='store_true',
        default=True,
        help='Enables auto-delete for the given disk.')

    disk_group = parser.add_mutually_exclusive_group(required=True)

    disk_group.add_argument(
        '--disk',
        help="""\
        Specifies a disk to set auto-delete for by its resource name. If
        you specify a disk to set auto-delete for by persistent disk name,
        then you must not specify its device name using the
        ``--device-name'' flag.
        """)

    disk_group.add_argument(
        '--device-name',
        help="""\
        Specifies a disk to set auto-delete for by its device name,
        which is the name that the guest operating system sees. The
        device name is set at the time that the disk is attached to the
        instance, and need not be the same as the persistent disk name.
        If the disk's device name is specified, then its persistent disk
        name must not be specified using the ``--disk'' flag.
        """)

  def CreateReference(self, client, resources, args):
    return flags.INSTANCE_ARG.ResolveAsResource(
        args, resources, scope_lister=flags.GetInstanceZoneScopeLister(client))

  def GetGetRequest(self, client, instance_ref):
    return (client.apitools_client.instances,
            'Get',
            client.messages.ComputeInstancesGetRequest(**instance_ref.AsDict()))

  def GetSetRequest(self, client, instance_ref, replacement, existing):
    # Our protocol buffers are mutable, so they cannot be
    # hashed. Because of this, we cannot do a set subraction on the
    # lists of disks. Instead, the task of finding the changed disk is
    # relegated to a for-loop.
    for existing_disk, replacement_disk in zip(
        existing.disks, replacement.disks):
      if existing_disk != replacement_disk:
        changed_disk = replacement_disk

    return (client.apitools_client.instances,
            'SetDiskAutoDelete',
            client.messages.ComputeInstancesSetDiskAutoDeleteRequest(
                deviceName=changed_disk.deviceName,
                instance=instance_ref.instance,
                project=instance_ref.project,
                zone=instance_ref.zone,
                autoDelete=changed_disk.autoDelete))

  def Modify(self, resources, args, instance_ref, existing):
    replacement = encoding.CopyProtoMessage(existing)
    disk_found = False

    if args.disk:
      disk_ref = resources.Parse(
          args.disk, collection='compute.disks',
          params={
              'project': instance_ref.project,
              'zone': instance_ref.zone
          })

      for disk in replacement.disks:
        disk_rel_name = resources.ParseURL(disk.source).RelativeName()
        if disk_rel_name == disk_ref.RelativeName():
          disk.autoDelete = args.auto_delete
          disk_found = True

      if not disk_found:
        raise exceptions.ToolException(
            'Disk [{0}] is not attached to instance [{1}] in zone [{2}].'
            .format(disk_ref.Name(), instance_ref.instance, instance_ref.zone))

    else:
      for disk in replacement.disks:
        if disk.deviceName == args.device_name:
          disk.autoDelete = args.auto_delete
          disk_found = True

      if not disk_found:
        raise exceptions.ToolException(
            'No disk with device name [{0}] is attached to instance [{1}] '
            'in zone [{2}].'
            .format(args.device_name, instance_ref.instance, instance_ref.zone))

    return replacement

  def Run(self, args):
    holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    client = holder.client

    instance_ref = self.CreateReference(client, holder.resources, args)
    get_request = self.GetGetRequest(client, instance_ref)

    objects = client.MakeRequests([get_request])

    new_object = self.Modify(holder.resources, args, instance_ref, objects[0])

    # If existing object is equal to the proposed object or if
    # Modify() returns None, then there is no work to be done, so we
    # print the resource and return.
    if objects[0] == new_object:
      log.status.Print(
          'No change requested; skipping update for [{0}].'.format(
              objects[0].name))
      return objects

    return client.MakeRequests(
        [self.GetSetRequest(client, instance_ref, new_object, objects[0])])
