# Functional furniture groups and circulation-aware furnishing of rectangular rooms.
from dataclasses import dataclass
from hearth import Box, Frame, ContractError
from hearth.kernel import Capability, Contract, Domain, Rule, Port, Plan, Child
from hearth.blocks import state_of, stairs, slab

GROUPS = {'living': [['spruce_stairs'], ['barrel']], 'bedroom': [['red_bed'], ['barrel']], 'library': [['bookshelf'], ['lectern']], 'kitchen': [['smoker'], ['furnace']], 'workshop': [['crafting_table'], ['smithing_table']], 'storage': [['barrel']]}


def supplies():
    return {'id': 'minecraft:barrel', 'Items': [{'Slot': 0, 'id': 'minecraft:bread', 'count': 6}, {'Slot': 1, 'id': 'minecraft:torch', 'count': 12}]}


@dataclass(frozen=True)
class FurnitureGroup:
    purpose: str

    def capability(self):
        return Capability('furnishing.group.' + self.purpose, ('furniture', self.purpose), offers=('interaction',), inputs={'purpose': Domain(choices=tuple(GROUPS))}, guarantees=('complete functional group', 'supported', 'clear interaction space'))

    def negotiate(self, ctx, parameters):
        area = Box((0, 0, 0), (1, 2, 1))
        interaction = Box((0, 0, 2), (1, 1, 2))
        return Contract(Box((0, 0, 0), (1, 2, 2)), ports=(Port('use', 'interaction', Frame((0, 0, 2)), interaction, 4),), rules=(Rule('support', tuple((x, -1, z) for x in range(2) for z in range(3))), Rule('clear', tuple(interaction.cells())), Rule('furnishing', box=area, data={'groups': GROUPS[self.purpose]}, phase='complete')), atomic_object=True)

    def realize(self, ctx, contract):
        p = Plan()
        barrel = state_of('barrel', facing='south', open='false')
        p.block((1, 0, 0), barrel, 'supplies', nbt=supplies())
        if self.purpose == 'bedroom':
            for z, part in ((0, 'head'), (1, 'foot')):
                p.block((0, 0, z), state_of('red_bed', facing='north', part=part, occupied='false'), 'bed')
            p.block((1, 1, 0), state_of('potted_fern'), 'bedside')
            p.block((1, 0, 1), state_of('red_carpet'), 'bedside-rug')
        elif self.purpose == 'library':
            for y in range(3):
                p.block((0, y, 0), state_of('bookshelf'), 'bookcase')
            p.block((0, 0, 1), state_of('lectern', facing='south', has_book='false', powered='false'), 'lectern')
            p.block((1, 1, 0), state_of('bookshelf'), 'bookcase')
            p.block((1, 2, 0), slab('spruce'), 'shelf-cap')
            p.block((1, 0, 1), stairs('spruce', 'north'), 'reading-seat')
        elif self.purpose in ('kitchen', 'workshop'):
            a, b = ('smoker', 'furnace') if self.purpose == 'kitchen' else ('crafting_table', 'smithing_table')
            props = {'facing': 'south', 'lit': 'false'} if self.purpose == 'kitchen' else {}
            p.block((0, 0, 0), state_of(a, **props), 'workstation')
            p.block((0, 0, 1), state_of(b, **props), 'workstation')
            p.block((1, 0, 1), state_of('crafting_table') if self.purpose == 'kitchen' else state_of('anvil', facing='north'), 'counter')
            for x in range(2):
                p.block((x, 2, 0), slab('spruce', 'top'), 'shelf')
        elif self.purpose == 'living':
            p.block((0, 0, 0), stairs('spruce', 'south'), 'seat')
            p.block((1, 0, 1), stairs('spruce', 'west'), 'seat')
            p.block((0, 0, 1), state_of('spruce_fence', north='false', south='false', east='false', west='false', waterlogged='false'), 'table')
            p.block((0, 1, 1), state_of('spruce_pressure_plate', powered='false'), 'tabletop')
            p.block((1, 1, 0), state_of('potted_poppy'), 'flowers')
        else:
            for x, y, z in ((0, 0, 0), (0, 1, 0), (1, 1, 0), (0, 0, 1)):
                p.block((x, y, z), barrel, 'storage', nbt=supplies())
            for x in range(2):
                p.block((x, 2, 0), slab('spruce'), 'shelf-cap')
        return p


@dataclass(frozen=True)
class FurnishingLayout:
    purpose: str
    width: int
    depth: int
    stair: bool = False

    def capability(self):
        return Capability('furniture.' + self.purpose, ('furniture', 'composite'), guarantees=('two or four accessible furniture groups', 'clear circulation cross'), inputs={'width': Domain(choices=(9, 11, 13)), 'depth': Domain(choices=(9, 11, 13))})

    def negotiate(self, ctx, parameters):
        if self.purpose not in GROUPS:
            raise ContractError('room-function', ctx.path)
        w, d = self.width, self.depth
        frames = [Frame((1, 0, 1)), Frame((1, 0, d - 2), 3)]
        if not self.stair:
            frames += [Frame((w - 2, 0, 1), 1), Frame((w - 2, 0, d - 2), 2)]
        table_frames = [Frame((3, 0, 2)), Frame((3, 0, d - 5))] if w >= 11 and d >= 11 else []
        # Tables occupy the wider western bays while preserving the central aisles.
        targets = ((w // 2, 0, d // 2), *(f.point((0, 0, 2)) for f in frames), *(f.point((0, 0, 2)) for f in table_frames))
        cross = tuple((x, y, z) for x in range(1, w - 1) for z in range(1, d - 1) if (x == w // 2 or z == d // 2) and not (self.stair and x >= w - 4 and 2 <= z <= 7) for y in (0, 1))
        box = Box((1, 0, 1), (w - 2, 3, d - 2))
        suites = {'living': ('gathering', 'study'), 'bedroom': ('study', 'cabinet'), 'library': ('study', 'gathering'), 'kitchen': ('cooking', 'gathering'), 'workshop': ('craft', 'cabinet'), 'storage': ('cabinet', 'craft')}
        station_order = list(suites[self.purpose])
        if ctx.rng('station-arrangement').choice((False, True)):
            station_order.reverse()
        return Contract(box, rules=(Rule('clear', cross), Rule('route', targets, box, phase='complete')), decisions={'frames': [(f.origin, f.turn) for f in frames], 'groups': len(frames), 'purpose': self.purpose, 'tables': [(f.origin, f.turn) for f in table_frames], 'stations': station_order if table_frames else []})

    def realize(self, ctx, contract):
        p = Plan()
        functions = {'living': ('living', 'library', 'living', 'storage'), 'bedroom': ('bedroom', 'storage', 'bedroom', 'library'), 'kitchen': ('kitchen', 'storage', 'kitchen', 'living'), 'library': ('library', 'living', 'library', 'storage'), 'workshop': ('workshop', 'storage', 'workshop', 'library'), 'storage': ('storage',) * 4}
        for i, (origin, turn) in enumerate(contract.decisions['frames']):
            p.children.append(Child('group-' + str(i), FurnitureGroup(functions[self.purpose][i]), frame=Frame(tuple(origin), turn)))
        for i, (origin, turn) in enumerate(contract.decisions['tables']):
            from .domestic import DomesticSuite
            p.children.append(Child('station-' + str(i), DomesticSuite(contract.decisions['stations'][i], min(3, self.width // 2 - 3)), frame=Frame(tuple(origin), turn)))
        # One continuous runner occupies only the circulation cross, never the stair slot.
        for z in range(2, self.depth - 2):
            p.block((self.width // 2, 0, z), state_of('red_carpet'), 'runner')
        return p


@dataclass(frozen=True)
class TableSetting:
    width: int = 3

    def capability(self):
        return Capability('furnishing.table-setting', ('furniture', 'seating'), inputs={'width': Domain(choices=(2, 3))}, offers=('interaction',), guarantees=('accessible seats and table',))

    def negotiate(self, ctx, parameters):
        area = Box((0, 0, 0), (self.width - 1, 1, 2))
        clear = tuple((x, y, 2) for x in range(self.width) for y in (0, 1))
        return Contract(area, rules=(Rule('clear', clear), Rule('support', tuple((x, -1, z) for x in range(self.width) for z in range(3))), Rule('furnishing', box=area, data={'groups': [['spruce_stairs'], ['spruce_pressure_plate']]}, phase='complete')), ports=(Port('use', 'interaction', Frame((0, 0, 2)), Box((0, 0, 2), (self.width - 1, 1, 2)), 4),))

    def realize(self, ctx, contract):
        p = Plan()
        table_x = 1 if self.width == 3 else 0
        for z in range(2):
            p.block((table_x, 0, z), state_of('spruce_fence', north=str(z > 0).lower(), south=str(z == 0).lower(), east='false', west='false', waterlogged='false'), 'table')
            p.block((table_x, 1, z), state_of('spruce_pressure_plate', powered='false'), 'tabletop')
            for x in range(self.width):
                if x != table_x:
                    p.block((x, 0, z), stairs('spruce', 'east' if x < table_x else 'west'), 'chair')
        return p
