from alembic import util, command, config
import argparse
import inspect


class AlembicCommandLine(object):
    prog = None
    description = None
    allowed_commands = None

    def __init__(self, prog=None, description=None, allowed_commands=None):
        if prog is not None:
            self.prog = prog
        if description is not None:
            self.description = description
        if allowed_commands is not None:
            self.allowed_commands = allowed_commands


        self.parser = self.generate_options()

    def add_command_options(self, parser, positional, kwargs):
        if 'template' in kwargs:
            parser.add_argument("-t", "--template",
                            default='generic',
                            type=str,
                            help="Setup template for use with 'init'")
        if 'message' in kwargs:
            parser.add_argument("-m", "--message",
                            type=str,
                            help="Message string to use with 'revision'")
        if 'sql' in kwargs:
            parser.add_argument("--sql",
                            action="store_true",
                            help="Don't emit SQL to database - dump to "
                                    "standard output/file instead")
        if 'tag' in kwargs:
            parser.add_argument("--tag",
                            type=str,
                            help="Arbitrary 'tag' name - can be used by "
                            "custom env.py scripts.")
        if 'autogenerate' in kwargs:
            parser.add_argument("--autogenerate",
                            action="store_true",
                            help="Populate revision script with candidate "
                                "migration operations, based on comparison "
                                "of database to model.")
        # "current" command
        if 'head_only' in kwargs:
            parser.add_argument("--head-only",
                                action="store_true",
                                help="Only show current version and "
                                "whether or not this is the head revision.")

        if 'rev_range' in kwargs:
            parser.add_argument("-r", "--rev-range",
                                action="store",
                                help="Specify a revision range; "
                                "format is [start]:[end]")


        positional_help = {
            'directory': "location of scripts directory",
            'revision': "revision identifier"
        }
        for arg in positional:
            parser.add_argument(arg, help=positional_help.get(arg))

    def add_options(self, parser):
        parser.add_argument("-c", "--config",
                            type=str,
                            default="alembic.ini",
                            help="Alternate config file")
        parser.add_argument("-n", "--name",
                            type=str,
                            default="alembic",
                            help="Name of section in .ini file to "
                                    "use for Alembic config")
        parser.add_argument("-x", action="append",
                            help="Additional arguments consumed by "
                            "custom env.py scripts, e.g. -x "
                            "setting1=somesetting -x setting2=somesetting")

    def generate_options(self):
        parser = argparse.ArgumentParser(prog=self.prog)
        self.add_options(parser)
        subparsers = parser.add_subparsers()

        for fn, name, doc, positional, kwarg in self.get_commands():
            subparser = subparsers.add_parser(name, help=doc)
            self.add_command_options(subparser, positional, kwarg)
            subparser.set_defaults(cmd=(fn, positional, kwarg))
        return parser

    def get_commands(self):
        cmds = []
        for fn in [getattr(command, n) for n in dir(command)]:
            if (inspect.isfunction(fn) and
                fn.__name__[0] != '_' and
                fn.__module__ == 'alembic.command'):

                if (self.allowed_commands and
                    fn.__name__ not in self.allowed_commands):
                    continue

                spec = inspect.getargspec(fn)
                if spec[3]:
                    positional = spec[0][1:-len(spec[3])]
                    kwarg = spec[0][-len(spec[3]):]
                else:
                    positional = spec[0][1:]
                    kwarg = []
                cmds.append((fn, fn.__name__, fn.__doc__, positional, kwarg))
        return cmds

    def get_config(self, options):
        return config.Config(file_=options.config,
                             ini_section=options.name,
                             cmd_opts=options)

    def run_cmd(self, config, options):
        fn, positional, kwarg = options.cmd

        try:
            fn(config, *[getattr(options, k) for k in positional],
                        **dict((k, getattr(options, k)) for k in kwarg))
        except util.CommandError as e:
            util.err(str(e))

    def main(self, argv=None):
        options = self.parser.parse_args(argv)
        if not hasattr(options, "cmd"):
            # see http://bugs.python.org/issue9253, argparse
            # behavior changed incompatibly in py3.3
            self.parser.error("too few arguments")
        else:
            self.run_cmd(self.get_config(options), options)


if __name__ == '__main__':
    cmdline = AlembicCommandLine()
    cmdline.main()
