# Copyright 2014 Google Inc. All Rights Reserved.
"""Command for creating instance templates."""
import collections

from googlecloudsdk.calliope import exceptions
from googlecloudsdk.compute.lib import base_classes
from googlecloudsdk.compute.lib import constants
from googlecloudsdk.compute.lib import image_utils
from googlecloudsdk.compute.lib import instance_utils
from googlecloudsdk.compute.lib import metadata_utils
from googlecloudsdk.compute.lib import utils


DISK_METAVAR = (
    'name=NAME [mode={ro,rw}] [boot={yes,no}] [device-name=DEVICE_NAME] '
    '[auto-delete={yes,no}]')


class Create(base_classes.BaseAsyncCreator, image_utils.ImageExpander):
  """Create Google Compute Engine virtual machine instance templates."""

  @staticmethod
  def Args(parser):
    metadata_utils.AddMetadataArgs(parser)
    instance_utils.AddDiskArgs(parser)
    instance_utils.AddLocalSsdArgs(parser)
    instance_utils.AddImageArgs(parser)
    instance_utils.AddCanIpForwardArgs(parser)
    instance_utils.AddAddressArgs(parser, instances=False)
    instance_utils.AddMachineTypeArgs(parser)
    instance_utils.AddMaintenancePolicyArgs(parser)
    instance_utils.AddNetworkArgs(parser)
    instance_utils.AddNoRestartOnFailureArgs(parser)
    instance_utils.AddScopeArgs(parser)
    instance_utils.AddTagsArgs(parser)

    parser.add_argument(
        '--description',
        help='Specifies a textual description for the instance template.')

    parser.add_argument(
        'name',
        metavar='NAME',
        help='The name of the instance template to create.')

  @property
  def service(self):
    return self.compute.instanceTemplates

  @property
  def method(self):
    return 'Insert'

  @property
  def resource_type(self):
    return 'instanceTemplates'

  def ValidateDiskFlags(self, args):
    """Validates the values of all disk-related flags.

    Args:
      args: the argparse arguments that this command was invoked with.

    Raises:
      ToolException: if any of the disk flags are invalid.
    """
    boot_disk_specified = False

    for disk in args.disk or []:
      disk_name = disk.get('name')
      if not disk_name:
        raise exceptions.ToolException(
            '[name] is missing in [--disk]. [--disk] value must be of the form '
            '[{0}].'.format(DISK_METAVAR))

      mode_value = disk.get('mode')
      if mode_value and mode_value not in ('rw', 'ro'):
        raise exceptions.ToolException(
            'Value for [mode] in [--disk] must be [rw] or [ro], not [{0}].'
            .format(mode_value))

      boot_value = disk.get('boot')
      if boot_value and boot_value not in ('yes', 'no'):
        raise exceptions.ToolException(
            'Value for [boot] in [--disk] must be [yes] or [no], not [{0}].'
            .format(boot_value))

      auto_delete_value = disk.get('auto-delete')
      if auto_delete_value and auto_delete_value not in ('yes', 'no'):
        raise exceptions.ToolException(
            'Value for [auto-delete] in [--disk] must be [yes] or [no], not '
            '[{0}].'.format(auto_delete_value))

      # If this is a boot disk and we have already seen a boot disk,
      # we need to fail because only one boot disk can be attached.
      if boot_value == 'yes':
        if boot_disk_specified:
          raise exceptions.ToolException(
              'Each instance can have exactly one boot disk. At least two '
              'boot disks were specified through [--disk].')
        else:
          boot_disk_specified = True

    if args.image and boot_disk_specified:
      raise exceptions.ToolException(
          'Each instance can have exactly one boot disk. One boot disk '
          'was specified through [--disk] and another through [--image].')

    if boot_disk_specified:
      if args.boot_disk_device_name:
        raise exceptions.ToolException(
            '[--boot-disk-device-name] can only be used when creating a new '
            'boot disk.')

      if args.boot_disk_type:
        raise exceptions.ToolException(
            '[--boot-disk-type] can only be used when creating a new boot '
            'disk.')

      if args.boot_disk_size:
        raise exceptions.ToolException(
            '[--boot-disk-size] can only be used when creating a new boot '
            'disk.')

      if args.no_boot_disk_auto_delete:
        raise exceptions.ToolException(
            '[--no-boot-disk-auto-delete] can only be used when creating a '
            'new boot disk.')

  def UseExistingBootDisk(self, args):
    """Returns True if the user has specified an existing boot disk.

    Args:
      args: the argparse arguments that this command was invoked with.

    Returns:
      bool: True if an existing boot disk is to be used, False otherwise.
    """
    return any(disk.get('boot') == 'yes' for disk in args.disk or [])

  def CreateAttachedPersistentDiskMessages(self, args):
    """Returns a list of AttachedDisk messages based on command-line args.

    Args:
      args: the argparse arguments that this command was invoked with.

    Returns:
      disks: a list of AttachedDisk message objects
    """
    disks = []

    for disk in args.disk or []:
      name = disk['name']

      # Resolves the mode.
      mode_value = disk.get('mode', 'rw')
      if mode_value == 'rw':
        mode = self.messages.AttachedDisk.ModeValueValuesEnum.READ_WRITE
      else:
        mode = self.messages.AttachedDisk.ModeValueValuesEnum.READ_ONLY

      boot = disk.get('boot') == 'yes'
      auto_delete = disk.get('auto-delete') == 'yes'

      attached_disk = self.messages.AttachedDisk(
          autoDelete=auto_delete,
          boot=boot,
          deviceName=disk.get('device-name'),
          mode=mode,
          source=name,
          type=self.messages.AttachedDisk.TypeValueValuesEnum.PERSISTENT)

      # The boot disk must end up at index 0.
      if boot:
        disks = [attached_disk] + disks
      else:
        disks.append(attached_disk)

    return disks

  def CreateDefaultBootAttachedDiskMessage(
      self, args, boot_disk_size_gb, image_uri):
    """Returns an AttachedDisk message for creating a new boot disk.

    Args:
      args: the argparse arguments that this command was invoked with.
      boot_disk_size_gb: size of the boot disk in GBs
      image_uri: the source image URI

    Returns:
      disk: an AttachedDisk message object
    """
    disk_type = None
    if args.boot_disk_type:
      disk_type = args.boot_disk_type

    return self.messages.AttachedDisk(
        autoDelete=not args.no_boot_disk_auto_delete,
        boot=True,
        deviceName=args.boot_disk_device_name,
        initializeParams=self.messages.AttachedDiskInitializeParams(