"""
Map arguments

input:

    {name~*}

    the * is treated as a glob and can be part of a filename
    name is used to expand the capture part later.

    {name=[1-5]}

    name is replaced by the range of

    {name=a,b,c,d}

"""

from collections import OrderedDict
import copy
import glob
import logging
import itertools
import re
import sys

import fantail

lg = logging.getLogger(__name__)
#plg.setLevel(logging.DEBUG)

RE_FIND_MAPINPUT = re.compile(
        r'(?<![{\\]){([a-zA-Z_][a-zA-Z0-9_]*)?([\~\=]?)([^}]+)?}(?![\\}])')

def very_basic_command_line_generator(app):
    """
    Most basic command line generator possible
    """
    cl = sys.argv[1:]
    yield cl


def map_range_expand(map_info, cl):
    """
    Convert a map range to a list of items:

    first - range conversion:
      - a,b,c -> ['a', 'b', 'c']
      - 5:10 -> [5,6,7,8,9]
      - 5:10:2 -> [5,7,9]
    then, merge with the command line item in which this was embedded (if any)

    so: test.{a=5:10:2}.in becomes: ['test.5.in', 'test.7.in', 'test.9.in']

    """


    mappat_range_3 = re.match(r'([0-9]+):([0-9]+):([0-9]+)',
                              map_info['pattern'])
    mappat_range_2 = re.match(r'([0-9]+):([0-9]+)',
                              map_info['pattern'])
    if mappat_range_3:
        start, stop, step = mappat_range_3.groups()
        lg.debug("expanding numerical range: %s %s %s", start, stop, step)
        map_items = map(str, range(int(start), int(stop)+1, int(step)))
    elif mappat_range_2:
        start, stop = mappat_range_2.groups()
        map_items = map(str, range(int(start), int(stop)+1))
    elif ',' in map_info['pattern']:
        map_items = [x.strip() for x in map_info['pattern'].split(',')]
    else:
        lg.critical("Can not parse range: %s", map_info['pattern'])
        exit(-1)

    return map_items


def _unescape(x):
    if r'\{' in x:
        x = x.replace(r'\{', '{')
    if r'\}' in x:
        x = x.replace(r'\}', '}')
    return x

def map_glob_expand(map_info, cl):

    globpattern = RE_FIND_MAPINPUT.sub(
                    map_info['pattern'], map_info['arg'])
    globhits = glob.glob(globpattern)

    if len(globhits) == 0:
        lg.critical("No hits found for pattern: %s", globpattern)
        exit(-1)

    sta, tail = map_info['start'], map_info['tail']
    #print globhits, sta, tail, globhits[0][sta:]

    if sta == tail == 0:
        return sorted(globhits)
    elif tail == 0:
        return sorted([g[sta:] for g in globhits])
    else:
        return sorted([g[sta:-tail] for g in globhits])


def map_iter(map_info):
    for i in map_info.get('items', []):
        map_clean = copy.copy(map_info)
        map_clean['item'] = i
        yield map_clean


def apply_map_info_to_cl(newcl, map_info):
    item = map_info['item']
    for i, arg in enumerate(newcl):
        map_to_re = map_info['re_replace']
        map_fr_re = map_info['re_from']
        if map_fr_re.search(arg):
            newcl[i] = map_fr_re.sub(item, arg)
        elif map_to_re.search(arg):
            newcl[i] = map_to_re.sub(item, arg)

    return newcl


def basic_command_line_generator(app):
    """
    Command line generator that expands globs & ranges
    """
    info = fantail.Fantail()
    stdin_file = app.kea_args.stdin
    stdout_file = app.kea_args.stdout
    stderr_file = app.kea_args.stderr

    #cl = [app.conf['executable']] + sys.argv[1:]
    cl = sys.argv[1:]

    max_no_jobs = app.kea_args.maxjobs

    #check if there are map arguments in here
    mapins = []
    mapcount = 0
    replacements = 0

    ## find all map definitions
    for i, arg in enumerate(cl):
        fipa = RE_FIND_MAPINPUT.search(arg)
        if not fipa:
            continue
        if fipa.groups()[2] is None:
            replacements += 1
            continue
        #print fipa.groups()
        mapins.append(i)

    # no map definitions found - then simply return the cl & execute
    if len(mapins) == 0:

        newcl = map(_unescape, cl)

        info['cl'] = newcl
        info['iteration'] = 0
        info['stdin_file'] = stdin_file
        info['stdout_file'] = stdout_file
        info['stderr_file'] = stderr_file
        yield info
        return

    #define iterators for each of the definitions

    mapiters = []
    for arg_pos in mapins:
        map_info = {}
        map_info['pos'] = arg_pos
        map_info['arg'] = cl[arg_pos]
        map_info['re_search'] = RE_FIND_MAPINPUT.search(cl[arg_pos])
        name, operator, pattern = map_info['re_search'].groups()

        if operator is None:
            if ':' in pattern:
                operator = '='
            else:
                operator = '~' #default to file type glob

        if name is None:
            name = ""


        lg.debug("cl expand 1")
        lg.debug(" - name     : %s", name)
        lg.debug(" - operator : %s", operator)
        lg.debug(" - pattern  : %s", pattern)
        map_info['name'] = name
        map_info['operator'] = operator
        map_info['pattern'] = pattern

        re_from_re = r'({' + map_info['name'] + r'[\~\=]?[^}]*})'
        map_info['re_from'] = \
            re.compile(re_from_re)

        map_info['re_replace'] = \
            re.compile(r'({' + map_info['name'] + r'})')

        map_info['start'] = map_info['re_search'].start()
        map_info['tail'] = len(cl[arg_pos]) - map_info['re_search'].end()

        if map_info['operator'] == '~':
            map_info['items'] = map_glob_expand(map_info, cl)
        elif map_info['operator'] == '=':
            map_info['items'] = map_range_expand(map_info, cl)

        mapiters.append(map_iter(map_info))

    for i, map_info_set in enumerate(itertools.product(*mapiters)):

        if i >= max_no_jobs:
            break

        newcl = copy.copy(cl)
        newinfo = copy.copy(info)

        newstdin = stdin_file
        newstdout = stdout_file
        newstderr = stderr_file


        for map_info in map_info_set:
            newcl = apply_map_info_to_cl(newcl, map_info)
            if not newstdin is None:
                newstdin = apply_map_info_to_cl([newstdin], map_info)[0]
            if not newstdout is None:
                newstdout = apply_map_info_to_cl([newstdout], map_info)[0]
            if not newstderr is None:
                newstderr = apply_map_info_to_cl([newstderr], map_info)[0]

        newcl = map(_unescape, cl)


        newinfo['cl'] = newcl
        newinfo['iteration'] = i
        newinfo['stdin_file'] = newstdin
        newinfo['stdout_file'] = newstdout
        newinfo['stderr_file'] = newstderr
        yield newinfo
