# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Classes for runtime handling of concept arguments."""

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

from googlecloudsdk.calliope import parser_errors
from googlecloudsdk.calliope.concepts import util
from googlecloudsdk.core import exceptions
import six


class Error(exceptions.Error):
  """Base class for errors in this module."""


class ParseError(Error):
  """Raised if a concept fails to parse."""

  def __init__(self, presentation_name, message):
    msg = 'Error parsing [{}].\n{}'.format(presentation_name, message)
    super(ParseError, self).__init__(msg)


class RepeatedConceptName(Error):
  """Raised when adding a concept if one with the given name already exists."""

  def __init__(self, concept_name):
    msg = 'Repeated concept name [{}].'.format(concept_name)
    super(RepeatedConceptName, self).__init__(msg)


class RuntimeHandler(object):
  """A handler to hold information about all concept arguments in a command.

  The handler is assigned to 'CONCEPTS' in the argparse namespace and has an
  attribute to match the name of each concept argument in lower snake case.
  """

  def __init__(self):
    # This is set by the ArgumentInterceptor later.
    self.parsed_args = None
    self._arg_name_lookup = {}
    self._all_concepts = []

  def ParsedArgs(self):
    """Basically a lazy property to use during lazy concept parsing."""
    return self.parsed_args

  def AddConcept(self, name, concept_info, required=True):
    """Adds a concept handler for a given concept.

    Args:
      name: str, the name to be used for the presentation spec.
      concept_info: ConceptInfo, the object that holds dependencies of the
        concept.
      required: bool, True if the concept must be parseable, False if not.

    Raises:
      RepeatedConceptName: If the given "name" has already been used with a
        concept.
    """
    # pylint: disable=g-import-not-at-top
    from googlecloudsdk.calliope.concepts import concepts
    # pylint: enable=g-import-not-at-top

    class LazyParse(object):
      """Class provided when accessing a concept to lazily parse from args."""

      def __init__(self, parse, arg_getter):
        self.parse = parse
        self.arg_getter = arg_getter

      def Parse(self):
        try:
          return self.parse(self.arg_getter())
        except concepts.InitializationError as e:
          if required:
            raise ParseError(name, six.text_type(e))
          return None

    if hasattr(self, name):
      raise RepeatedConceptName(name)
    setattr(self, name, LazyParse(concept_info.Parse, self.ParsedArgs))
    self._all_concepts.append({
        'name': name,
        'concept_info': concept_info,
        'required': required,
    })
    for _, arg_name in six.iteritems(concept_info.attribute_to_args_map):
      self._arg_name_lookup[util.NormalizeFormat(arg_name)] = concept_info

  def ArgNameToConceptInfo(self, arg_name):
    return self._arg_name_lookup.get(util.NormalizeFormat(arg_name))

  def Reset(self):
    for concept_details in self._all_concepts:
      concept_details['concept_info'].ClearCache()

  def GetValue(self, dest):
    """Returns the value of the argument registered for dest.

    Based on argparse.Namespace.GetValue().

    Args:
      dest: The dest of a registered argument.

    Raises:
      UnknownDestinationException: If no arg is registered for dest.

    Returns:
      The value of the argument registered for dest.
    """
    try:
      return getattr(self, dest)
    except AttributeError:
      raise parser_errors.UnknownDestinationException(
          'No registered concept arg for destination [{}].'.format(dest))
