import os
import re
import sys
import contextlib
from functools import reduce
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

from abrtcli.l18n import _
from abrtcli.config import MEDIUM_FMT
import report

braces_re = re.compile(r'\{([^}]+)\}')


def as_table(data, margin=2, separator=' '):
    '''
    Return `data` formatted as table.
    '''

    data = list(map(lambda x: list(map(str, x)), data))

    widths = reduce(
        lambda x, y: list(map(
            lambda t: max(t[0], t[1]), zip(x, y)
        )),
        map(lambda x: map(len, x), data),
        map(lambda _: 0, data))

    fmt = ''
    for num, width in enumerate(widths):
        if num != 0:
            width = 0
        fmt += '{{{0}:<{1}}}{2}'.format(num, width, separator * margin)
    fmt += '\n'

    return ''.join(map(lambda row: fmt.format(*row), data))[:-1]


def fmt_problems(probs, fmt=MEDIUM_FMT):
    '''
    Return preformatted problem data of `prob` according to `fmt`
    '''

    if probs is None:
        return ''

    if not isinstance(probs, list):
        probs = [probs]

    fmt = fmt.replace('|', '\n')
    tabular = '#table' in fmt
    oneline = '\n' not in fmt

    out = ''
    for prob in probs:
        what_field, what = get_human_identifier(prob)

        context_vars = {
            'what': what,
            'what_field': what_field,
        }

        uid = get_problem_field(prob, 'uid')
        if uid:
            username = get_problem_field(prob, 'username')
            if username:
                uid_username = ('{0} ({1})'
                                .format(uid, username))
            else:
                uid_username = str(uid)

            context_vars['uid_username'] = uid_username

        if prob.not_reportable:
            context_vars['not_reportable'] = _('Not reportable')
            reason = prob.not_reportable_reason.rstrip()

            if tabular:
                reason = reason.replace('\n', '\n,')

            context_vars['not_reportable_reason'] = reason

        if hasattr(prob, 'reported_to'):
            r_out = ''
            rtl = prob.reported_to.splitlines()

            # each reported to line item as separate row
            # except for BTHASH
            for rline in rtl:
                if 'BTHASH' in rline:
                    continue

                if 'URL=' in rline:
                    rep_to, url = rline.split('URL=', 1)
                    if ': ' in rep_to:
                        rep_to, rest = rep_to.split(': ')

                    if tabular:
                        r_out += '\n{},{}'.format(rep_to, url)

            if r_out:
                context_vars['reported_to'] = r_out

        if hasattr(prob, 'backtrace'):
            if not oneline:
                context_vars['backtrace'] = '\n' + prob.backtrace

        if not hasattr(prob, 'count'):
            context_vars['count'] = 1

        sfmt = fmt.splitlines()
        for line in sfmt:
            if not line.rstrip():
                if tabular:
                    out += ',\n'
                elif not oneline:
                    out += '\n'
                continue

            if line[0] == '#':  # output mode selector or comment
                continue

            template_vars = braces_re.findall(line)

            missing_var = False
            for var in template_vars:
                # try looking up missing context var in problem items
                if var not in context_vars:
                    val = get_problem_field(prob, var)
                    if val:
                        context_vars[var] = val
                    else:
                        missing_var = True
                        context_vars[var] = ''

            if not missing_var or oneline:
                fmtline = line.format(**context_vars)
                if not oneline:
                    fmtline = upcase_first_letter(fmtline)

                out += fmtline + '\n'

        # separator
        if tabular:
            out += ',\n'
        elif not oneline:
            out += '\n'

    if tabular:
        rows = out.splitlines()
        rows = map(lambda x: x.split(',', 1), rows)
        out = as_table(list(rows)[:-1])
    else:
        out = out.replace('\n\n', '\n')

    return out.rstrip()


def get_problem_field(prob, field):
    '''
    Return problem field `field` or None
    '''

    try:
        return getattr(prob, field)
    except AttributeError:
        return


def get_human_identifier(prob):
    '''
    Return first found problem field from list of candidate fields
    that will be used as a problem identifier for humans
    '''

    candidates = ['component', 'executable', 'type']

    for c in candidates:
        val = get_problem_field(prob, c)
        if val:
            return c, val

    return None, None


def sort_problems(probs, recent_first=True):
    '''
    Sort problems by time, `recent_first` by default
    '''

    return sorted(probs, key=lambda p: p.time, reverse=recent_first)


def upcase_first_letter(s):
    '''
    UC First
    '''
    return s[0].upper() + s[1:]


def run_event(event_name, prob):
    '''
    Run event with `event_name` on problem `prob`
    '''

    state, ret = report.run_event_on_problem_dir(prob.path, event_name)

    if ret == 0 and state.children_count == 0:
        sys.stderr.write('No actions were found for event {}'
                         .format(event_name))
        return False

    return True


@contextlib.contextmanager
def remember_cwd():
    curdir = os.getcwd()
    try:
        yield
    finally:
        os.chdir(curdir)


@contextlib.contextmanager
def captured_output():
    """
    Capture stdout and stderr output of the executed block

    Example:

    with captured_output() as (out, err):
        foo()
    """

    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err
