# Copyright 2011 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.

"""Validation routines for discovery elements.

This is a singleton class which provides validation for names which can
appear in discovery documents
"""

__author__ = 'wclarkso@google.com (Will Clarkson)'

import re
import sys


# The first character must alpha (a-zA-Z), a slash (/), a '@', or an
# underscore (no single digits).  Subsequent characters can be alpha
# numeric. We also permit them to have a slash(/), underscore (_), dot
# (.) or dash (-).  NOTE: the '$' character is to get around $ref or foo$bar
# variable names in some APIs.
_VARNAME_REGEX = re.compile(
    r'^[a-zA-Z]$|([a-zA-Z_/$@][a-zA-Z0-9_./$-]+)$')

_API_NAME_REGEX = re.compile(r'[a-z][a-zA-Z0-9_]*$')
_API_VERSION_REGEX = re.compile(r'[a-z0-9][a-zA-Z0-9._-]*$')


class ValidationError(ValueError):
  pass


def Validate(name):
  """Validates the name against a regular expression object.

  If the name matches the regular expression, we return nothing.
  If the name fails to match, we generate an exception.

  Args:
    name: (str) name of variable or method
  Returns:
    (str) The name.

  Raises:
    ValidationError: An Error if name does not conform to style
  """
  if _VARNAME_REGEX.match(name) is None:
    raise ValidationError(
        'Variable %s does not conform to style guide' % name)
  return name


def ValidateApiName(api_name):
  """Validates a potential API name.

  An API name must match the regular expression[a-z0-9][a-zA-Z0-9_]*

  Args:
    api_name: (str) The API name to check.
  Returns:
    (str) The api name.

  Raises:
    ValidationError: An Error if name does not conform to style
  """
  if _API_NAME_REGEX.match(api_name) is None:
    raise ValidationError(
        'API name %s does not conform to style guide' % api_name)
  return api_name


def ValidateApiVersion(api_version):
  """Validates a potential API version.

  An API version must match the regular expression[a-z0-9][a-zA-Z0-9.]*

  Args:
    api_version: (str) The API version to check.
  Returns:
    (str) The api version.

  Raises:
    ValidationError: An Error if version does not conform to style
  """
  if _API_VERSION_REGEX.match(api_version) is None:
    raise ValidationError(
        'API version %s does not conform to style guide' % api_version)
  return api_version


def ValidateAndSanitizeComment(comment_string):
  """Validates a comment string.

  Remove comment terminators (e.g. */) from a string.

  TODO(user): Make a per-language validator. Allow non-dangerous symbols
  depending on language. e.g. */ is OK for Python but not PHP

  Args:
    comment_string: (str|unicode) input comment string
  Returns:
    (unicode) String with invalid character sequences removed
  """
  # Strip anything which is known to be a comment terminator in any
  # supported language.
  invalid_strings = [u'/*',    # C-style Multi-line start
                     u'*/',    # C-style Multi-line end
                     u'\"""',  # Python Multiline string
                     u'///',   # Escaped comment begin
                     u'\\*',   # Escaped Multiline begin
                    ]

  # Hacky python 2 vs. 3 portability
  if ((sys.version_info[0] == 2 and isinstance(comment_string, str))
      or (sys.version_info[0] == 3 and isinstance(comment_string, bytes))):
    comment_string = comment_string.decode('utf-8')
  change_made = True
  while change_made:
    change_made = False
    # Save original length for easy comparision later
    beginning_length = len(comment_string)

    for substring in invalid_strings:
      # Replace all instances of substring with empty string
      comment_string = comment_string.replace(substring, '')

    # If the length of the string changed, then a replacement occurred.
    # We need to repeat the process until no changes occur
    if len(comment_string) != beginning_length:
      change_made = True

  return comment_string
