# Copyright 2014 Google Inc. 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.
"""Annotates the resource types with extra information."""
from __future__ import absolute_import
from __future__ import unicode_literals
import collections

from apitools.base.protorpclite import messages

from googlecloudsdk.api_lib.compute import instance_utils
from googlecloudsdk.api_lib.compute import path_simplifier
from googlecloudsdk.api_lib.compute import property_selector
import six
import six.moves.http_client


def _FirewallRulesToCell(firewall):
  """Returns a compact string describing the firewall rules."""
  rules = []
  for allowed in firewall.get('allowed', []):
    protocol = allowed.get('IPProtocol')
    if not protocol:
      continue

    port_ranges = allowed.get('ports')
    if port_ranges:
      for port_range in port_ranges:
        rules.append('{0}:{1}'.format(protocol, port_range))
    else:
      rules.append(protocol)

  return ','.join(rules)


def _TargetPoolHealthChecksToCell(target_pool):
  """Comma-joins the names of health checks of the given target pool."""
  return ','.join(path_simplifier.Name(check) for check in
                  target_pool.get('healthChecks', []))


def _FirewallSourceRangesToCell(firewall):
  """Comma-joins the source ranges of the given firewall rule."""
  return ','.join(firewall.get('sourceRanges', []))


def _FirewallSourceTagsToCell(firewall):
  """Comma-joins the source tags of the given firewall rule."""
  return ','.join(firewall.get('sourceTags', []))


def _FirewallTargetTagsToCell(firewall):
  """Comma-joins the target tags of the given firewall rule."""
  return ','.join(firewall.get('targetTags', []))


def _ForwardingRuleTarget(forwarding_rule):
  """Gets the API-level target or backend-service of the given rule."""
  backend_service = forwarding_rule.get('backendService', None)
  if backend_service is not None:
    return backend_service
  else:
    return forwarding_rule.get('target', None)


def _StatusToCell(zone_or_region):
  """Returns status of a machine with deprecation information if applicable."""
  deprecated = zone_or_region.get('deprecated', '')
  if deprecated:
    return '{0} ({1})'.format(zone_or_region.get('status'),
                              deprecated.get('state'))
  else:
    return zone_or_region.get('status')


def _DeprecatedDateTimeToCell(zone_or_region):
  """Returns the turndown timestamp of a deprecated machine or ''."""
  deprecated = zone_or_region.get('deprecated', '')
  if deprecated:
    return deprecated.get('deleted')
  else:
    return ''


def _QuotaToCell(metric, is_integer=True):
  """Returns a function that can format the given quota as usage/limit."""

  def QuotaToCell(region):
    """Formats the metric from the parent function."""
    for quota in region.get('quotas', []):
      if quota.get('metric') != metric:
        continue

      if is_integer:
        return '{0:6}/{1}'.format(
            int(quota.get('usage')),
            int(quota.get('limit')))
      else:
        return '{0:7.2f}/{1:.2f}'.format(
            quota.get('usage'),
            quota.get('limit'))

    return ''

  return QuotaToCell


def _LocationName(instance_group):
  """Returns a location name, could be region name or zone name."""
  if 'zone' in instance_group:
    return path_simplifier.Name(instance_group['zone'])
  elif 'region' in instance_group:
    return path_simplifier.Name(instance_group['region'])
  else:
    return None


def _LocationScopeType(instance_group):
  """Returns a location scope type, could be region or zone."""
  if 'zone' in instance_group:
    return 'zone'
  elif 'region' in instance_group:
    return 'region'
  else:
    return None


def _MachineTypeMemoryToCell(machine_type):
  """Returns the memory of the given machine type in GB."""
  memory = machine_type.get('memoryMb')
  if memory:
    return '{0:5.2f}'.format(memory / 2.0 ** 10)
  else:
    return ''


def _FormatCustomMachineTypeName(mt):
  """Checks for custom machine type and modifies output.

  Args:
    mt: machine type to be formatted

  Returns:
    If mt was a custom type, then it will be formatted into the desired custom
      machine type output. Otherwise, it is returned unchanged.

  Helper function for _MachineTypeNameToCell
  """
  custom_cpu, custom_ram = instance_utils.GetCpuRamFromCustomName(mt)
  if custom_cpu and custom_ram:
    # Restricting output to 2 decimal places
    custom_ram_gb = '{0:.2f}'.format(float(custom_ram) / (2 ** 10))
    mt = 'custom ({0} vCPU, {1} GiB)'.format(custom_cpu, custom_ram_gb)
  return mt


def _MachineTypeNameToCell(machine_type):
  """Returns the formatted name of the given machine type.

  Most machine types will be untouched, with the exception of the custom machine
  type. This modifies the 'custom-N-M' custom machine types with
  'custom (N vCPU, M GiB)'.

  For example, given the following custom machine_type:

    custom-2-3500

  This function will return:

    custom (2 vCPU, 3.41 GiB)

  in the MACHINE_TYPE field when listing out the current instances.

  Args:
    machine_type: The machine type of the given instance

  Returns:
    A formatted version of the given custom machine type (as shown in example
    in docstring above).
  """
  mt = machine_type.get('properties', machine_type).get('machineType')
  if mt:
    return _FormatCustomMachineTypeName(mt)
  return mt


def FormatDescribeMachineTypeName(resources, com_path):
  """Formats a custom machine type when 'instances describe' is called.

  Args:
    resources: dict of resources available for the instance in question
    com_path: command path of the calling command

  Returns:
    If input is a custom type, returns the formatted custom machine type.
      Otherwise, returns None.
  """
  if ('instances' in com_path) and ('describe' in com_path):
    if not resources:
      return None
    if 'machineType' not in resources:
      return None
    mt_splitlist = resources['machineType'].split('/')
    mt = mt_splitlist[-1]
    if 'custom' not in mt:
      return None
    formatted_mt = _FormatCustomMachineTypeName(mt)
    mt_splitlist[-1] = formatted_mt
    return '/'.join(mt_splitlist)
  else:
    return None


def _OperationHttpStatusToCell(operation):
  """Returns the HTTP response code of the given operation."""
  if operation.get('status') == 'DONE':
    return operation.get('httpErrorStatusCode') or six.moves.http_client.OK
  else:
    return ''


def _ProjectToCell(resource):
  """Returns the project name of the given resource."""
  self_link = resource.get('selfLink')
  if self_link:
    return path_simplifier.ProjectSuffix(self_link).split('/')[0]
  else:
    return ''


def _MembersToCell(group):
  members = group.get('members')
  if members:
    return len(members)
  # Must be '0' instead of 0 to pass comparison 0 or ''.
  return '0'


def _BackendsToCell(backend_service):
  """Comma-joins the names of the backend services."""
  return ','.join(backend.get('group')
                  for backend in backend_service.get('backends', []))


def _RoutesNextHopToCell(route):
  """Returns the next hop value in a compact form."""
  if route.get('nextHopInstance'):
    return path_simplifier.ScopedSuffix(route.get('nextHopInstance'))
  elif route.get('nextHopGateway'):
    return path_simplifier.ScopedSuffix(route.get('nextHopGateway'))
  elif route.get('nextHopIp'):
    return route.get('nextHopIp')
  elif route.get('nextHopVpnTunnel'):
    return path_simplifier.ScopedSuffix(route.get('nextHopVpnTunnel'))
  elif route.get('nextHopPeering'):
    return route.get('nextHopPeering')
  else:
    return ''


def _TargetProxySslCertificatesToCell(target_proxy):
  """Joins the names of ssl certificates of the given HTTPS or SSL proxy."""
  return ','.join(path_simplifier.Name(cert) for cert in
                  target_proxy.get('sslCertificates', []))


def _ProtobufDefinitionToFields(message_class):
  """Flattens the fields in a protocol buffer definition.

  For example, given the following definition:

    message Point {
      required int32 x = 1;
      required int32 y = 2;
      optional string label = 3;
    }

    message Polyline {
      repeated Point point = 1;
      optional string label = 2;
    }

  a call to this function with the Polyline class would produce:

    ['label',
     'point[].label',
     'point[].x',
     'point[].y']

  Args:
    message_class: A class that inherits from protorpc.self.messages.Message
        and defines a protocol buffer.

  Yields:
    The flattened fields, in non-decreasing order.
  """
  for field in sorted(message_class.all_fields(), key=lambda field: field.name):
    if isinstance(field, messages.MessageField):
      for remainder in _ProtobufDefinitionToFields(field.type):
        if field.repeated:
          yield field.name + '[].' + remainder
        else:
          yield field.name + '.' + remainder
    else:
      if field.repeated:
        yield field.name + '[]'
      else:
        yield field.name


_InternalSpec = collections.namedtuple(
    'Spec',
    ['message_class_name', 'table_cols', 'transformations', 'editables'])

_SPECS_V1 = {
    'addresses': _InternalSpec(
        message_class_name='Address',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('ADDRESS', 'address'),
            ('STATUS', 'status'),
        ],
        transformations=[
            ('region', path_simplifier.Name),
            ('users[]', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'autoscalers': _InternalSpec(
        message_class_name='Autoscaler',
        table_cols=[
            ('NAME', 'name'),
            ('TARGET', 'target'),
            ('POLICY', 'autoscalingPolicy'),
        ],
        transformations=[
            ('zone', path_simplifier.Name),
            ('target', path_simplifier.Name),
        ],
        editables=None,
    ),

    'backendBuckets': _InternalSpec(
        message_class_name='BackendBucket',
        table_cols=[
            ('NAME', 'name'),
            ('GCS_BUCKET_NAME', 'bucketName'),
            ('ENABLE_CDN', 'enableCdn')
        ],
        transformations=[
            ('enableCdn', lambda x: str(x).lower()),
        ],
        editables=[
            'bucketName'
            'description',
            'enableCdn',
        ]),

    'backendServices': _InternalSpec(
        message_class_name='BackendService',
        table_cols=[
            ('NAME', 'name'),
            ('BACKENDS', _BackendsToCell),
            ('PROTOCOL', 'protocol'),
        ],
        transformations=[
            ('backends[].group', path_simplifier.ScopedSuffix),
        ],
        editables=[
            'backends',
            'description',
            'enableCDN',
            'healthChecks',
            'iap.enabled',
            'iap.oauth2ClientId',
            'iap.oauth2ClientSecret',
            'port',
            'portName',
            'protocol',
            'timeoutSec',
        ],
    ),

    'backendServiceGroupHealth': _InternalSpec(
        message_class_name='BackendServiceGroupHealth',
        table_cols=[
            ],
        transformations=[
            ('healthStatus[].instance', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'disks': _InternalSpec(
        message_class_name='Disk',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('SIZE_GB', 'sizeGb'),
            ('TYPE', 'type'),
            ('STATUS', 'status'),
        ],
        transformations=[
            ('sourceSnapshot', path_simplifier.Name),
            ('type', path_simplifier.Name),
            ('zone', path_simplifier.Name),
        ],
        editables=None,
    ),

    'diskTypes': _InternalSpec(
        message_class_name='DiskType',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('VALID_DISK_SIZES', 'validDiskSize'),
        ],
        transformations=[
            ('zone', path_simplifier.Name),
        ],
        editables=None,
    ),

    'firewalls': _InternalSpec(
        message_class_name='Firewall',
        table_cols=[
            ('NAME', 'name'),
            ('NETWORK', 'network'),
            ('SRC_RANGES', _FirewallSourceRangesToCell),
            ('RULES', _FirewallRulesToCell),
            ('SRC_TAGS', _FirewallSourceTagsToCell),
            ('TARGET_TAGS', _FirewallTargetTagsToCell),
        ],
        transformations=[
            ('network', path_simplifier.Name),
        ],
        editables=None,
    ),

    'forwardingRules': _InternalSpec(
        message_class_name='ForwardingRule',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('IP_ADDRESS', 'IPAddress'),
            ('IP_PROTOCOL', 'IPProtocol'),
            ('TARGET', _ForwardingRuleTarget),
        ],
        transformations=[
            ('region', path_simplifier.Name),
            ('target', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'groups': _InternalSpec(
        message_class_name='Group',
        table_cols=[
            ('NAME', 'name'),
            ('NUM_MEMBERS', _MembersToCell),
            ('DESCRIPTION', 'description'),
        ],
        transformations=[],
        editables=[],
    ),

    'healthChecks': _InternalSpec(
        message_class_name='HealthCheck',
        table_cols=[
            ('NAME', 'name'),
            ('PROTOCOL', 'type'),
        ],
        transformations=[],
        editables=None,
    ),

    'httpHealthChecks': _InternalSpec(
        message_class_name='HttpHealthCheck',
        table_cols=[
            ('NAME', 'name'),
            ('HOST', 'host'),
            ('PORT', 'port'),
            ('REQUEST_PATH', 'requestPath'),
        ],
        transformations=[
            ],
        editables=None,
    ),

    'httpsHealthChecks': _InternalSpec(
        message_class_name='HttpsHealthCheck',
        table_cols=[
            ('NAME', 'name'),
            ('HOST', 'host'),
            ('PORT', 'port'),
            ('REQUEST_PATH', 'requestPath'),
        ],
        transformations=[
            ],
        editables=None,
    ),

    'iap': _InternalSpec(
        message_class_name='BackendServiceIAP',
        table_cols=[
            ('NAME', 'name'),
            ('ENABLED', 'enabled'),
            ('OAUTH2_CLIENT_ID', 'oauth2ClientId'),
            ('OAUTH2_CLIENT_SECRET', 'oauth2ClientSecret'),
            ('OAUTH2_CLIENT_SECRET_SHA256', 'oauth2ClientSecretSha256'),
        ],
        transformations=[],
        editables=None,
    ),

    'images': _InternalSpec(
        message_class_name='Image',
        table_cols=[
            ('NAME', 'name'),
            ('PROJECT', _ProjectToCell),
            ('FAMILY', 'family'),
            ('DEPRECATED', 'deprecated.state'),
            ('STATUS', 'status'),
        ],
        transformations=[],
        editables=None,
    ),

    'instanceGroups': _InternalSpec(
        message_class_name='InstanceGroup',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('NETWORK', 'network'),
            ('MANAGED', 'isManaged'),
            ('INSTANCES', 'size'),
        ],
        transformations=[
            ('zone', path_simplifier.Name),
            ('size', str),
        ],
        editables=None,
    ),

    'instanceGroupManagers': _InternalSpec(
        message_class_name='InstanceGroupManager',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('BASE_INSTANCE_NAME', 'baseInstanceName'),
            ('SIZE', 'size'),
            ('TARGET_SIZE', 'targetSize'),
            ('INSTANCE_TEMPLATE', 'instanceTemplate'),
            ('AUTOSCALED', 'autoscaled'),
        ],
        transformations=[
            ('zone', path_simplifier.Name),
            ('instanceGroup', path_simplifier.Name),
            ('instanceTemplate', path_simplifier.Name),
        ],
        editables=None,
    ),

    'instances': _InternalSpec(
        message_class_name='Instance',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('MACHINE_TYPE', _MachineTypeNameToCell),
            ('PREEMPTIBLE', 'scheduling.preemptible'),
            ('INTERNAL_IP', 'networkInterfaces[].networkIP.notnull().list()'),
            ('EXTERNAL_IP',
             'networkInterfaces[].accessConfigs[0].natIP.notnull().list()'),
            ('STATUS', 'status'),
        ],
        transformations=[
            ('disks[].source', path_simplifier.Name),
            ('machineType', path_simplifier.Name),
            ('networkInterfaces[].network', path_simplifier.Name),
            ('zone', path_simplifier.Name),
        ],
        editables=None,
    ),

    'instanceTemplates': _InternalSpec(
        message_class_name='InstanceTemplate',
        table_cols=[
            ('NAME', 'name'),
            ('MACHINE_TYPE', _MachineTypeNameToCell),
            ('PREEMPTIBLE', 'properties.scheduling.preemptible'),
            ('CREATION_TIMESTAMP', 'creationTimestamp'),
        ],
        transformations=[],
        editables=None,
    ),

    'machineTypes': _InternalSpec(
        message_class_name='MachineType',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('CPUS', 'guestCpus'),
            ('MEMORY_GB', _MachineTypeMemoryToCell),
            ('DEPRECATED', 'deprecated.state'),
        ],
        transformations=[
            ('zone', path_simplifier.Name),
        ],
        editables=None,
    ),

    'networks': _InternalSpec(
        message_class_name='Network',
        table_cols=[
            ('NAME', 'name'),
            ('MODE', 'x_gcloud_mode'),
            ('IPV4_RANGE', 'IPv4Range'),
            ('GATEWAY_IPV4', 'gatewayIPv4'),
        ],
        transformations=[],
        editables=None,
    ),

    'projects': _InternalSpec(
        message_class_name='Project',
        table_cols=[],  # We do not support listing projects since
        # there is only one project (and there is no
        # API support).
        transformations=[
            ],
        editables=None,
    ),

    'operations': _InternalSpec(
        message_class_name='Operation',
        table_cols=[
            ('NAME', 'name'),
            ('TYPE', 'operationType'),
            ('TARGET', 'targetLink'),
            ('HTTP_STATUS', _OperationHttpStatusToCell),
            ('STATUS', 'status'),
            ('TIMESTAMP', 'insertTime'),
        ],
        transformations=[
            ('targetLink', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'invalidations': _InternalSpec(
        message_class_name='Operation',
        table_cols=[
            ('DESCRIPTION', 'description'),
            ('HTTP_STATUS', _OperationHttpStatusToCell),
            ('STATUS', 'status'),
            ('TIMESTAMP', 'insertTime'),
        ],
        transformations=[
            ('targetLink', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'regionBackendServices': _InternalSpec(
        message_class_name='BackendService',
        table_cols=[
            ('NAME', 'name'),
            ('BACKENDS', _BackendsToCell),
            ('PROTOCOL', 'protocol'),
        ],
        transformations=[
            ('backends[].group', path_simplifier.ScopedSuffix),
        ],
        editables=[
            'backends',
            'description',
            'enableCDN',
            'healthChecks',
            'port',
            'portName',
            'protocol',
            'timeoutSec',
        ],
    ),

    'regions': _InternalSpec(
        message_class_name='Region',
        table_cols=[
            ('NAME', 'name'),
            ('CPUS', _QuotaToCell('CPUS', is_integer=False)),
            ('DISKS_GB', _QuotaToCell('DISKS_TOTAL_GB', is_integer=True)),
            ('ADDRESSES', _QuotaToCell('IN_USE_ADDRESSES', is_integer=True)),
            ('RESERVED_ADDRESSES',
             _QuotaToCell('STATIC_ADDRESSES', is_integer=True)),
            ('STATUS', _StatusToCell),
            ('TURNDOWN_DATE', _DeprecatedDateTimeToCell),
        ],
        transformations=[
            ('zones[]', path_simplifier.Name),
        ],
        editables=None,
    ),

    'routes': _InternalSpec(
        message_class_name='Route',
        table_cols=[
            ('NAME', 'name'),
            ('NETWORK', 'network'),
            ('DEST_RANGE', 'destRange'),
            ('NEXT_HOP', _RoutesNextHopToCell),
            ('PRIORITY', 'priority'),
        ],
        transformations=[
            ('network', path_simplifier.Name),
        ],
        editables=None,
    ),

    'snapshots': _InternalSpec(
        message_class_name='Snapshot',
        table_cols=[
            ('NAME', 'name'),
            ('DISK_SIZE_GB', 'diskSizeGb'),
            ('SRC_DISK', 'sourceDisk'),
            ('STATUS', 'status'),
        ],
        transformations=[
            ('sourceDisk', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'sslCertificates': _InternalSpec(
        message_class_name='Network',
        table_cols=[
            ('NAME', 'name'),
            ('CREATION_TIMESTAMP', 'creationTimestamp'),
        ],
        transformations=[],
        editables=None,
    ),

    'subnetworks': _InternalSpec(
        message_class_name='Subnetwork',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('NETWORK', 'network'),
            ('RANGE', 'ipCidrRange')
        ],
        transformations=[
            ('network', path_simplifier.Name),
            ('region', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetHttpProxies': _InternalSpec(
        message_class_name='TargetHttpProxy',
        table_cols=[
            ('NAME', 'name'),
            ('URL_MAP', 'urlMap'),
        ],
        transformations=[
            ('urlMap', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetHttpsProxies': _InternalSpec(
        message_class_name='TargetHttpsProxy',
        table_cols=[
            ('NAME', 'name'),
            ('SSL_CERTIFICATES', _TargetProxySslCertificatesToCell),
            ('URL_MAP', 'urlMap'),
        ],
        transformations=[
            ('sslCertificates[]', path_simplifier.Name),
            ('urlMap', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetSslProxies': _InternalSpec(
        message_class_name='TargetSslProxy',
        table_cols=[
            ('NAME', 'name'),
            ('PROXY_HEADER', 'proxyHeader'),
            ('SERVICE', 'service'),
            ('SSL_CERTIFICATES', _TargetProxySslCertificatesToCell)
        ],
        transformations=[
            ('sslCertificates[]', path_simplifier.Name),
            ('service', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetInstances': _InternalSpec(
        message_class_name='TargetInstance',
        table_cols=[
            ('NAME', 'name'),
            ('ZONE', 'zone'),
            ('INSTANCE', 'instance'),
            ('NAT_POLICY', 'natPolicy'),
        ],
        transformations=[
            ('instance', path_simplifier.Name),
            ('zone', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetPoolInstanceHealth': _InternalSpec(
        message_class_name='TargetPoolInstanceHealth',
        table_cols=[
            ],
        transformations=[
            ('healthStatus[].instance', path_simplifier.ScopedSuffix),
        ],
        editables=None,
    ),

    'targetPools': _InternalSpec(
        message_class_name='TargetPool',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('SESSION_AFFINITY', 'sessionAffinity'),
            ('BACKUP', 'backupPool'),
            ('HEALTH_CHECKS', _TargetPoolHealthChecksToCell),
        ],
        transformations=[
            ('backupPool', path_simplifier.Name),
            ('healthChecks[]', path_simplifier.Name),
            ('instances[]', path_simplifier.ScopedSuffix),
            ('region', path_simplifier.Name),
        ],
        editables=None,
    ),

    'targetVpnGateways': _InternalSpec(
        message_class_name='TargetVpnGateway',
        table_cols=[
            ('NAME', 'name'),
            ('NETWORK', 'network'),
            ('REGION', 'region')
        ],
        transformations=[
            ('network', path_simplifier.Name),
            ('region', path_simplifier.Name)],
        editables=None
    ),

    'users': _InternalSpec(
        message_class_name='User',
        table_cols=[
            ('NAME', 'name'),
            ('OWNER', 'owner'),
            ('DESCRIPTION', 'description'),
        ],
        transformations=[],
        editables=[],
    ),

    'zones': _InternalSpec(
        message_class_name='Zone',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('STATUS', _StatusToCell),
            ('TURNDOWN_DATE', _DeprecatedDateTimeToCell),
        ],
        transformations=[
            ('region', path_simplifier.Name),
        ],
        editables=None,
    ),

    'vpnTunnels': _InternalSpec(
        message_class_name='VpnTunnel',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('GATEWAY', 'targetVpnGateway'),
            ('PEER_ADDRESS', 'peerIp')
        ],
        transformations=[
            ('region', path_simplifier.Name),
            ('targetVpnGateway', path_simplifier.Name)],
        editables=None
    ),

    'routers': _InternalSpec(
        message_class_name='Router',
        table_cols=[
            ('NAME', 'name'),
            ('REGION', 'region'),
            ('NETWORK', 'network'),
        ],
        transformations=[
            ('network', path_simplifier.Name),
            ('region', path_simplifier.Name),
        ],
        editables=None,
    ),
}


_SPECS_BETA = _SPECS_V1.copy()
_SPECS_BETA['backendServices'] = _InternalSpec(
    message_class_name='BackendService',
    table_cols=[
        ('NAME', 'name'),
        ('BACKENDS', _BackendsToCell),
        ('PROTOCOL', 'protocol'),
    ],
    transformations=[
        ('backends[].group', path_simplifier.ScopedSuffix),
    ],
    editables=[
        'backends',
        'description',
        'enableCDN',
        'sessionAffinity',
        'affinityCookieTTL',
        'healthChecks',
        'iap.enabled',
        'iap.oauth2ClientId',
        'iap.oauth2ClientSecret',
        'port',
        'portName',
        'protocol',
        'timeoutSec',
    ],)
_SPECS_BETA['commitments'] = _InternalSpec(
    message_class_name='Commitment',
    table_cols=[
        ('NAME', 'name'),
        ('ENDS', 'endTimestamp'),
        ('REGION', 'region'),
        ('STATUS', 'status'),
    ],
    transformations=[],
    editables=[])


_SPECS_ALPHA = _SPECS_BETA.copy()

_SPECS_ALPHA['instanceGroups'] = _InternalSpec(
    message_class_name='InstanceGroup',
    table_cols=[
        ('NAME', 'name'),
        ('LOCATION', _LocationName),
        ('SCOPE', _LocationScopeType),
        ('NETWORK', 'network'),
        ('MANAGED', 'isManaged'),
        ('INSTANCES', 'size'),
    ],
    transformations=[
        ('size', str),
    ],
    editables=None,
)
_SPECS_ALPHA['instanceGroupManagers'] = _InternalSpec(
    message_class_name='InstanceGroupManager',
    table_cols=[
        ('NAME', 'name'),
        ('LOCATION', _LocationName),
        ('SCOPE', _LocationScopeType),
        ('BASE_INSTANCE_NAME', 'baseInstanceName'),
        ('SIZE', 'size'),
        ('TARGET_SIZE', 'targetSize'),
        ('INSTANCE_TEMPLATE', 'instanceTemplate'),
        ('AUTOSCALED', 'autoscaled'),
    ],
    transformations=[
        ('instanceGroup', path_simplifier.Name),
        ('instanceTemplate', path_simplifier.Name),
    ],
    editables=None,
    )


def _GetSpecsForVersion(api_version):
  """Get Specs for the given API version.

  This currently always returns _SPECS_V1, but is left here for the future,
  as a pattern for providing different specs for different versions.

  Args:
    api_version: A string identifying the API version, e.g. 'v1'.

  Returns:
    A map associating each message class name with an _InternalSpec object.
  """
  if api_version == 'v1' or api_version == 'v2beta1':
    return _SPECS_V1
  if 'alpha' in api_version:
    return _SPECS_ALPHA
  return _SPECS_BETA


Spec = collections.namedtuple(
    'Spec',
    ['message_class', 'fields', 'table_cols', 'transformations', 'editables'])


def GetSpec(resource_type, message_classes, api_version):
  """Returns a Spec for the given resource type."""
  spec = _GetSpecsForVersion(api_version)

  if resource_type not in spec:
    raise KeyError('"%s" not found in Specs for version "%s"' %
                   (resource_type, api_version))

  spec = spec[resource_type]

  table_cols = []
  for name, action in spec.table_cols:
    if isinstance(action, six.string_types):
      table_cols.append((name, property_selector.PropertyGetter(action)))
    elif callable(action):
      table_cols.append((name, action))
    else:
      raise ValueError('expected function or property in table_cols list: {0}'
                       .format(spec))

  message_class = getattr(message_classes, spec.message_class_name)
  fields = list(_ProtobufDefinitionToFields(message_class))
  return Spec(message_class=message_class,
              fields=fields,
              table_cols=table_cols,
              transformations=spec.transformations,
              editables=spec.editables)
