# Usable hearths, dining groups and measured textiles for furnished room compositions.
from dataclasses import dataclass
from hearth import Box, Frame
from hearth.kernel import Capability, Contract, Domain, Port, Rule, Plan
from hearth.blocks import state_of, stairs, slab, name_of


@dataclass(frozen=True)
class Hearth:
    """A contained campfire hearth with a clear interaction apron."""

    @staticmethod
    def placement_geometry():
        return Box((0, 0, 0), (2, 2, 1)), Box((0, 0, 2), (2, 1, 2)), (1, 0, 2)

    def capability(self):
        return Capability('furnishing.hearth', ('furniture', 'hearth', 'station'), offers=('interaction',), guarantees=('lit contained campfire', 'supported masonry', 'usable apron'))

    def negotiate(self, ctx, parameters):
        body, apron, target = self.placement_geometry()
        masonry = tuple(q for q in body.cells() if q != (1, 1, 1))
        return Contract(Box((0, 0, 0), (2, 2, 2)), ports=(Port('use', 'interaction', Frame(target), apron, 4),), rules=(Rule('support', tuple((x, -1, z) for x in range(3) for z in range(3))), Rule('clear', tuple(apron.cells())), Rule('expected', masonry, data={'names': ['stone_bricks', 'bricks', 'stone_brick_slab']}), Rule('expected', ((1, 1, 1),), data={'names': ['campfire']}), Rule('light', (target,), Box((0, 0, 0), (2, 2, 2)), {'minimum': 1}, 'complete')), atomic_object=True)

    def realize(self, ctx, contract):
        p = Plan()
        p.fill(Box((0, 0, 0), (2, 0, 1)), state_of('stone_bricks'), 'hearthstone')
        for x in range(3):
            p.block((x, 1, 0), state_of('bricks'), 'fireback')
            p.block((x, 2, 0), slab('stone_brick', 'top'), 'mantel')
            p.block((x, 2, 1), slab('stone_brick', 'top'), 'mantel')
        for x in (0, 2):
            p.block((x, 1, 1), state_of('stone_bricks'), 'jamb')
        p.block((1, 1, 1), state_of('campfire', facing='south', lit='true', signal_fire='false', waterlogged='false'), 'contained-fire')
        return p


@dataclass(frozen=True)
class DiningSet:
    length: int = 3

    def placement_geometry(self):
        return Box((0, 0, 0), (2, 1, self.length - 1)), Box((0, 0, self.length), (2, 1, self.length)), (1, 0, self.length)

    def capability(self):
        return Capability('furnishing.dining-set', ('furniture', 'seating', 'station'), inputs={'length': Domain(2, 4)}, offers=('interaction',), guarantees=('paired seating and long table', 'clear approach'))

    def negotiate(self, ctx, parameters):
        body, apron, target = self.placement_geometry()
        return Contract(Box((0, 0, 0), (2, 1, self.length)), ports=(Port('use', 'interaction', Frame(target), apron, 4),), rules=(Rule('support', tuple((x, -1, z) for x in range(3) for z in range(self.length + 1))), Rule('clear', tuple(apron.cells())), Rule('expected', tuple((x, 0, z) for x in (0, 2) for z in range(self.length)), data={'names': ['dark_oak_stairs']}), Rule('expected', tuple((1, 1, z) for z in range(self.length)), data={'names': ['spruce_pressure_plate']})), decisions={'length': self.length}, atomic_object=True)

    def realize(self, ctx, contract):
        p = Plan()
        for z in range(self.length):
            p.block((1, 0, z), state_of('dark_oak_fence', north=str(z > 0).lower(), south=str(z < self.length - 1).lower(), east='false', west='false', waterlogged='false'), 'table-trestle')
            p.block((1, 1, z), state_of('spruce_pressure_plate', powered='false'), 'tabletop')
            for x in (0, 2):
                p.block((x, 0, z), stairs('dark_oak', 'east' if x == 0 else 'west'), 'bench')
        return p


@dataclass(frozen=True)
class FloorTextiles:
    width: int
    depth: int
    centers: tuple
    excluded: tuple = ()

    def capability(self):
        return Capability('furnishing.textiles', ('furniture', 'decoration'), guarantees=('supported passable rugs', 'no replacement'))

    def negotiate(self, ctx, parameters):
        excluded = set(tuple(q) for q in self.excluded)
        cells = set()
        for cx, _, cz in self.centers:
            for x in range(max(1, cx - 1), min(self.width - 1, cx + 2)):
                for z in range(max(1, cz - 1), min(self.depth - 1, cz + 2)):
                    q = (x, 0, z)
                    if q not in excluded and name_of(ctx.state(q)) == 'air' and ctx.view.supports(ctx.world((x, -1, z))):
                        cells.add(q)
        return Contract(Box((1, 0, 1), (self.width - 2, 0, self.depth - 2)), rules=(Rule('support', tuple((x, -1, z) for x, _, z in sorted(cells))),), decisions={'cells': sorted(cells)})

    def realize(self, ctx, contract):
        p = Plan()
        cells = set(tuple(q) for q in contract.decisions['cells'])
        for x, y, z in sorted(cells):
            border = any((x + dx, y, z + dz) not in cells for dx, dz in ((1, 0), (-1, 0), (0, 1), (0, -1)))
            p.block((x, y, z), state_of('brown_carpet' if border else 'red_carpet'), 'woven-rug')
        return p
