# w.c.s. - web application for online forms
# Copyright (C) 2005-2010  Entr'ouvert
#
# 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, see <http://www.gnu.org/licenses/>.

from __future__ import print_function

import optparse
from optparse import make_option
import sys
import os

__all__ = [
    'Command',
    ]

from django.utils.six.moves import configparser as ConfigParser

from wcs import qommon
from . import _
qommon._commands = {}

class Command(object):
    doc = ''
    name = None
    usage_args = '[ options ... ]'

    def __init__(self, options=[]):
        self.config = ConfigParser.ConfigParser()
        self.options = options + [
                    make_option('--extra', metavar='DIR', action='append',
                        dest='extra', default=[]),
                    make_option('--app-dir', metavar='DIR', action='store',
                        dest='app_dir', default=None),
                    make_option('--data-dir', metavar='DIR', action='store',
                        dest='data_dir', default=None),
                    ]

    def run(self, args, base_options):
        if base_options.configfile:
            if not os.path.exists(base_options.configfile):
                print('Missing configuration file %s' % base_options.configfile, file=sys.stderr)
                sys.exit(1)
            try:
                self.config.read(base_options.configfile)
            except ConfigParser.ParsingError as e:
                print('Invalid configuration file %s' % base_options.configfile, file=sys.stderr)
                print(e, file=sys.stderr)
                sys.exit(1)
        if not self.config.has_section('main'):
            self.config.add_section('main')
        sub_options, args = self.parse_args(args)
        if sub_options.app_dir:
            self.config.set("main", "app_dir", sub_options.app_dir)
        if sub_options.data_dir:
            self.config.set("main", "data_dir", sub_options.data_dir)
        if sub_options.extra:
            if not self.config.has_section('extra'):
                self.config.add_section('extra')
            for i, extra in enumerate(sub_options.extra):
                self.config.set("extra", "cmd_line_extra_%d" % i, extra)
        return self.execute(base_options, sub_options, args)

    def parse_args(self, args):
        self.parser = optparse.OptionParser(
            usage='%%prog %s %s' % (self.name, _(self.usage_args)),
            description=self.doc)
        self.parser.add_options(self.options)
        return self.parser.parse_args(args)

    def execute(self, base_options, sub_options, args):
        """The body of the command"""
        raise NotImplementedError

    @classmethod
    def register(cls):
        qommon._commands[cls.name] = cls

class Ctl(object):
    def __init__(self, cmd_prefixes=[]):
        self.cmd_prefixes = cmd_prefixes
        self.parser = optparse.OptionParser(
                        usage='%prog [ -f config ] command [ options ... ]',
                        add_help_option=False)
        self.parser.disable_interspersed_args()
        self.parser.add_option('-f', '--file', action='store', metavar='CONFIG',
                        type='string', dest='configfile',
                        help=_('use a non default configuration file'))
        self.parser.add_option('--help', action='callback',
                        callback=self.print_help,
                        help=_("Display this help and exit"))

    def load_all_commands(self, ignore_errors=True):
        for cmd_prefix in self.cmd_prefixes:
            if not cmd_prefix in sys.modules:
                __import__(cmd_prefix)
            mod = sys.modules.get(cmd_prefix)
            if not mod:
                continue
            if os.path.isdir(mod.__file__):
                cmddir = os.path.abspath(mod.__file__)
            else:
                cmddir = os.path.abspath(os.path.dirname(mod.__file__))
            for fname in os.listdir(os.path.join(cmddir)):
                name, ext = os.path.splitext(fname)
                if not ext == '.py':
                    continue
                if name.startswith('_'):
                    continue
                try:
                    __import__('%s.%s' % (cmd_prefix, name))
                except ImportError as e:
                    if not ignore_errors:
                        raise e

    def get_commands(self):
        return qommon._commands

    def print_help(self, *args):
        self.parser.print_help()
        self.load_all_commands()
        print()

        commands = [(x.name, x.doc) for x in self.get_commands().values()]
        commands.sort()
        print('Available commands:')
        for name, description in commands:
            print('  %-15s %s' % (name, description))
        sys.exit(0)

    def run(self, args):
        options, args = self.parser.parse_args(args)
        if not args:
            self.parser.error('You must use a command')
        command, args = args[0], args[1:]

        if command not in self.get_commands():
            # load a module named like the command, this is the common case
            for cmd_prefix in self.cmd_prefixes:
                try:
                    __import__('%s.%s' % (cmd_prefix, command))
                except ImportError:
                    pass

        if command not in self.get_commands():
            # if the command could not be loaded from a same-name module,
            # go over all modules
            self.load_all_commands()

        command_class = self.get_commands()[command]
        cmd = command_class()
        return cmd.run(args, options)

