#!/usr/bin/env python
# Copyright (C) Andrew Mitchell 2006,2007

#  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 St, Fifth Floor, Boston, MA  02110-1301 USA
############################################################################

import sys, re, os, logging, readline
from AuthTool.authtools import Authtools
from optparse import OptionParser

known_actions={}
program = os.path.basename(sys.argv[0])


def register_action(action_class):
    known_actions[action_class.name] = action_class


class AuthAction:
    _option_parser = None
    name = None
    help = ""
    def __init__(self):
        self.errors_occured = 0
        parser = self.get_option_parser()
        parser.set_usage('usage: %s [<option> ...] %s [<option> ...]' % (program, self.name))

    def get_option_parser(self):
        if not self._option_parser:
            p = OptionParser()
            self._option_parser = p
        return self._option_parser

    def info(self, msg, stream=sys.stderr):
        logging.info('%s %s: %s', program, self.name, msg)

    def warn(self, msg, stream=sys.stderr):
        logging.warn('%s %s: %s', program, self.name, msg)

    def error(self, msg, stream=sys.stderr, go_on=False):
        logging.error('%s %s: %s', program, self.name, msg)
        self.errors_occured += 1
        if not go_on:
            sys.exit(1)

    def parse_args(self, arguments):
        self.options, self.args = self._option_parser.parse_args(arguments)
        return self.options, self.args

    def check_args(self, global_options):
        return self.errors_occured

    def run(self, authtools, global_opts):
        pass



class AuthConfigure(AuthAction):
    name = 'configure'
    help = 'configure authentication settings'
#register_action(AuthConfigure)    

class AuthDisable(AuthAction):
    name = 'disable'
    help = 'disable configuration for package';
#register_action(AuthDisable)    

#class AuthEnable(AuthAction):
#    name = 'enable'
#    help = 'enable configuration for package';

#    def run(self, authtools, global_opts):
#        pass
#register_action(AuthEnable)    

#class AuthRegister(AuthAction):
#    name = 'register'
#    help = 'register a package';
#register_action(AuthRegister)

class AuthSetMethod(AuthAction):
    name = 'set-method'
    help = 'select an authentication method'

    def __init__(self):
        AuthAction.__init__(self)
        self.orig_method = at.authtools.get_current()
        self.get_option_parser().set_usage(
            'usage: %s [<option> ...] %s <method> [<options>]' % (program, self.name))


    def run(self, at, global_opts):
        if self.method not in at.authtools.get_methods():
            print "Method '%s' not valid" % self.method
            at.list_methods(False)
        else:
            #print "Setting method", self.method
            # Ask any relevant questions, iff they weren't supplied in the config file
            cfg = at.authtools.get_method_config(self.method)

            if not global_opts.force:
            
                for method in cfg:
                    #print method
                    for question in cfg[method]:
                        q = cfg[method][question]
                        # Test for question type
                        if q[0] == 'string':
                            #print q
                            #fetch the question to ask, ask it, and stick it in the method object
                            m = at.authtools.get_method_obj(method)
                            value = raw_input("%s [%s] > " % (q[1], q[2]) )
                            m.store(question, value)

            # Disable old method
            at.authtools.disable(self.orig_method, verbose=True)
            # Enable new method
            at.authtools.enable(self.method, verbose=True)
        

    def check_args(self, global_options):
        if len(self.args) < 1:
            self.error('<method> <options>')

        self.method = self.args[0]
        



register_action(AuthSetMethod)

class ListMethods(AuthAction):
    name = 'list'
    help = 'list authentication methods'

    print_all = False
    verbose = False

    def __init__(self):
        AuthAction.__init__(self)
        self.get_option_parser().set_usage(
            'usage: %s [<option> ...] %s [<options>]' % (program, self.name))

    def check_args(self, global_options):
        if len(self.args) < 1:
            return False
        if 'all' in self.args:
            self.print_all = True
        if 'verbose' in self.args:
            print "Setting verbosity"
            self.verbose = True

        else:
            #Check if user wants all properties listed
            print self.args
            return False
        
    def run(self, at, global_options):
        #Actually do the listing
        at.list_methods(self.print_all, self.verbose)
        
register_action(ListMethods)    



class AuthtoolCLI:

    
    def __init__(self):
        # Would be good to get the conffile option first, but use reload_config() later
        self.authtools = Authtools()
        self.methods = self.authtools.get_methods()
        self.current_method = self.authtools.get_current()


    
    def list_methods(self, properties, verbose=False ):
        print "Supported authentication methods"
        #print self.methods
        for method in self.methods:
            if self.authtools.method_display(method):
                if self.authtools.get_current() == method:
                    active = " * "
                else:
                    active = "   "
                print active, "%s :\t%s" %(method,self.methods[method])
                if properties:
                    # Print the current config option for the method
                    props = self.authtools.get_method_config(method)
                    # transform the config dictionary into a 'pretty' format
                    for meth in props:
                        if len(props[meth]) > 0:
                            print "      ------------"
                        for x in props[meth]:
                            if verbose:
                                print "      (%s)\t%s: %s" % (x, props[meth][x][1], props[meth][x][2])
                            else:
                                print "      %s: %s" % (props[meth][x][1], props[meth][x][2])
                self.authtools.required_packages(method)

            
    # match a string with the list of available actions
    def action_matches(self, action, actions):
        prog = re.compile('[^-]*?-'.join(action.split('-')))
        return [a for a in actions if prog.match(a)]
    
    # parse command line arguments
    def parse_options(self,args):
        shortusage = 'usage: %prog [<option> ...] <action> [<option> ...]'
        parser = OptionParser(usage=shortusage)
        parser.disable_interspersed_args()
        
        parser.remove_option('-h')
        parser.add_option('-h', '--help',
                          help='help screen',
                          action='store_true', dest='help')
        parser.add_option('-v', '--verbose',
                          help='verbose mode',
                          action='store_true', dest='verbose')
        parser.add_option('-f', '--force',
                          help='perform operations non-interactively, e.g. overwriting, removing configuration',
                          action='store_true', dest='force')
        parser.add_option('-n', '--dry-run',
                          help='do not execute commands, print only (not yet implemented)',
                          action='store', default=False, dest='dryrun')
        parser.add_option('-c', '--config-file',
                          help='configuration file to seed authentication questions',
                          action='store', dest='conffile', default=None)
        global_options, args = parser.parse_args()
        
        # Print the help screen and exit
        if len(args) == 0 or args[0].lower() == 'help' or global_options.help:
            parser.print_help()
            print "\nactions:"
            for n, a in sorted(known_actions.items()):
                print "  %-21s %s" % (n, a.help)
            print ""
            sys.exit(1)

        # dry-run option not yet implemented XXX-TODO-XXX
        if global_options.dryrun:
            print >>sys.stderr, "option --dry-run not yet implemented"
            sys.exit(1)

        # Load in the preseed config if it exists
        if global_options.conffile:
            #print >>sys.stderr, "Using config %s" % global_options.conffile
            self.conffile = global_options.conffile
            # Need to re-init authtools with new conffile
            # authtools gets initialised before options are parsed
            self.authtools.reload_config(self.conffile)

        # check if the specified action really exists
        action_name = args[0]
        del args[0]
        matching_actions = self.action_matches(action_name, known_actions.keys())
        if len(matching_actions) == 0:
            self.usage(sys.stderr, "unknown action `%s'" % action_name)
        elif len(matching_actions) > 1:
            self.usage(sys.stderr,
                       "ambiguous action `%s', matching actions: %s"
                       % (action_name, strlist(matching_actions)))
        else:
            action_name = matching_actions[0]
            
        # instantiate an object for the action and parse the remaining arguments
        action = known_actions[action_name]()
        action_options, action_names = action.parse_args(args)
            
        return global_options, action

    def usage(self, stream, msg=None):
        print >>stream, msg
        print >>stream, "use `%s help' for help on actions and arguments" % program
        print >>stream
        sys.exit(1)

    
    def run(self):
        #command line options
        global_options, action = self.parse_options(sys.argv[1:])
        #print global_options,action
        
        # check the arguments according to the action called
        if action.check_args(global_options):
            sys.exit(1)

        # run the action and exit
        rv = action.run(self, global_options)
        sys.exit(rv)
        

if __name__ == '__main__':
    at = AuthtoolCLI()
    at.run()


    
