#!/usr/bin/env python3
"""Reproduce Temporary Fence Span-Rounding Audit version 1.0.0.

The script uses integer half-foot units so every tested width is represented
exactly, including 11.5 feet. It writes the full audit, summary, and straight-
run lookup published by Temporary Fence Rental Chattanooga Research.
"""
from __future__ import annotations

import csv
from collections import Counter
from pathlib import Path

OUT_DIR = Path(__file__).resolve().parent
WIDTHS_HALF_FEET = (16, 20, 23, 24)  # 8, 10, 11.5, 12 feet
SIDE_MIN_FT = 25
SIDE_MAX_FT = 250
LOOKUP_LENGTHS_FT = (
    10, 15, 20, 25, 30, 40, 50, 60, 75, 100, 120, 150, 175, 200,
    250, 300, 400, 500, 600, 750, 1000, 1500, 2000,
)

AUDIT_NAME = "temporary-fence-span-rounding-audit-2026-07-27.csv"
SUMMARY_NAME = "temporary-fence-rounding-audit-summary-2026-07-27.csv"
LOOKUP_NAME = "temporary-fence-straight-run-lookup-2026-07-27.csv"


def ceil_div(numerator: int, denominator: int) -> int:
    if numerator < 0 or denominator <= 0:
        raise ValueError("ceil_div requires a nonnegative numerator and positive denominator")
    return (numerator + denominator - 1) // denominator


def width_label(width_half_feet: int) -> str:
    return str(width_half_feet // 2) if width_half_feet % 2 == 0 else f"{width_half_feet / 2:g}"


def coverage_label(panel_count: int, width_half_feet: int) -> str:
    return f"{panel_count * width_half_feet / 2:.1f}"


def write_audit() -> dict[int, Counter[int]]:
    deficits_by_width: dict[int, Counter[int]] = {
        width: Counter() for width in WIDTHS_HALF_FEET
    }
    path = OUT_DIR / AUDIT_NAME
    with path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        writer.writerow([
            "panel_width_ft",
            "side_a_ft",
            "side_b_ft",
            "perimeter_ft",
            "panels_total_perimeter_method",
            "panels_span_aware_method",
            "deficit_panels",
        ])
        for width in WIDTHS_HALF_FEET:
            for side_a in range(SIDE_MIN_FT, SIDE_MAX_FT + 1):
                for side_b in range(side_a, SIDE_MAX_FT + 1):
                    perimeter_ft = 2 * (side_a + side_b)
                    total_only = ceil_div(perimeter_ft * 2, width)
                    span_aware = (
                        2 * ceil_div(side_a * 2, width)
                        + 2 * ceil_div(side_b * 2, width)
                    )
                    deficit = span_aware - total_only
                    deficits_by_width[width][deficit] += 1
                    writer.writerow([
                        width_label(width),
                        side_a,
                        side_b,
                        perimeter_ft,
                        total_only,
                        span_aware,
                        deficit,
                    ])
    return deficits_by_width


def write_summary(deficits_by_width: dict[int, Counter[int]]) -> None:
    path = OUT_DIR / SUMMARY_NAME
    all_deficits: Counter[int] = Counter()
    with path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        writer.writerow([
            "panel_width_ft",
            "rectangles_tested",
            "exact_matches",
            "undercounts",
            "undercount_rate_pct",
            "mean_deficit_when_short",
            "max_deficit",
            "short_by_1",
            "short_by_2",
            "short_by_3",
        ])
        for width in WIDTHS_HALF_FEET:
            counts = deficits_by_width[width]
            all_deficits.update(counts)
            total = sum(counts.values())
            exact = counts[0]
            under = total - exact
            weighted_deficit = sum(deficit * count for deficit, count in counts.items())
            writer.writerow([
                width_label(width),
                total,
                exact,
                under,
                f"{100 * under / total:.4f}",
                f"{weighted_deficit / under:.4f}",
                max(counts),
                counts[1],
                counts[2],
                counts[3],
            ])
        total = sum(all_deficits.values())
        exact = all_deficits[0]
        under = total - exact
        writer.writerow([
            "ALL",
            total,
            exact,
            under,
            f"{100 * under / total:.4f}",
            "",
            max(all_deficits),
            all_deficits[1],
            all_deficits[2],
            all_deficits[3],
        ])


def write_lookup() -> None:
    path = OUT_DIR / LOOKUP_NAME
    with path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        writer.writerow([
            "run_length_ft",
            "panels_8ft",
            "nominal_coverage_8ft",
            "panels_10ft",
            "nominal_coverage_10ft",
            "panels_11_5ft",
            "nominal_coverage_11_5ft",
            "panels_12ft",
            "nominal_coverage_12ft",
        ])
        for run_length in LOOKUP_LENGTHS_FT:
            row: list[str | int] = [run_length]
            for width in WIDTHS_HALF_FEET:
                panels = ceil_div(run_length * 2, width)
                row.extend([panels, coverage_label(panels, width)])
            writer.writerow(row)


def main() -> None:
    deficits = write_audit()
    write_summary(deficits)
    write_lookup()
    print(OUT_DIR / AUDIT_NAME)
    print(OUT_DIR / SUMMARY_NAME)
    print(OUT_DIR / LOOKUP_NAME)


if __name__ == "__main__":
    main()
