# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# Version: $Id$
import getopt
import sys
import logging
from gnuviechadmin.exceptions import GnuviechadminError


class CliCommand:
    """Base class for command line interface.

    A specific implementation class must define the fields name,
    description and _optionmap.

    The field name is the name of the subcommand.

    The field description is a short, one line description of the
    command.

    The field _optionmap is a map which maps the subcommand names to
    lists of tuples. Each tuple consists of four elements. The first
    element is a list of command line arguments, short arguments start
    with dash, long arguments with a double dash. The second element
    is the name of the field in the data map of the command, it will
    directly be sent to the underlying entity. The third field is a
    description for the group of command line options in field
    one. The fourth field is True for mandatory fields and False
    otherwise.
    """

    def _usage(self):
        """This method shows usage information. The implementation
        relies on the information in the fields name, description and
        _optionmap in the implementation classes."""
        print """GNUViechAdmin command line interface

Subcommand: %(command)s

 %(description)s

Usage:

%(called)s %(command)s -h|--help

 gives this usage information.

Common options:

 %(option)s
    %(mandatory)s %(optiondesc)s
""" % {'called': sys.argv[0],
       'command': self.name,
       'description': self.description,
       'option': '-v, --verbose',
       'optiondesc': 'verbose operation',
       'mandatory': " "}
        for commandname in self._optionmap.keys():
            cmdl = "%(called)s %(command)s %(subcommand)s [-v|--verbose]" % {
                'called': sys.argv[0],
                'command': self.name,
                'subcommand': commandname}
            desc = """
 %s
""" % (self._optionmap[commandname][0])
            for (options, field, optiondesc, mandatory) in \
                    self._optionmap[commandname][1]:
                cmd = " "
                pairs = []
                for option in options:
                    if field:
                        if option.startswith("--"):
                            pairs.append("%s=<%s>" % (option, field))
                        else:
                            pairs.append("%s <%s>" % (option, field))
                    else:
                        pairs.append(option)
                if not mandatory:
                    cmd = cmd + "["
                cmd = cmd + "|".join(pairs)
                if not mandatory:
                    cmd = cmd + "]"
                descmap = {
                    'option': ", ".join(pairs),
                    'optiondesc': optiondesc,
                    'mandatory': ' '}
                if mandatory:
                    descmap['mandatory'] = '*'
                desc = desc + """ %(option)s
    %(mandatory)s %(optiondesc)s
""" % descmap
                if (len(cmdl) + len(cmd)) > 79:
                    print cmdl
                    cmdl = cmd
                else:
                    cmdl = cmdl + cmd
            print cmdl
            print desc
        print "Mandatory options are marked with *"

    def _subcommands(self):
        """Convenience method for retrieving the subcommand names from
        the _optionmap field."""
        return self._optionmap.keys()

    def _longopts(self, subcommand):
        """This method retrieves available long options in a format
        valid for getopt.gnu_getopt(...) from the _optionmap field."""
        longopts = []
        for cur in [(option[0], option[1]) for option in \
                    self._optionmap[subcommand][1]]:
            for command in cur[0]:
                if command.startswith("--"):
                    if cur[1]:
                        longopts.append(command[2:] + "=")
                    else:
                        longopts.append(command[2:])
        return longopts

    def _shortopts(self, subcommand):
        """This method retrieves available short options in a format
        valid for getopt.gnu_getopt(...) from the _optionmap field."""
        shortopts = ""
        for cur in [(option[0], option[1]) for option in \
                    self._optionmap[subcommand][1]]:
            for command in cur[0]:
                if not command.startswith("--"):
                    if cur[1]:
                        shortopts = shortopts + command[1:] + ":"
                    else:
                        shortopts = shortopts + command[1:]
        return shortopts

    def _checkrequired(self, subcommand):
        """Checks whether the required fields of the given subcommand
        are set."""
        reqcheck = [True, []]
        for req in [option for option in \
                    self._optionmap[subcommand][1] if option[3]]:
            if not req[1] in self._data:
                reqcheck[0] = False
                reqcheck[1].append(""" %s
    * %s""" % (", ".join(req[0]), req[2]))
        return reqcheck

    def _handleoption(self, subcommand, o, a):
        """Handles a command line option by assigning it to the
        matching key as defined in the _optionmap property of the
        implementation class."""
        optionmap = [(option[0], option[1]) for option in \
                     self._optionmap[subcommand][1]]
        if optionmap:
            for (options, datakey) in optionmap:
                if o in options:
                    self._data[datakey] = a

    def _execute(self, subcommand):
        """This method is called when the subcommand of the command is
        executed."""
        raise NotImplementedError

    def _parseopts(self, subcommand, args):
        """This method parses the options given on the command line."""
        longopts = ["help", "verbose"]
        longopts.extend(self._longopts(subcommand))
        try:
            opts, args = getopt.gnu_getopt(
                args,
                "hv" + self._shortopts(subcommand),
                longopts)
        except getopt.GetoptError:
            self._usage()
            sys.exit(2)
        self._verbose = False
        for o, a in opts:
            if o in ("-v", "--verbose"):
                self._verbose = True
            if o in ("-h", "--help"):
                self._usage()
                sys.exit()
            self._handleoption(subcommand, o, a)

    def __init__(self, args):
        """This initializes the command with the given command line
        arguments and executes it."""
        self.logger = logging.getLogger("%s.%s" % (
            self.__class__.__module__, self.__class__.__name__))
        self._data = {}
        if len(args) > 0:
            if args[0] in self._subcommands():
                self._parseopts(args[0], args[1:])
                reqcheck = self._checkrequired(args[0])
                if reqcheck[0]:
                    try:
                        self._execute(args[0])
                    except GnuviechadminError, e:
                        print e
                else:
                    self._usage()
                    print """
the following required arguments are missing:
"""
                    print "\n".join(reqcheck[1])
            else:
                self._usage()
                print "invalid sub command"
        else:
            self._usage()