# Pitched roof surfaces and open porch attachments with explicit overlap policy.
from dataclasses import dataclass
from hearth.space import Box, Frame
from hearth.blocks import state_of, stairs, slab, log, name_of
from hearth.kernel import Capability, Contract, Grant, Port, Rule, Plan, Child
from .interior import Light
from .primitives import Window


@dataclass(frozen=True)
class Roof:
    width: int
    depth: int
    axis: str = 'z'
    material: str = 'deepslate_tile'
    steep: bool = True

    def capability(self):
        return Capability('roof.gable', ('roof', 'enclosure'), offers=('junction',), adaptations=('abut taller wall', 'merge shared eaves'), guarantees=('pitched weather skin',))

    def negotiate(self, ctx, parameters):
        span = self.width if self.axis == 'z' else self.depth
        height = (span + 2) // 2 + 2
        box = Box((-1, -1, -1), (self.width, height, self.depth))
        return Contract(box, (Port('junction', 'roof-seam', Frame(), box, 100, 'junction'),), (Grant('junction', box, ('join',), ('roof',), box.volume),), decisions={'axis': self.axis, 'span': span, 'steep': self.steep, 'material': self.material})

    def realize(self, ctx, contract):
        p = Plan()
        w, d = self.width, self.depth
        span = w if self.axis == 'z' else d
        length = d if self.axis == 'z' else w
        for cross in range(-1, span + 1):
            step = min(cross + 1, span - cross)
            y = step if self.steep else (step + 1) // 2
            face = ('east' if cross < span / 2 else 'west') if self.axis == 'z' else ('south' if cross < span / 2 else 'north')
            for along in range(-1, length + 1):
                x, z = (cross, along) if self.axis == 'z' else (along, cross)
                q = ctx.world((x, y, z))
                existing = name_of(ctx.view.state(q))
                if existing != 'air':
                    # Negotiated abutment is only to actual masonry/roof boundaries.
                    continue
                trim = along in (-1, length)
                material = 'dark_oak' if trim else self.material
                p.block((x, y, z), stairs(material, face), 'roof-course')
        for end in (0, length - 1):
            frame = Frame((0, 0, end)) if self.axis == 'z' else Frame((end, 0, span - 1), 3)
            p.children.append(Child(f'gable-{end}', Gable(span, self.steep), frame=frame))
        for cross in (-1, span):
            for along in range(length):
                x, z = (cross, along) if self.axis == 'z' else (along, cross)
                if name_of(ctx.view.state(ctx.world((x, -1, z)))) == 'air':
                    p.block((x, -1, z), slab('spruce', 'top'), 'eave')
        mid = span // 2
        peak = min(mid + 1, span - mid) if self.steep else (min(mid + 1, span - mid) + 1) // 2
        for along in range(-1, length + 1):
            x, z = (mid, along) if self.axis == 'z' else (along, mid)
            if name_of(ctx.view.state(ctx.world((x, peak + 1, z)))) == 'air':
                p.block((x, peak + 1, z), slab('dark_oak'), 'ridge')
        for along in (-1, length):
            x, z = (mid, along) if self.axis == 'z' else (along, mid)
            if name_of(ctx.view.state(ctx.world((x, peak + 2, z)))) == 'air':
                p.block((x, peak + 2, z), state_of('dark_oak_fence', north='false', south='false', east='false', west='false', waterlogged='false'), 'finial')
        return p


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

    def capability(self):
        return Capability('attachment.porch', ('attachment', 'access', 'composite'), guarantees=('open sheltered access',))

    def negotiate(self, ctx, parameters):
        center = self.width // 2
        clear = tuple((x, y, z) for x in range(center - 1, center + 2) for y in (1, 2) for z in range(-self.depth, 0))
        return Contract(Box((0, 0, -self.depth), (self.width - 1, 6, -1)), rules=(Rule('clear', clear),))

    def realize(self, ctx, contract):
        p = Plan()
        w = self.width
        d = self.depth
        p.fill(Box((0, 0, -d), (w - 1, 0, -1)), state_of('spruce_planks'), 'deck')
        for x in (0, w - 1):
            p.fill(Box((x, 1, -d), (x, 4, -d)), log(), 'post')
        for z in range(-d, 0):
            y = 4 + (z + d) // 2
            for x in range(w):
                p.block((x, y, z), slab('dark_oak') if z % 2 else stairs('dark_oak', 'south'), 'canopy')
        for x in range(w):
            if abs(x - w // 2) > 1:
                p.block((x, 1, -d), state_of('spruce_fence', north='false', south='false', east=str(x < w - 1).lower(), west=str(x > 0).lower(), waterlogged='false'), 'balustrade')
        p.children.append(Child('lantern', Light(), frame=Frame((1, 2, -2))))
        return p


@dataclass(frozen=True)
class Gable:
    span: int
    steep: bool = True

    def capability(self):
        return Capability('wall.gable', ('wall', 'enclosure'), offers=('window',), adaptations=('abut existing upper wall',))

    def negotiate(self, ctx, parameters):
        height = (self.span + 1) // 2
        region = Box((1, 0, 0), (self.span - 2, height - 2, 0))
        box = Box((0, -1, -1), (self.span - 1, height - 1, 0))
        return Contract(box, (Port('infill', 'wall-installation', Frame((1, 0, 0)), region, 4, 'install', facts={'thickness': 1}),), (Grant('install', region, ('install',), ('window',), 30),))

    def realize(self, ctx, contract):
        p = Plan()
        for x in range(self.span):
            step = min(x + 1, self.span - x)
            y = step if self.steep else (step + 1) // 2
            for yy in range(y):
                if name_of(ctx.view.state(ctx.world((x, yy, 0)))) == 'air':
                    p.block((x, yy, 0), log('spruce') if x in (0, self.span // 2, self.span - 1) else state_of('smooth_sandstone'), 'gable-infill')
        available = {w.point for w in p.writes}
        at = (self.span // 2 - 1, 0, 0)
        if self.steep and all((at[0] + x, y, 0) in available for x in range(2) for y in (0, 1)) and all(name_of(ctx.view.state(ctx.world((at[0] + x, 1, -1)))) == 'air' for x in range(2)):
            from hearth.kernel import Binding
            p.children.append(Child('window', Window(2, 2, False), frame=Frame(at), bindings=(Binding(ctx.path, 'infill'),)))
        return p
