"""Dice rolling and SVG assembly, ported from roll_dice_svg.php."""
from collections import Counter
import random
import re

from django.db import transaction
from django.db.models import F, Q

from . import svg_data as data
from .models import DieFaceStat

DICE_SIDES = {'d4': 4, 'd6': 6, 'd8': 8, 'd10': 10, 'd12': 12, 'd20': 20}

HEX_RE = re.compile(r'^[0-9A-Fa-f]{6}$')


def increment_ever(increments):
    """increments: iterable of (dice_type, face) tuples, one per die rolled."""
    counts = Counter(increments)
    if not counts:
        return

    with transaction.atomic():
        DieFaceStat.objects.bulk_create(
            [DieFaceStat(dice_type=dice_type, face=face)
             for dice_type, face in counts],
            ignore_conflicts=True,
        )

        filters = Q()
        for dice_type, face in counts:
            filters |= Q(dice_type=dice_type, face=face)
        rows = list(DieFaceStat.objects.filter(filters))
        for row in rows:
            row.ever_count = F('ever_count') + \
                counts[(row.dice_type, row.face)]
        DieFaceStat.objects.bulk_update(rows, ['ever_count'])


def ever_stats():
    """Returns {dice_type: {face: ever_count}}, zero-filled for every face."""
    stats = {
        dice_type: {str(face): 0 for face in range(1, sides + 1)}
        for dice_type, sides in DICE_SIDES.items()
    }
    for row in DieFaceStat.objects.all():
        face = str(row.face)
        if row.dice_type in stats and face in stats[row.dice_type]:
            stats[row.dice_type][face] = row.ever_count
    return stats


def parse_dshort(dshort):
    matches = list(re.finditer(data.DIE_REGEX, dshort or '', re.IGNORECASE))
    if not matches:
        return [('0', 'd6', 'numr', '', '', '')]
    return [m.groups('') for m in matches]


def roll_die(dicetype):
    return random.randint(1, DICE_SIDES.get(dicetype, 6))


def resolve_color(value, fallback):
    if value == 'black':
        return '#000000'
    if value == 'white':
        return '#FFFFFF'
    if HEX_RE.match(value or ''):
        return '#' + value
    return fallback


def render_die(dicetype, diceface, roll, position, diecolor, outcolor, pipcolor):
    """Port of the PHP $diceface switch. `position` is this die's 1-based
    index within its dshort group (only meaningful for the 'pdrt' face)."""
    font = 'sans-serif'
    fontsize = '525px'
    y = '278'
    number = roll

    if diceface == 'rfdw':
        die = data.RFD_WHITE_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SS'
    elif diceface == 'rfdg':
        die = data.RFD_GREY_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SS'
    elif diceface == 'rfdb':
        die = data.RFD_BLACK_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die['pip_color']
        dieshape = 'SS'
    elif diceface == 'rfdp':
        die = data.RFD_PINK_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SS'
    elif diceface == 'dddr':
        die = data.DDDS_RED_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        outcolor = die['out_color']
        pipcolor = die['pip_color']
        dieshape = 'SR'
    elif diceface == 'dddb':
        die = data.DDDS_BLUE_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        outcolor = die['out_color']
        pipcolor = die['pip_color']
        dieshape = 'SR'
    elif diceface == 'dddw':
        die = data.DDDS_WHITE_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        outcolor = die['out_color']
        pipcolor = die['pip_color']
        dieshape = 'SR'
    elif diceface == 'pdrt':
        pip = data.DIE_PIP['numr']
        order_die = data.PAYDIRT_DIE[data.PAYDIRT_ORDER[str(position)]]
        number = order_die[str(roll)]
        font = 'New Century Schoolbook, Century Schoolbook, Century Schoolbook L, Georgia, serif'
        y = '258'
        diecolor = order_die['die_color']
        pipcolor = order_die['pip_color']
        dieshape = 'SR'
    elif diceface == 'memr':
        die = data.MEMOIR_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SR'
    elif diceface == 'dlds':
        die = data.DLD_DIE[data.DLD_ORDER[str(position)]]
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SR'
    elif diceface == 'quid':
        die = data.QUIDITY_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die['pip_color']
        dieshape = 'SS'
    elif diceface == 'pips':
        pip = data.DIE_PIP[data.STANDARD_DIE[str(roll)]]
        dieshape = 'SS'
    elif diceface == 'cpip':
        die = data.CPIP_DIE
        pip = data.DIE_PIP[die[str(roll)]]
        diecolor = die['die_color']
        pipcolor = die[f'{roll}_color']
        dieshape = 'SS'
    else:  # 'numr' and any unrecognized diceface, per the PHP default: label
        pip = data.DIE_PIP['numr']
        if dicetype in ('d4', 'd8'):
            fontsize, y, dieshape = '350px', '338', 'TS'
        elif dicetype == 'd10':
            fontsize, y, dieshape = '350px', '338', 'KS'
            number = 0 if roll == 10 else roll
        elif dicetype == 'd12':
            fontsize, y, dieshape = '350px', '298', 'PS'
        elif dicetype == 'd20':
            fontsize, y, dieshape = '250px', '378', 'TS'
        else:  # d6 and default
            dieshape = 'SS'

    die_back = data.DIE_BACK[data.DIE_BACK_FILE[dieshape]]

    return {
        'pip': pip, 'die_back': die_back, 'dieshape': dieshape,
        'diecolor': diecolor, 'outcolor': outcolor, 'pipcolor': pipcolor,
        'font': font, 'fontsize': fontsize, 'y': y, 'number': number,
    }


def roll_dshort(dshort, track_stats=True):
    """Roll every dice group in `dshort` and assemble the result HTML.

    Returns a dict with roll_html, dnum, increments (list of (dicetype, roll)
    for stat tracking), paydirt_off/paydirt_def (None unless a pdrt group was
    rolled), and dshort (the input normalized to the dice counts actually
    used, e.g. any pdrt/dlds group's count forced to 5/3).
    """
    roll_html = ''
    dnum = 0
    increments = []
    paydirt_off = None
    paydirt_def = None
    normalized_groups = []

    for invalue_str, dicetype, diceface, dcolor, ocolor, pcolor in parse_dshort(dshort):
        try:
            invalue = int(invalue_str)
        except ValueError:
            invalue = 0
        if invalue < 1 or invalue > 20:
            invalue = 0
        if diceface == 'pdrt':
            invalue = 5
        if diceface == 'dlds':
            invalue = 3
        if invalue == 0:
            continue

        normalized_groups.append(
            f'{invalue}{dicetype}{diceface}'
            + (f'#{dcolor}#{ocolor}#{pcolor}' if dcolor else '')
        )

        this_roll = {}
        for position in range(1, invalue + 1):
            dnum += 1
            roll = roll_die(dicetype)
            diecolor = dcolor or 'white'
            outcolor = ocolor or 'black'
            pipcolor = pcolor or 'black'

            face = render_die(dicetype, diceface, roll,
                              position, diecolor, outcolor, pipcolor)
            diecolor = resolve_color(face['diecolor'], '#FFFFFF')
            outcolor = resolve_color(face['outcolor'], '#000000')
            pipcolor = resolve_color(face['pipcolor'], '#000000')

            svg = data.SVG_HEADER + \
                face['die_back'] + face['pip'] + data.SVG_TAIL
            svg = (svg.replace('%diecolor%', diecolor)
                      .replace('%outcolor%', outcolor)
                      .replace('%pipcolor%', pipcolor)
                      .replace('%fillopacity%', '1')
                      .replace('%desc%', str(face['number']))
                      .replace('%text%', str(face['number']))
                      .replace('%font%', face['font'])
                      .replace('%fontsize%', face['fontsize'])
                      .replace('%y%', face['y']))

            die_key = f"1{dicetype}{diceface}{diecolor}{outcolor}{pipcolor}"
            roll_html += (
                f"<div class='{face['dieshape']} F die' data-dnum='{dnum}' "
                f"data-die='{die_key}'>{svg}</div>\n"
            )
            this_roll[position] = roll
            increments.append((dicetype, roll))

        if diceface == 'pdrt':
            paydirt_off = (
                int(data.PAYDIRT_DIE[data.PAYDIRT_ORDER['1']]
                    [str(this_roll[1])]) * 10
                + int(data.PAYDIRT_DIE[data.PAYDIRT_ORDER['2']][str(this_roll[2])])
                + int(data.PAYDIRT_DIE[data.PAYDIRT_ORDER['3']][str(this_roll[3])])
            )
            paydirt_def = (
                int(data.PAYDIRT_DIE[data.PAYDIRT_ORDER['4']]
                    [str(this_roll[4])])
                + int(data.PAYDIRT_DIE[data.PAYDIRT_ORDER['5']][str(this_roll[5])])
            )

    if increments and track_stats:
        increment_ever(increments)

    return {
        'roll_html': roll_html,
        'dnum': dnum,
        'increments': increments,
        'paydirt_off': paydirt_off,
        'paydirt_def': paydirt_def,
        'dshort': ''.join(normalized_groups),
    }
