# Fit reusable domestic furniture groups around reserved circulation and daylight.
from collections import deque

from .api import DesignError
from .blocks import is_air, full_support
from .materials import DIRECTIONS
from .navigation import flood, path_to


def table_group(wood):
    """Build a one-block-high trestle table with four inward-facing chairs."""
    pieces = [(0, 0, 0, wood + '_stairs', {'facing': 'east', 'half': 'top'}), (2, 0, 0, wood + '_stairs', {'facing': 'west', 'half': 'top'}), (1, 0, 0, wood + '_slab', {'type': 'top'}), (1, 1, 0, 'flower_pot', {})]
    pieces += [(x, 0, z, wood + '_stairs', {'facing': 'north' if z < 0 else 'south'}) for x in (0, 2) for z in (-1, 1)]
    return pieces


def modules(function, mat, area):
    """Translate room functions into useful compact furniture groups."""
    color = mat.accent
    sofa = [(0, 0, 0, mat.floor + '_stairs', {'facing': 'north'}), (1, 0, 0, mat.floor + '_stairs', {'facing': 'north'})]
    programs = {
        'living': [('settle', sofa), ('table', [(0, 0, 0, mat.floor + '_planks', {}), (0, 1, 0, 'potted_fern', {})])],
        'kitchen': [('cooking', [(0, 0, 0, 'smoker', {
            'facing': 'south'
        }), (1, 0, 0, 'crafting_table', {}), (2, 0, 0, 'water_cauldron', {
            'level': '3'
        })])],
        'sleeping': [('bed', [(0, 0, 0, color + '_bed', {
            'part': 'foot',
            'facing': 'north'
        }), (0, 0, -1, color + '_bed', {
            'part': 'head',
            'facing': 'north'
        })]), ('bedside', [(0, 0, 0, 'barrel', {
            'facing': 'up'
        })])],
        'storage': [('stores', [(0, 0, 0, 'barrel', {
            'facing': 'south'
        }), (1, 0, 0, 'barrel', {
            'facing': 'south'
        })])],
        'workshop': [('workbench', [(0, 0, 0, 'crafting_table', {}), (1, 0, 0, 'smithing_table', {})]), ('toolbox', [(0, 0, 0, 'barrel', {
            'facing': 'up'
        })])],
        'study': [('bookcase', [(0, 0, 0, 'bookshelf', {}), (0, 1, 0, 'bookshelf', {})]), ('desk', [(0, 0, 0, 'cartography_table', {})])],
        'enchanting': [('enchantment', [(0, 0, 0, 'enchanting_table', {})]), ('books', [(0, 0, 0, 'bookshelf', {}), (1, 0, 0, 'bookshelf', {})])],
        'brewing': [('alchemy', [(0, 0, 0, 'smooth_stone', {}), (0, 1, 0, 'brewing_stand', {}), (1, 0, 0, 'water_cauldron', {
            'level': '3'
        })])],
        'trading': [('counter', [(0, 0, 0, 'barrel', {
            'facing': 'south'
        }), (1, 0, 0, 'cartography_table', {})])],
        'fishing': [('tackle', [(0, 0, 0, 'barrel', {
            'facing': 'up'
        }), (1, 0, 0, 'smoker', {
            'facing': 'south'
        })])],
    }
    if area >= 75:
        if function == 'living':
            programs[function] = [
                ('settle', [(x, 0, 0, mat.floor + '_stairs', {
                    'facing': 'north'
                }) for x in range(3)] + [(3, 0, 0, mat.floor + '_planks', {}), (3, 1, 0, 'potted_fern', {})]),
                ('dining', table_group(mat.floor)),
            ]
        if function == 'kitchen':
            programs[function] = [('kitchen_counter', programs[function][0][1] + [(3, 0, 0, 'barrel', {'facing': 'south'}), (3, 1, 0, 'potted_red_tulip', {})]), ('cupboard', [(0, 0, 0, 'barrel', {'facing': 'south'}), (0, 1, 0, 'barrel', {'facing': 'south'})])]
        if function == 'sleeping':
            programs[function] = [('double_bed', [(x, 0, z, color + '_bed', {'part': part, 'facing': 'north'}) for x in (0, 1) for z, part in ((0, 'foot'), (-1, 'head'))]), ('wardrobe', [(0, 0, 0, 'barrel', {'facing': 'south'}), (0, 1, 0, 'barrel', {'facing': 'south'}), (1, 0, 0, 'bookshelf', {})])]
        if function == 'storage':
            programs[function] = [('stores', [(x, y, 0, 'barrel', {'facing': 'south'}) for x in range(3) for y in (0, 1)])]
        if function == 'study':
            programs[function] = [('library', [(x, y, 0, 'bookshelf', {}) for x in range(3) for y in (0, 1)]), ('writing_desk', [(0, 0, 0, 'cartography_table', {}), (1, 0, 0, mat.floor + '_planks', {}), (1, 1, 0, 'potted_fern', {}), (0, 0, 1, mat.floor + '_stairs', {'facing': 'south'})])]
        if function == 'enchanting':
            programs[function] = [('enchantment', [(0, 0, 0, 'enchanting_table', {})]), ('books', [(x, y, 0, 'bookshelf', {}) for x in range(3) for y in (0, 1)])]
    if area >= 150 and function == 'living':
        stove = [(x, y, 0, 'stone_bricks', {}) for x in (0, 2) for y in (0, 1)]
        stove += [(1, 0, 0, 'furnace', {'facing': 'south'}), (1, 1, 0, 'chiseled_stone_bricks', {})]
        stove += [(x, 2, 0, 'stone_brick_slab', {'type': 'top'}) for x in range(3)]
        programs[function].append(('masonry_stove', stove))
        programs[function].append(('sideboard', [(0, 0, 0, 'barrel', {'facing': 'south'}), (1, 0, 0, 'bookshelf', {}), (1, 1, 0, 'potted_fern', {})]))
    if area >= 150:
        if function == 'sleeping':
            # Bedside furniture belongs to the bed, with space at its foot for approach.
            bed = programs[function][0][1]
            bed += [(x, 0, -1, 'barrel', {'facing': 'south'}) for x in (-1, 2)]
            bed += [(-1, 1, -1, 'potted_fern', {}), (2, 1, -1, 'lantern', {'hanging': 'false'})]
            programs[function] += [
                ('linen_chest', [(x, 0, 0, 'barrel', {
                    'facing': 'up'
                }) for x in range(3)]),
                ('dressing_table', [(x, 0, 0, mat.floor + '_planks', {}) for x in range(3)] + [(0, 1, 0, 'potted_red_tulip', {}), (2, 1, 0, 'flower_pot', {}), (1, 0, 1, mat.floor + '_stairs', {
                    'facing': 'south'
                })]),
            ]
        if function == 'study':
            programs[function].append(('reference_shelves', [(x, y, 0, 'bookshelf', {}) for x in range(4) for y in (0, 1)]))
            programs[function].append(('reading_table', table_group(mat.floor)))
        if function == 'kitchen':
            programs[function].append(('pantry', [(x, y, 0, 'barrel', {'facing': 'south'}) for x in range(3) for y in (0, 1)]))
            programs[function].append(('island', [(x, 0, 0, 'smooth_stone', {}) for x in range(3)] + [(0, 1, 0, 'potted_fern', {}), (2, 1, 0, 'flower_pot', {})]))
        if function == 'workshop':
            programs[function].append(('long_workbench', [(x, 0, 0, block, {}) for x, block in enumerate(('crafting_table', 'fletching_table', 'smithing_table', 'cartography_table'))]))
            programs[function].append(('parts_shelves', [(x, y, 0, 'barrel', {'facing': 'south'}) for x in range(3) for y in (0, 1)]))
        if function == 'storage':
            programs[function].append(('supply_rack', [(x, y, 0, 'barrel', {'facing': 'south'}) for x in range(4) for y in (0, 1)]))
    return programs[function]


def rotate_piece(piece, rotation):
    """Rotate a furnishing in plan, including bed halves and chair facings."""
    dx, dy, dz, block, properties = piece
    props = dict(properties)
    for _ in range(rotation):
        dx, dz = -dz, dx
        if 'facing' in props and props['facing'] in DIRECTIONS:
            props['facing'] = {'north': 'east', 'east': 'south', 'south': 'west', 'west': 'north'}[props['facing']]
    return dx, dy, dz, block, props


def place_group(ctx, room, kind, pieces, function):
    """Find a supported layout and reserve a reachable interaction point before placement."""
    g = ctx.g
    floor = room['floor']
    cells = room['cells']
    allowed = {(x, floor + 1, z) for x, z in cells}
    original_reachable = set(flood(g, room['anchor'], allowed, maximum=2000))
    candidates = [(x, z, rot) for x, z in sorted(cells) for rot in range(4)]
    g.rng.shuffle(candidates)
    # Favor corners and walls, while retaining randomized tie-breaks.
    if kind in ('dining', 'reading_table', 'island'):
        candidates.sort(key=lambda p: abs(p[0] - room['anchor'][0]) + abs(p[1] - room['anchor'][2]))
    else:
        candidates.sort(key=lambda p: sum((p[0] + dx, p[1] + dz) in cells for dx, dz in DIRECTIONS.values()))
    for x, z, rotation in candidates:
        parts = [rotate_piece(piece, rotation) for piece in pieces]
        footprint = {(x + dx, z + dz) for dx, _, dz, _, _ in parts}
        if not footprint <= cells:
            continue
        occupied = [(x + dx, floor + 1 + dy, z + dz) for dx, dy, dz, _, _ in parts]
        clearance = [(px, floor + dy, pz) for px, pz in footprint for dy in (1, 2)]
        if not g.free(occupied) or not g.free(clearance) or any(not full_support(g.get(px, floor, pz)) for px, pz in footprint):
            continue
        parents = flood(g, room['anchor'], allowed - {(px, floor + 1, pz) for px, pz in footprint}, maximum=2000)
        removed = {(px, floor + 1, pz) for px, pz in footprint}
        if original_reachable - removed - set(parents):
            continue
        contacts = [(px + dx, floor + 1, pz + dz) for px, pz in sorted(footprint) for dx, dz in DIRECTIONS.values() if (px + dx, floor + 1, pz + dz) in parents]
        if not contacts:
            continue
        access = min(contacts, key=lambda p: (abs(p[0] - room['anchor'][0]) + abs(p[2] - room['anchor'][2]), p))
        g.route(path_to(parents, access), room['id'] + '/' + kind)
        for dx, dy, dz, block, props in parts:
            point = x + dx, floor + 1 + dy, z + dz
            g.put(*point, block, **props)
            if block in ('barrel', 'smoker'):
                supplies = [('fishing_rod', 1), ('cod', 6), ('string', 12)] if function == 'fishing' else [('emerald', 8), ('paper', 12)] if function == 'trading' else [('bread', 6), ('oak_log', 12)]
                g.inventory(point, supplies)
        g.metadata['furniture'].append({'owner': room['id'], 'function': function, 'kind': kind, 'cells': occupied, 'access': access, 'states': [g.get(*p) for p in occupied]})
        return
    raise DesignError(f'{room["id"]}: cannot fit {function}/{kind} with clear access; enlarge the floor or reduce this room program')


def furnish(ctx):
    """Equip every program, illuminate every room and keep the protected routes clear."""
    g, mat = ctx.g, ctx.plan.design.materials
    for room in ctx.rooms:
        # Fit long and multi-block pieces first, before optional decor.
        groups = [(kind, pieces, function) for function in room['functions'] for kind, pieces in modules(function, mat, len(room['cells']))]
        groups.sort(key=lambda item: -len({(p[0], p[2]) for p in item[1]}))
        for kind, pieces, function in groups:
            place_group(ctx, room, kind, pieces, function)
        # The lamp module carries its own real support, including in irregular attics.
        place_group(ctx, room, 'lamp', [(0, 0, 0, mat.floor + '_planks', {}), (0, 1, 0, 'lantern', {'hanging': 'false'})], 'lighting')
        lamp = g.metadata['furniture'][-1]['cells'][-1]
        g.metadata['lights'].append({'owner': room['id'], 'point': lamp})
        if len(room['cells']) > 100 and room['height'] >= 5:
            # Two supported pendant lamps illuminate larger domestic rooms.
            ax, _, az = room['anchor']
            candidates = sorted(room['cells'], key=lambda p: abs(p[0] - ax) + abs(p[1] - az))
            pendants = []
            for x, z in candidates:
                y = room['floor'] + room['height'] - 1
                if any(abs(x - px) + abs(z - pz) < 6 for px, pz in pendants):
                    continue
                if full_support(g.get(x, y + 1, z)) and g.free([(x, y, z)]):
                    g.lantern(x, y, z, room['id'], True)
                    pendants.append((x, z))
                    if len(pendants) == 2:
                        break
        # Inlaid rugs add domestic color without reducing headroom or obstructing steps.
        ax, _, az = room['anchor']
        for x in range(ax - 2, ax + 3):
            for z in range(az - 1, az + 2):
                if (x, z) in room['cells'] and full_support(g.get(x, room['floor'], z)) and is_air(g.get(x, room['floor'] + 1, z)):
                    g.put(x, room['floor'], z, (mat.accent if abs(x - ax) == 2 or abs(z - az) == 1 else 'white') + '_terracotta')
