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

"""The cli_tree command help document markdown generator.

This module generates command help markdown from the tree generated by:

  gcloud --quiet alpha  # make sure the alpha component is installed
  gcloud --quiet beta   # make sure the beta component is installed
  gcloud meta list-gcloud --format=json |
  python -c "
    import json
    import sys
    data = json.load(sys.stdin)
    print 'gcloud_tree =', data" > gcloud_tree.py

Usage:

  from googlecloudsdk.calliope import cli_tree_markdown
  from googlecloudsdk.command_lib.shell import gcloud_tree

  command = <command node in gcloud tree>
  flag = <flag node in gcloud tree>
  generator = cli_tree_markdown.CliTreeMarkdownGenerator(command, gcloud_tree)
  generator.PrintSynopsisSection()
  generator.PrintFlagDefinition(flag)
    ...
  markdown = generator.Edit()
"""

import argparse

from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import markdown
from googlecloudsdk.calliope import usage_text
from googlecloudsdk.core import properties


def _GetReleaseTrackFromId(release_id):
  """Returns the base.ReleaseTrack for release_id."""
  if release_id == 'INTERNAL':
    release_id = 'GA'
  return base.ReleaseTrack.FromId(release_id)


def GroupAttr(d):
  """Returns a group attr object suitable for the calliope.markdown module."""
  return type('attr', (object,), d)


def Flag(d, is_global=False):
  """Returns a flag object suitable for the calliope.markdown module."""
  flag = type('flag', (object,), d)
  flag.help = argparse.SUPPRESS if flag.hidden else flag.description
  flag.is_global = is_global
  flag.dest = flag.value.lower().replace('-', '_')
  flag.metavar = flag.value
  flag.option_strings = [flag.name]

  if flag.type == 'bool':
    flag.nargs = 0
  else:
    flag.nargs = 1
  if flag.type == 'dict':
    flag.type = arg_parsers.ArgDict()
  elif flag.type == 'list':
    flag.type = arg_parsers.ArgList()
  elif flag.type == 'string':
    flag.type = None
  if not flag.group:
    flag.group = flag.name

  if flag.attr.get('inverted_synopsis'):
    flag.inverted_synopsis = True
  prop = flag.attr.get('property')
  if prop:
    if 'value' in prop:
      kind = 'value'
      value = prop['value']
    else:
      value = None
      kind = 'bool' if flag.type == 'bool' else None
    flag.store_property = (properties.FromString(prop['name']), kind, value)

  return flag


def Positional(d):
  """Returns a positional object suitable for the calliope.markdown module."""
  positional = type('positional', (object,), d)
  positional.help = positional.description
  positional.hidden = False
  positional.dest = positional.value.lower().replace('-', '_')
  positional.metavar = positional.value
  positional.option_strings = [positional.name]
  try:
    positional.nargs = int(positional.nargs)
  except ValueError:
    pass
  return positional


class CliTreeMarkdownGenerator(markdown.MarkdownGenerator):
  """cli_tree command help markdown document generator.

  Attributes:
    _capsule: The help text capsule.
    _command: The tree node for command.
    _command_path: The command path list.
    _tree: The (sub)tree root.
    _sections: The help text sections indexed by SECTION name.
    _subcommands: The dict of subcommand help indexed by subcommand name.
    _subgroups: The dict of subgroup help indexed by subcommand name.
  """

  def __init__(self, command, tree):
    """Constructor.

    Args:
      command: The command node in the root tree.
      tree: The (sub)tree root.
    """
    self._tree = tree
    self._command = command
    self._command_path = command['path']
    self._subcommands = self.GetSubCommandHelp()
    self._subgroups = self.GetSubGroupHelp()
    super(CliTreeMarkdownGenerator, self).__init__(
        self._command_path,
        _GetReleaseTrackFromId(self._command['release']),
        self._command['hidden'])
    self._capsule = self._command['capsule']
    self._sections = self._command['sections']

  @staticmethod
  def FlagGroupSortKey(flags):
    """Returns a flag group sort key for sorted()."""
    return [len(flags) > 1] + sorted([flag.name for flag in flags])

  @staticmethod
  def IsHidden(arg):
    """Returns True if arg is hidden."""
    return arg.hidden

  def _GetCommandFromPath(self, command_path):
    """Returns the command node for command_path."""
    commands = self._tree['commands']
    for name in command_path:
      if name not in commands:
        return None
      commands = commands[name]['commands']
    return commands

  def IsValidSubPath(self, command_path):
    """Returns True if the given command path is valid."""
    return bool(self._GetCommandFromPath(command_path))

  def GetPositionalArgs(self):
    """Returns the command positional args."""
    return [Positional(value) for value in self._command['positionals']]

  def _GetAllFlagsAndGroupAttr(self):
    """Returns the flag group attrs and list of all flags for the command."""
    flags = {}
    groups = {}
    commands = self._tree['commands']
    for name in self._command_path[1:]:
      if name not in commands:
        break
      flags.update(commands[name]['flags'])
      groups.update(commands[name]['groups'])
      commands = commands[name]['commands']
    return ([Flag(flag, is_global=True)
             for flag in self._tree['flags'].values()] +
            [Flag(flag) for flag in flags.values()],
            {name: GroupAttr(attr) for name, attr in groups.iteritems()})

  def GetArgDetails(self, arg):
    """Returns the help text with auto-generated details for arg.

    The help text was already generated on the cli_tree generation side.

    Args:
      arg: The arg to auto-generate help text for.

    Returns:
      The help text with auto-generated details for arg.
    """
    return arg.help

  def GetFlagGroups(self):
    """Returns (group, group_attr, has_global_flags)."""
    # Place all flag groups into a dict. Flags that are in a mutually
    # exclusive group are mapped group_id -> [flags]. All other flags
    # are mapped dest -> [flag].
    has_global_flags = False
    groups = {}
    flags, group_attr = self._GetAllFlagsAndGroupAttr()
    for flag in flags:
      if not self._is_root and flag.is_global:
        has_global_flags = True
      else:
        group_id = flag.group
        if group_id not in groups:
          groups[group_id] = []
        groups[group_id].append(flag)
    return groups, group_attr, has_global_flags

  def _GetSubHelp(self, is_group=False):
    """Returns the help dict indexed by command for sub commands or groups."""
    return {name: usage_text.HelpInfo(
        help_text=subcommand['capsule'],
        is_hidden=subcommand['hidden'],
        release_track=_GetReleaseTrackFromId(subcommand['release']))
            for name, subcommand in self._command['commands'].iteritems()
            if subcommand['group'] == is_group}

  def GetSubCommandHelp(self):
    """Returns the subcommand help dict indexed by subcommand."""
    return self._GetSubHelp(is_group=False)

  def GetSubGroupHelp(self):
    """Returns the subgroup help dict indexed by subgroup."""
    return self._GetSubHelp(is_group=True)

  def PrintFlagDefinition(self, flag, disable_header=False):
    """Prints a flags definition list item."""
    if isinstance(flag, dict):
      flag = Flag(flag)
    super(CliTreeMarkdownGenerator, self).PrintFlagDefinition(
        flag, disable_header=disable_header)

  def _ExpandHelpText(self, doc):
    """{...} references were done when the tree was generated."""
    return doc


def Markdown(command, tree):
  """Returns the help markdown document string for the command node in tree.

  Args:
    command: The command node in the root tree.
    tree: The (sub)tree root.

  Returns:
    The markdown document string.
  """
  return CliTreeMarkdownGenerator(command, tree).Generate()
