# Retained roof-room and veranda assessment with independent frozen environments.
import argparse
import json
from pathlib import Path
from hearth import Scene, Box
from hearth.environment import Terrain, gradient, wave, radial
from hearth.randomness import Scope
from hearth.components import Blueprint, RoomSpec, FacadeDesign, adapted
from hearth.components.roofcraft import RoofDesign
from hearth.lodges import lodge_scene
from hearth.persistence import semantic_digest, encode
from hearth.quality_assessment import evaluate, summary, fingerprint
from examples.roof_home import roof_home


def held_out_design(seed):
    """Compose a three-bay front and a rear wing using the same public intent API."""
    rng = Scope(seed).stream('held-out-room-graph')
    rooms = [RoomSpec(0, 0, 0, 'living'), RoomSpec(1, 0, 0, 'library'), RoomSpec(2, 0, 0, 'workshop'), RoomSpec(0, 1, 0, 'kitchen')]
    rooms.append(RoomSpec(rng.choice((0, 1, 2)), 0, 1, 'bedroom'))
    return Blueprint(tuple(rooms), bay=rng.choice((9, 11, 13)), roof_axis=rng.choice(('x', 'z', 'auto')), roof_design=RoofDesign(rng.choice(((1, 2), (1, 1, 2))), rng.choice((7, 12))), attic_purpose=rng.choice(('bedroom', 'library', 'storage')), porch_bays=3, porch_depth=5, chimney=True, facade_design=FacadeDesign(), rail_material='dark_oak')


def interior_measurements(scene):
    """Retain actual functional measurements independently of principal shape."""
    rooms = [n for n in scene.nodes.values() if n.type.startswith('room.loft.')]
    return {'lofts': len(rooms), 'purposes': [n.contract.decisions['purpose'] for n in rooms], 'stations': sum(len(n.contract.decisions['furniture']) for n in rooms), 'hung_lights': sum(n.type == 'light.roof-hanger' for n in scene.nodes.values()), 'collar_cells': sum(len(scene.cells(n.path)) for n in scene.nodes.values() if n.type == 'timber.roof-collars'), 'guard_cells': sum(len(scene.cells(n.path)) for n in scene.nodes.values() if n.type == 'circulation.aperture-guard'), 'optional_station_omissions': sum(n.contract.decisions['omitted_optional_stations'] for n in rooms), 'furniture_signature': fingerprint([(n.type, sorted(n.frame.inverse(p) for p in scene.cells(n.path))) for n in scene.nodes.values() if n.type.startswith('furnishing.')])}


def run(seed, output, artifacts):
    output.mkdir(parents=True, exist_ok=True)
    artifacts.mkdir(parents=True, exist_ok=True)
    domain = Box((-10, -4, -20), (52, 72, 52))
    environments, inputs, rows = [], [], []
    for index in range(8):
        es = 5100 + seed * 100 + index
        rng = Scope(es).stream('roof-space-environment')
        field = (gradient(rng.choice((-.025, 0, .025)), rng.choice((-.02, 0, .02)), 4) + wave(.3, .13, .09, rng.random() * 6)).blend(gradient(-.02, .02, 4) + radial(18, 18, 36, .5), radial(8, 10, 44, .6)).clamp(2, 6)
        water = rng.choice((None, 4, 5))
        environment = Scene(es, domain=domain)
        environment.place('land', Terrain(domain, field, water))
        environment.finalize()
        environments.append(environment)
        inputs.append({'environment_seed': es, 'expression': field.expression(), 'water': water, 'digest': semantic_digest(environment)})
    (output / 'frozen-inputs.json').write_text(json.dumps(inputs, indent=2))

    def retain(label, inputs, recipe, artifact=None):
        measurements = {}

        def measured():
            scene = recipe()
            measurements.update(interior_measurements(scene))
            return scene

        row = evaluate(label, inputs, measured, artifact)
        row['interior'] = measurements
        rows.append(row)
        (output / 'results.json').write_text(json.dumps(rows, indent=2))
        print(label, row['status'], flush=True)

    for bs in range(8):
        artifact = artifacts / f'roof-home-{bs}.litematic' if bs in (0, 2, 4, 7) else None
        retain(f'roof-home-{bs}', {'structure_seed': bs, 'environment_seed': seed * 100 + bs, 'design': encode(roof_home(bs))}, lambda b=bs: lodge_scene(roof_home(b), b, seed * 100 + b), artifact)
    for index, environment in enumerate(environments):
        for bs in (2, 7):

            def place(e=environment, b=bs):
                scene = e.fork(seed=b)
                scene.compose_choice(adapted('building', roof_home(b), '/land'))
                return scene.finalize()

            label = f'pair-{index}-{bs}'
            artifact = artifacts / (label + '.litematic') if (index, bs) in ((0, 2), (3, 7), (7, 2)) else None
            retain(label, {**inputs[index], 'structure_seed': bs, 'design': encode(roof_home(bs))}, place, artifact)
            if semantic_digest(environment) != inputs[index]['digest']:
                raise AssertionError('Frozen environment changed')
    for index, environment in enumerate(environments):
        bs = seed * 100 + index
        design = held_out_design(bs)

        def place(e=environment, b=bs, d=design):
            scene = e.fork(seed=b)
            scene.compose_choice(adapted('building', d, '/land'))
            return scene.finalize()

        label = f'held-out-{index}'
        artifact = artifacts / (label + '.litematic') if index in (0, 7) else None
        retain(label, {**inputs[index], 'structure_seed': bs, 'design': encode(design)}, place, artifact)
        if semantic_digest(environment) != inputs[index]['digest']:
            raise AssertionError('Frozen environment changed')
    report = {'client': summary(rows[:8]), 'independent_pairs': summary(rows[8:24]), 'held_out': summary(rows[24:])}
    (output / 'summary.json').write_text(json.dumps(report, indent=2))
    (artifacts / 'manifest.json').write_text(json.dumps([r for r in rows if 'artifact' in r], indent=2))
    return report


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--seed', type=int, default=0)
    parser.add_argument('--output', type=Path, default=Path('reports/building-space/new-compositions'))
    parser.add_argument('--artifacts', type=Path, default=Path('samples/building-space/new-compositions'))
    args = parser.parse_args()
    print(json.dumps(run(args.seed, args.output, args.artifacts), indent=2))
