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

from __future__ import absolute_import
import argparse
import optparse

from py_utils import camel_case


class ArgumentHandlerMixIn(object):
  """A structured way to handle command-line arguments.

  In AddCommandLineArgs, add command-line arguments.
  In ProcessCommandLineArgs, validate them and store them in a private class
  variable. This way, each class encapsulates its own arguments, without needing
  to pass an arguments object around everywhere.
  """

  @classmethod
  def AddCommandLineArgs(cls, parser):
    """Override to accept custom command-line arguments."""

  @classmethod
  def ProcessCommandLineArgs(cls, parser, args):
    """Override to process command-line arguments.

    We pass in parser so we can call parser.error()."""


class Command(ArgumentHandlerMixIn):
  """An abstraction for things that run from the command-line."""

  @classmethod
  def Name(cls):
    return camel_case.ToUnderscore(cls.__name__)

  @classmethod
  def Description(cls):
    if cls.__doc__:
      return cls.__doc__.splitlines()[0]
    else:
      return ''

  def Run(self, args):
    raise NotImplementedError()

  @classmethod
  def main(cls, args=None):
    """Main method to run this command as a standalone script."""
    parser = argparse.ArgumentParser()
    cls.AddCommandLineArgs(parser)
    args = parser.parse_args(args=args)
    cls.ProcessCommandLineArgs(parser, args)
    return min(cls().Run(args), 255)


# TODO: Convert everything to argparse.
class OptparseCommand(Command):
  usage = ''

  @classmethod
  def CreateParser(cls):
    return optparse.OptionParser('%%prog %s %s' % (cls.Name(), cls.usage),
                                 description=cls.Description())

  @classmethod
  def AddCommandLineArgs(cls, parser, environment):
    # pylint: disable=arguments-differ
    pass

  @classmethod
  def ProcessCommandLineArgs(cls, parser, args, environment):
    # pylint: disable=arguments-differ
    pass

  def Run(self, args):
    raise NotImplementedError()

  @classmethod
  def main(cls, args=None):
    """Main method to run this command as a standalone script."""
    parser = cls.CreateParser()
    cls.AddCommandLineArgs(parser, None)
    options, args = parser.parse_args(args=args)
    options.positional_args = args
    cls.ProcessCommandLineArgs(parser, options, None)
    return min(cls().Run(options), 255)


class SubcommandCommand(Command):
  """Combines Commands into one big command with sub-commands.

  E.g. "svn checkout", "svn update", and "svn commit" are separate sub-commands.

  Example usage:
    class MyCommand(command_line.SubcommandCommand):
      commands = (Help, List, Run)

    if __name__ == '__main__':
      sys.exit(MyCommand.main())
  """

  commands = ()

  @classmethod
  def AddCommandLineArgs(cls, parser):
    subparsers = parser.add_subparsers()

    for command in cls.commands:
      subparser = subparsers.add_parser(
          command.Name(), help=command.Description())
      subparser.set_defaults(command=command)
      command.AddCommandLineArgs(subparser)

  @classmethod
  def ProcessCommandLineArgs(cls, parser, args):
    args.command.ProcessCommandLineArgs(parser, args)

  def Run(self, args):
    return args.command().Run(args)
