import sys
import types
import inspect
import re
import optparse
from collections import defaultdict
from textwrap import wrap

from scriptine import misc, log

def parse_and_run_function(function, args=None, command_name=None,
        add_dry_run_option=True, add_verbosity_option=True):
    #TODO refactor me, I'm too long
    if args is None:
        args = sys.argv
    
    required_args, optional_args = inspect_args(function)
    
    func_doc = function.__doc__ or ''
    params_doc = parse_rst_params(func_doc)
    
    usage = 'usage: %prog '
    if command_name:
        usage += command_name.replace('_', '-') + ' '
    usage += '[options] ' + ' '.join(required_args)
    
    if func_doc:
        first_paragraph = re.findall('(.*?)((\n[ \t]*\n)|$)', func_doc,
            re.DOTALL)[0][0]
        first_paragraph = ' '.join(l.strip() for l in
            first_paragraph.split('\n'))
        usage += '\n\n' + '\n'.join(wrap(first_paragraph, 60))
    
    if set(required_args).intersection(list(params_doc.keys())):
        usage += '\n\nRequired arguments:'
        for arg in required_args:
            usage += '\n%s' % arg
            if arg in params_doc:
                usage += ': %s' % params_doc[arg]
        
    add_help_option = True
    if getattr(function, 'no_help', False):
        add_help_option = False
    
    fetch_all = None
    if hasattr(function, 'fetch_all'):
        fetch_all = function.fetch_all
        optional_args = [(arg, default) for arg, default in optional_args
            if arg != fetch_all]
    
    parser = optparse.OptionParser
    if getattr(function, 'non_strict', False):
        parser = NonStrictOptionParser
    
    parser = parser(usage, add_help_option=add_help_option)
    
    for arg_name, default in optional_args:
        options = {}
        if isinstance(default, bool):
            if default:
                options =  {'action': 'store_false'}
            else: 
                options =  {'action': 'store_true'}
        elif isinstance(default, int):
            options =  {'type': 'int'}
        elif isinstance(default, float):
            options =  {'type': 'float'}
        parser.add_option('--' + arg_name.replace('_', '-'),
                          help=params_doc.get(arg_name, None),
                          dest=arg_name, default=default, metavar=default, **options)
    
    if add_dry_run_option:
        parser.add_option('--dry-run', '-n', dest='dry_run', default=False,
            action='store_true', help='don\'t actually do anything')
    
    if getattr(function, 'no_verbosity', False):
        add_verbosity_option = False
    if add_verbosity_option:
        parser.add_option('--verbose', '-v', dest='verbose',
            action='count', help='be more verbose')
        parser.add_option('--quite', '-q', dest='quite',
            action='count', help='be more silent')
    
    (options, args) = parser.parse_args(args)
    
    if add_verbosity_option:
        verbosity = (options.verbose or 0) - (options.quite or 0)
        log.inc_log_level(verbosity)
    
    
    if add_dry_run_option and options.dry_run:
        misc.options.dry = True
        log.inc_log_level(1)
        log.warn('running in dry-mode. don\'t actually do anything')
    
    args = args[1:]
    if len(args) < len(required_args):
        parser.error('number of arguments does not match')
    kw = {}
    for arg_name, _default in optional_args:
        kw[arg_name] = getattr(options, arg_name)
    
    if fetch_all:
        kw[fetch_all] = args[len(required_args):]
    return function(*args[:len(required_args)], **kw)

def no_help(cmd):
    cmd.no_help = True
    return cmd

def no_verbosity(cmd):
    cmd.no_verbosity = True
    return cmd

def non_strict(cmd):
    cmd.non_strict = True
    return cmd

def fetch_all(arg_name):
    def _fetch_all(cmd):