# Domestic garden courts using public access ports and bounded garden foundations.
from dataclasses import dataclass
from hearth import Box, Frame, ContractError
from hearth.kernel import Capability, Contract, Plan, Child, Binding
from hearth.adapters.site import Foundation, SiteLimits
from hearth.blocks import state_of, stairs
from .landscape import PathNetwork, Planter, Landscape, LampPost


@dataclass(frozen=True)
class RaisedBed:
    terrain: str
    width: int = 3

    def capability(self):
        return Capability('garden.raised-bed', ('garden', 'composite'), adaptations=('bounded stepped footing',))

    def negotiate(self, ctx, parameters):
        heights = [ctx.elevation(x, z) for x in range(self.width + 2) for z in range(4)]
        if max(heights) - min(heights) > 2:
            raise ContractError('garden-relief', ctx.path)
        floor = max(heights) + 1
        return Contract(Box((0, min(heights) - 1, 0), (self.width + 1, floor + 1, 3)), decisions={'floor': floor})

    def realize(self, ctx, contract):
        floor = contract.decisions['floor']
        return Plan(children=[Child('footing', Foundation.rectangular(self.width + 2, 4, floor, limits=SiteLimits(support_height=3, fill_volume=60)), bindings=(Binding(self.terrain, 'construction', 'fill'),)), Child('soil', GardenSoil(self.width, floor - 1), bindings=(Binding(self.terrain, 'construction', 'fill'),)), Child('bed', Planter(self.width), frame=Frame((1, floor, 1))), Child('edging', BedBorder(self.width + 2, 4), frame=Frame((0, floor, 0)))])


@dataclass(frozen=True)
class GardenSoil:
    width: int
    top: int

    def capability(self):
        return Capability('garden.soil-lift', ('siteworks', 'support'), guarantees=('continuous substrate below planting bed', 'at most two cells of fill'))

    def negotiate(self, ctx, parameters):
        from hearth.kernel import Rule
        columns = [(x, ctx.elevation(x, z), z) for x in range(1, self.width + 1) for z in (1, 2)]
        if any(self.top - y > 2 or y > self.top for x, y, z in columns):
            raise ContractError('garden-soil-depth', ctx.path)
        support = tuple((x, self.top, z) for x, _, z in columns)
        return Contract(Box((1, min(y for _, y, _ in columns), 1), (self.width, self.top, 2)), rules=(Rule('support', support),), decisions={'columns': columns})

    def realize(self, ctx, contract):
        p = Plan()
        for x, bottom, z in contract.decisions['columns']:
            for y in range(bottom + 1, self.top + 1):
                p.block((x, y, z), state_of('dirt'), 'soil-fill')
        return p


@dataclass(frozen=True)
class BedBorder:
    width: int
    depth: int

    def capability(self):
        return Capability('garden.stone-edging', ('garden', 'masonry'), guarantees=('supported planting boundary',))

    def negotiate(self, ctx, parameters):
        from hearth.kernel import Rule
        edge = tuple((x, 0, z) for x in range(self.width) for z in range(self.depth) if x in (0, self.width - 1) or z in (0, self.depth - 1))
        return Contract(Box((0, 0, 0), (self.width - 1, 0, self.depth - 1)), rules=(Rule('support', tuple((x, -1, z) for x, _, z in edge)),), decisions={'edge': edge})

    def realize(self, ctx, contract):
        p = Plan()
        for x, y, z in contract.decisions['edge']:
            face = 'south' if z == 0 else 'north' if z == self.depth - 1 else 'east' if x == 0 else 'west'
            p.block((x, y, z), stairs('stone_brick', face), 'edging')
        return p


@dataclass(frozen=True)
class GardenCourt:
    building: str
    terrain: str
    area: Box
    beds: int = 4
    trees: int = 3

    def capability(self):
        return Capability('garden.domestic-court', ('garden', 'composite'), assumptions=('access street port',), guarantees=('clear path to public entrance', 'bounded garden beds', 'retained building circulation'))

    def negotiate(self, ctx, parameters):
        port = ctx.view.port(self.building, 'street')
        if port.kind != 'access':
            raise ContractError('garden-access', ctx.path)
        access = ctx.local(port.frame.origin)
        gateway = (access[0], ctx.elevation(access[0], self.area.lo[2] + 2) + 1, self.area.lo[2] + 2)
        return Contract(self.area, decisions={'access': access, 'gateway': gateway, 'beds': self.beds, 'trees': self.trees})

    def realize(self, ctx, contract):
        access, gateway = contract.decisions['access'], contract.decisions['gateway']
        p = Plan(children=[Child('walk', PathNetwork((access, gateway), ((0, 1),)), bindings=(Binding(self.terrain, 'construction', 'surface'), Binding(self.building, 'street', 'surface')))])
        for i in range(self.beds):
            side = -1 if i % 2 == 0 else 1
            x = access[0] + (side * 5) - (2 if side < 0 else 0)
            z = gateway[2] + 2 + (i // 2) * 4
            p.children.append(Child(f'bed-{i}', RaisedBed(self.terrain), frame=Frame((x, 0, z))))
        for i, x in enumerate((access[0] - 3, access[0] + 3)):
            y = ctx.elevation(x, gateway[2] + 1) + 1
            p.children.append(Child(f'light-{i}', LampPost(), frame=Frame((x, y, gateway[2] + 1))))
        p.children.append(Child('planting', Landscape(self.area, self.terrain, self.trees, self.trees + 1, .035, sample_step=2, heights=(9, 11, 13))))
        return p
