# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


""" Utilities for dealing with builder names. This module obtains its attributes
dynamically from builder_name_schema.json. """


import json
import os


# All of these global variables are filled in by _LoadSchema().

# The full schema.
BUILDER_NAME_SCHEMA = None

# Character which separates parts of a builder name.
BUILDER_NAME_SEP = None

# Builder roles.
BUILDER_ROLE_CANARY = 'Canary'
BUILDER_ROLE_BUILD = 'Build'
BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper'
BUILDER_ROLE_PERF = 'Perf'
BUILDER_ROLE_TEST = 'Test'
BUILDER_ROLES = (BUILDER_ROLE_CANARY,
                 BUILDER_ROLE_BUILD,
                 BUILDER_ROLE_HOUSEKEEPER,
                 BUILDER_ROLE_PERF,
                 BUILDER_ROLE_TEST)

# Suffix which distinguishes trybots from normal bots.
TRYBOT_NAME_SUFFIX = None


def _LoadSchema():
  """ Load the builder naming schema from the JSON file. """

  def _UnicodeToStr(obj):
    """ Convert all unicode strings in obj to Python strings. """
    if isinstance(obj, unicode):
      return str(obj)
    elif isinstance(obj, dict):
      return dict(map(_UnicodeToStr, obj.iteritems()))
    elif isinstance(obj, list):
      return list(map(_UnicodeToStr, obj))
    elif isinstance(obj, tuple):
      return tuple(map(_UnicodeToStr, obj))
    else:
      return obj

  builder_name_json_filename = os.path.join(
      os.path.dirname(__file__), 'builder_name_schema.json')
  builder_name_schema_json = json.load(open(builder_name_json_filename))

  global BUILDER_NAME_SCHEMA
  BUILDER_NAME_SCHEMA = _UnicodeToStr(
      builder_name_schema_json['builder_name_schema'])

  global BUILDER_NAME_SEP
  BUILDER_NAME_SEP = _UnicodeToStr(
      builder_name_schema_json['builder_name_sep'])

  global TRYBOT_NAME_SUFFIX
  TRYBOT_NAME_SUFFIX = _UnicodeToStr(
      builder_name_schema_json['trybot_name_suffix'])

  # Since the builder roles are dictionary keys, just assert that the global
  # variables above account for all of them.
  assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA)
  for role in BUILDER_ROLES:
    assert role in BUILDER_NAME_SCHEMA


_LoadSchema()


def MakeBuilderName(role, extra_config=None, is_trybot=False, **kwargs):
  schema = BUILDER_NAME_SCHEMA.get(role)
  if not schema:
    raise ValueError('%s is not a recognized role.' % role)
  for k, v in kwargs.iteritems():
    if BUILDER_NAME_SEP in v:
      raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, v))
    if not k in schema:
      raise ValueError('Schema does not contain "%s": %s' %(k, schema))
  if extra_config and BUILDER_NAME_SEP in extra_config:
    raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP,
                                                extra_config))
  name_parts = [role]
  name_parts.extend([kwargs[attribute] for attribute in schema])
  if extra_config:
    name_parts.append(extra_config)
  if is_trybot:
    name_parts.append(TRYBOT_NAME_SUFFIX)
  return BUILDER_NAME_SEP.join(name_parts)


def BuilderNameFromObject(obj, is_trybot=False):
  """Create a builder name based on properties of the given object.

  Args:
      obj: the object from which to create the builder name. The object must
          have as properties:
          - A valid builder role, as defined in the JSON file
          - All properties listed in the JSON file for that role
          - Optionally, an extra_config property
      is_trybot: bool; whether or not the builder is a trybot.
  Returns:
      string which combines the properties of the given object into a valid
          builder name.
  """
  schema = BUILDER_NAME_SCHEMA.get(obj.role)
  if not schema:
    raise ValueError('%s is not a recognized role.' % obj.role)
  name_parts = [obj.role]
  for attr_name in schema:
    attr_val = getattr(obj, attr_name)
    name_parts.append(attr_val)
  extra_config = getattr(obj, 'extra_config', None)
  if extra_config:
    name_parts.append(extra_config)
  if is_trybot:
    name_parts.append(TRYBOT_NAME_SUFFIX)
  return BUILDER_NAME_SEP.join(name_parts)


def IsTrybot(builder_name):
  """ Returns true if builder_name refers to a trybot (as opposed to a
  waterfall bot). """
  return builder_name.endswith(TRYBOT_NAME_SUFFIX)


def GetWaterfallBot(builder_name):
  """Returns the name of the waterfall bot for this builder. If it is not a
  trybot, builder_name is returned unchanged. If it is a trybot the name is
  returned without the trybot suffix."""
  if not IsTrybot(builder_name):
    return builder_name
  return _WithoutSuffix(builder_name, BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX)


def TrybotName(builder_name):
  """Returns the name of the trybot clone of this builder.

  If the given builder is a trybot, the name is returned unchanged. If not, the
  TRYBOT_NAME_SUFFIX is appended.
  """
  if builder_name.endswith(TRYBOT_NAME_SUFFIX):
    return builder_name
  return builder_name + BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX


def _WithoutSuffix(string, suffix):
  """ Returns a copy of string 'string', but with suffix 'suffix' removed.
  Raises ValueError if string does not end with suffix. """
  if not string.endswith(suffix):
    raise ValueError('_WithoutSuffix: string %s does not end with suffix %s' % (
        string, suffix))
  return string[:-len(suffix)]


def DictForBuilderName(builder_name):
  """Makes a dictionary containing details about the builder from its name."""
  split_name = builder_name.split(BUILDER_NAME_SEP)

  def pop_front():
    try:
      return split_name.pop(0)
    except:
      raise ValueError('Invalid builder name: %s' % builder_name)

  result = {'is_trybot': False}

  if split_name[-1] == TRYBOT_NAME_SUFFIX:
    result['is_trybot'] = True
    split_name.pop()

  if split_name[0] in BUILDER_NAME_SCHEMA.keys():
    key_list = BUILDER_NAME_SCHEMA[split_name[0]]
    result['role'] = pop_front()
    for key in key_list:
      result[key] = pop_front()
    if split_name:
      result['extra_config'] = pop_front()
    if split_name:
      raise ValueError('Invalid builder name: %s' % builder_name)
  else:
    raise ValueError('Invalid builder name: %s' % builder_name)
  return result