#!/usr/bin/env python3
"""
Generate markdown tables for the Kim+Sam Roth conversion case study.

This script runs the Kim+Sam spending case across fixed and historical
rate scenarios with Medicare handled via optimization or a self-consistent loop.
It outputs a single markdown document with the tables.
"""

from __future__ import annotations

import sys
import argparse
from pathlib import Path
from datetime import datetime

import numpy as np
import pandas as pd

sys.path.insert(0, "./src")

import owlplanner as owl  # noqa: E402
from owlplanner import plan as owl_plan  # noqa: E402
from owlplanner import rates as owl_rates  # noqa: E402


DEFAULT_BASE_CASE = Path("examples/Case_kim+sam-spending.toml")
DEFAULT_OUTPUT = Path("roth_case_study.md")
DEFAULT_OUTPUT_BEQUEST = Path("roth_case_study_bequest.md")
DEFAULT_REPORT = Path("convergence_report.md")
DEFAULT_REPORT_BEQUEST = Path("convergence_report_bequest.md")
DEFAULT_MAX_ROTH = [200, 150, 100, 50, 10, 0]  # nominal k$


SCENARIOS = [
    {
        "id": "Case 1",
        "label": "Fixed conservative rates",
        "rate_method": "user",
        "rate_values": [6.0, 4.0, 3.3, 2.8],
        "rate_from": None,
    },
    {
        "id": "Case 2",
        "label": "Fixed optimistic rates",
        "rate_method": "user",
        "rate_values": [8.0, 4.5, 3.3, 2.8],
        "rate_from": None,
    },
    {
        "id": "Case 3",
        "label": "Historical rates from 1966",
        "rate_method": "historical",
        "rate_values": None,
        "rate_from": 1966,
    },
    {
        "id": "Case 4",
        "label": "Historical rates from 1991",
        "rate_method": "historical",
        "rate_values": None,
        "rate_from": 1991,
    },
]


MEDICARE_MODES = [
    ("optimize", "a", "Medicare/IRMAA optimized"),
    ("loop", "b", "Medicare/IRMAA solved self-consistently"),
]

BEQUEST_TAX_RATES = [33, 22, 0]
BEQUEST_NET_SPENDING = 145  # k$ (inflation-adjusted)

# Optional per-case overrides for (absTol, relTol, gap)
# Keyed by (scenario_id, with_medicare, max_roth_k)
TUNING_OVERRIDES: dict[tuple[str, str, int], dict[str, float]] = {
    # Relax SC loop tolerance for Case 4 optimize at low Roth caps.
    ("Case 4", "optimize", 0): {"relTol": 2e-4},
    ("Case 4", "optimize", 10): {"relTol": 2e-4},
}


def parse_max_roth(values: str) -> list[int]:
    items = [int(item.strip()) for item in values.split(",") if item.strip()]
    if not items:
        raise ValueError("max Roth list cannot be empty")
    return items


def case_report_path(scenario_id: str, study: str) -> Path:
    digits = "".join(ch for ch in scenario_id if ch.isdigit())
    suffix = digits if digits else scenario_id.strip().replace(" ", "-").lower()
    return Path(f"convergence_report_case-{suffix}-{study}.md")


def report_title_for_case(scenario_id: str, study: str) -> str:
    return f"Convergence report - {scenario_id} ({study})"


def format_currency(value: float) -> str:
    return f"${value:,.0f}"


def format_percent(value: float) -> str:
    return f"{value * 100:.2f}%"


def load_plan(base_case: Path) -> owl_plan.Plan:
    return owl.readConfig(str(base_case), loadHFP=True)


def default_tolerances(with_medicare: str, max_roth_k: int) -> dict[str, float]:
    gap = owl_plan.GAP
    if with_medicare == "optimize":
        fac = 10 if max_roth_k <= 15 else 1
        gap = fac * owl_plan.MILP_GAP
    rel_tol = max(owl_plan.REL_TOL, gap / 300)
    abs_tol = owl_plan.ABS_TOL
    return {"absTol": abs_tol, "relTol": rel_tol, "gap": gap}


def case_key(scenario_id: str, with_medicare: str, max_roth_k: int) -> tuple[str, str, int]:
    return (scenario_id, with_medicare, max_roth_k)


def resolve_tolerances(scenario_id: str, with_medicare: str, max_roth_k: int) -> dict[str, float]:
    tol = default_tolerances(with_medicare, max_roth_k)
    override = TUNING_OVERRIDES.get(case_key(scenario_id, with_medicare, max_roth_k))
    if override:
        for key, value in override.items():
            tol[key] = value
        if "gap" in override and "relTol" not in override:
            tol["relTol"] = max(owl_plan.REL_TOL, tol["gap"] / 300)
    return tol


def configure_plan(
    plan,
    scenario,
    with_medicare: str,
    max_roth_k: int,
    *,
    objective: str,
    net_spending_k: int | None = None,
    heirs_tax_rate: int | None = None,
    solver_verbose: bool = False,
) -> dict[str, float]:
    if scenario["rate_method"] == "user":
        plan.setRates("user", values=scenario["rate_values"])
    elif scenario["rate_method"] == "historical":
        plan.setRates("historical", frm=scenario["rate_from"], to=owl_rates.TO)
    else:
        raise ValueError(f"Unknown rate method: {scenario['rate_method']}")

    plan.objective = objective
    plan.solverOptions["withMedicare"] = with_medicare
    plan.solverOptions["maxRothConversion"] = max_roth_k
    plan.solverOptions["verbose"] = 1 if solver_verbose else 0
    if objective == "maxBequest":
        if net_spending_k is None:
            raise ValueError("net_spending_k is required for maxBequest.")
        plan.solverOptions["netSpending"] = net_spending_k
    if heirs_tax_rate is not None:
        plan.setHeirsTaxRate(heirs_tax_rate)
    tolerances = resolve_tolerances(scenario["id"], with_medicare, max_roth_k)
    plan.solverOptions["absTol"] = tolerances["absTol"]
    plan.solverOptions["relTol"] = tolerances["relTol"]
    plan.solverOptions["gap"] = tolerances["gap"]
    return tolerances


def compute_metrics(plan, baseline_total_spending_today: float) -> dict[str, float]:
    net_spending_today = plan.g_n[0] / plan.gamma_n[0]
    total_net_spending_today = float(np.sum(plan.g_n / plan.gamma_n[:-1], axis=0))
    total_roth_nominal = float(np.sum(plan.x_in, axis=None))
    total_roth_today = float(np.sum(np.sum(plan.x_in, axis=0) / plan.gamma_n[:-1], axis=0))

    net_benefit_today = total_net_spending_today - baseline_total_spending_today
    net_benefit_pct = 0.0
    if total_net_spending_today:
        net_benefit_pct = net_benefit_today / total_net_spending_today

    benefit_pct_of_roth_today = 0.0
    if total_roth_today:
        benefit_pct_of_roth_today = net_benefit_today / total_roth_today

    return {
        "net_spending_today": net_spending_today,
        "total_net_spending_today": total_net_spending_today,
        "net_benefit_today": net_benefit_today,
        "net_benefit_pct": net_benefit_pct,
        "total_roth_nominal": total_roth_nominal,
        "total_roth_today": total_roth_today,
        "benefit_pct_of_roth_today": benefit_pct_of_roth_today,
    }


def write_report(
    report_path: Path,
    report_rows: list[dict[str, object]],
    *,
    title: str | None = None,
) -> None:
    header = title or "Convergence report"
    report = f"# {header}\n\n"
    report += f"Generated: {datetime.now().isoformat(sep=' ', timespec='seconds')}\n\n"
    if not report_rows:
        report += "No convergence data collected.\n"
        report_path.write_text(report, encoding="utf-8")
        return
    df = pd.DataFrame(report_rows)
    df = df.sort_values(by=["objective", "scenario", "medicare", "heirs_tax_rate", "max_roth_k"])
    df["gap"] = df["gap"].map(lambda x: f"{x:.2e}")
    df["relTol"] = df["relTol"].map(lambda x: f"{x:.2e}")
    df["absTol"] = df["absTol"].map(lambda x: f"{x:.2f}")
    report += df.to_markdown(index=False)
    report += "\n"
    report_path.write_text(report, encoding="utf-8")


def record_report_row(
    report_rows: list[dict[str, object]],
    report_path: Path,
    row: dict[str, object],
    report_title: str | None = None,
) -> None:
    report_rows.append(row)
    write_report(report_path, report_rows, title=report_title)


def run_table(
    base_case: Path,
    scenario,
    with_medicare: str,
    max_roth_list: list[int],
    report_rows: list[dict[str, object]],
    report_path: Path,
    verbose: bool,
    report_title: str | None = None,
    *,
    objective: str,
    net_spending_k: int | None = None,
    heirs_tax_rate: int | None = None,
    solver_verbose: bool = False,
) -> pd.DataFrame:
    baseline_plan = load_plan(base_case)
    base_tols = configure_plan(
        baseline_plan,
        scenario,
        with_medicare,
        0,
        objective=objective,
        net_spending_k=net_spending_k,
        heirs_tax_rate=heirs_tax_rate,
        solver_verbose=solver_verbose,
    )
    if verbose:
        baseline_plan.setVerbose(True)
    baseline_plan.solve(baseline_plan.objective, baseline_plan.solverOptions)
    if verbose:
        baseline_plan.setVerbose(False)
    if baseline_plan.caseStatus != "solved":
        raise RuntimeError(f"Baseline case failed: {scenario['id']} {with_medicare}")
    baseline_total_spending_today = float(
        np.sum(baseline_plan.g_n / baseline_plan.gamma_n[:-1], axis=0)
    )
    record_report_row(
        report_rows,
        report_path,
        {
            "scenario": scenario["id"],
            "scenario_label": scenario["label"],
            "medicare": with_medicare,
            "max_roth_k": 0,
            "absTol": base_tols["absTol"],
            "relTol": base_tols["relTol"],
            "gap": base_tols["gap"],
            "case_status": baseline_plan.caseStatus,
            "convergence": baseline_plan.convergenceType,
            "objective": objective,
            "net_spending_k": net_spending_k,
            "heirs_tax_rate": heirs_tax_rate,
        },
        report_title,
    )

    rows = []
    for max_roth in max_roth_list:
        plan = load_plan(base_case)
        tolerances = configure_plan(
            plan,
            scenario,
            with_medicare,
            max_roth,
            objective=objective,
            net_spending_k=net_spending_k,
            heirs_tax_rate=heirs_tax_rate,
            solver_verbose=solver_verbose,
        )
        print(f"\n==== {scenario['id']} ======= {scenario['label']} ==== ${max_roth}k =====")
        if verbose:
            plan.setVerbose(True)
        plan.solve(plan.objective, plan.solverOptions)
        if verbose:
            plan.setVerbose(False)
        if plan.caseStatus != "solved":
            raise RuntimeError(f"Case failed: {scenario['id']} {with_medicare} max={max_roth}")
        record_report_row(
            report_rows,
            report_path,
            {
                "scenario": scenario["id"],
                "scenario_label": scenario["label"],
                "medicare": with_medicare,
                "max_roth_k": max_roth,
                "absTol": tolerances["absTol"],
                "relTol": tolerances["relTol"],
                "gap": tolerances["gap"],
                "case_status": plan.caseStatus,
                "convergence": plan.convergenceType,
                "objective": objective,
                "net_spending_k": net_spending_k,
                "heirs_tax_rate": heirs_tax_rate,
            },
            report_title,
        )

        if objective == "maxSpending":
            metrics = compute_metrics(plan, baseline_total_spending_today)
            rows.append(
                {
                    "max Roth X (nominal k$)": max_roth,
                    "net spending (today's $)": metrics["net_spending_today"],
                    "total net spending (today's $)": metrics["total_net_spending_today"],
                    "net benefit (today's $)": metrics["net_benefit_today"],
                    "net benefit (%)": metrics["net_benefit_pct"],
                    "total Roth X (nominal $)": metrics["total_roth_nominal"],
                    "total Roth converted (today's $)": metrics["total_roth_today"],
                    "benefit as % of total converted": metrics["benefit_pct_of_roth_today"],
                }
            )
        else:
            net_bequest_today = float(plan.bequest)
            base_bequest_today = float(baseline_plan.bequest)
            net_benefit = net_bequest_today - base_bequest_today
            net_benefit_pct = 0.0
            if net_bequest_today:
                net_benefit_pct = net_benefit / net_bequest_today
            total_roth_nominal = float(np.sum(plan.x_in, axis=None))
            rows.append(
                {
                    "max Roth X (nominal k$)": max_roth,
                    "net spending (today's $)": format_currency(net_spending_k * 1000),
                    "net bequest (today's $)": net_bequest_today,
                    "net benefit (today's $)": net_benefit,
                    "net benefit (%)": net_benefit_pct,
                    "total Roth X (nominal $)": total_roth_nominal,
                }
            )

    df = pd.DataFrame(rows)
    df["max Roth X (nominal k$)"] = df["max Roth X (nominal k$)"].astype(int)
    df["net spending (today's $)"] = df["net spending (today's $)"].map(format_currency)
    df["net benefit (today's $)"] = df["net benefit (today's $)"].map(format_currency)
    df["net benefit (%)"] = df["net benefit (%)"].map(format_percent)
    df["total Roth X (nominal $)"] = df["total Roth X (nominal $)"].map(format_currency)
    if objective == "maxSpending":
        df["total net spending (today's $)"] = df["total net spending (today's $)"].map(format_currency)
        df["total Roth converted (today's $)"] = df["total Roth converted (today's $)"].map(format_currency)
        df["benefit as % of total converted"] = df["benefit as % of total converted"].map(format_percent)
    else:
        df["net bequest (today's $)"] = df["net bequest (today's $)"].map(format_currency)
    return df


def render_section(title: str, df: pd.DataFrame) -> str:
    return f"### {title}\n\n{df.to_markdown(index=False)}\n\n"


def build_markdown(
    base_case: Path,
    max_roth_list: list[int],
    include_no_medicare: bool,
    report_rows: list[dict[str, object]],
    report_path: Path,
    verbose: bool,
    scenarios: list[dict[str, object]],
    medicare_modes: list[tuple[str, str, str]],
    *,
    objective: str,
    net_spending_k: int | None = None,
    heirs_tax_rates: list[int] | None = None,
    solver_verbose: bool = False,
) -> str:
    sections = []
    if objective == "maxSpending":
        sections.append("# Roth conversion case study (Kim + Sam)\n")
        sections.append(
            "Net spending is an annual value, as is net benefit. Totals are over the full plan.\n\n"
        )
    else:
        sections.append("# Roth conversion case study (Kim + Sam) - Bequest optimization\n")
        sections.append(
            "Net spending is fixed at $145,000 (inflation-adjusted). "
            "Net benefit is relative to the 0-conversion baseline.\n\n"
        )

    for scenario in scenarios:
        for with_medicare, suffix, label in medicare_modes:
            if objective == "maxBequest":
                if heirs_tax_rates is None:
                    raise ValueError("heirs_tax_rates required for maxBequest.")
                for heirs_tax_rate in heirs_tax_rates:
                    title = (
                        f"{scenario['id']}{suffix}-{heirs_tax_rate}: {scenario['label']} with "
                        f"{label} and {heirs_tax_rate}% heirs' marginal tax rate"
                    )
                    df = run_table(
                        base_case,
                        scenario,
                        with_medicare,
                        max_roth_list,
                        report_rows,
                        report_path,
                        verbose,
                        objective=objective,
                        net_spending_k=net_spending_k,
                        heirs_tax_rate=heirs_tax_rate,
                        solver_verbose=solver_verbose,
                    )
                    sections.append(render_section(title, df))
            else:
                title = f"{scenario['id']}{suffix}: {scenario['label']} with {label}"
                df = run_table(
                    base_case,
                    scenario,
                    with_medicare,
                    max_roth_list,
                    report_rows,
                    report_path,
                    verbose,
                    objective=objective,
                    solver_verbose=solver_verbose,
                )
                sections.append(render_section(title, df))

        if include_no_medicare and scenario["id"] == "Case 1" and objective == "maxSpending":
            title = f"{scenario['id']}c: {scenario['label']} with no Medicare calculations"
            df = run_table(
                base_case,
                scenario,
                "None",
                max_roth_list,
                report_rows,
                report_path,
                verbose,
                objective=objective,
                solver_verbose=solver_verbose,
            )
            sections.append(render_section(title, df))

    return "".join(sections)


def run_report_only(
    base_case: Path,
    max_roth_list: list[int],
    report_rows: list[dict[str, object]],
    report_path: Path,
    verbose: bool,
    scenarios: list[dict[str, object]],
    medicare_modes: list[tuple[str, str, str]],
    report_title: str | None = None,
    *,
    objective: str,
    net_spending_k: int | None = None,
    heirs_tax_rates: list[int] | None = None,
    solver_verbose: bool = False,
) -> None:
    for scenario in scenarios:
        for with_medicare, _, _ in medicare_modes:
            heirs_rates = heirs_tax_rates or [None]
            for heirs_tax_rate in heirs_rates:
                for max_roth in max_roth_list:
                    plan = load_plan(base_case)
                    tolerances = configure_plan(
                        plan,
                        scenario,
                        with_medicare,
                        max_roth,
                        objective=objective,
                        net_spending_k=net_spending_k,
                        heirs_tax_rate=heirs_tax_rate,
                        solver_verbose=solver_verbose,
                    )
                    print(
                        f"\n==== {scenario['id']} ======= {scenario['label']} ==== ${max_roth}k ====="
                    )
                    if verbose:
                        plan.setVerbose(True)
                    plan.solve(plan.objective, plan.solverOptions)
                    if verbose:
                        plan.setVerbose(False)
                    if plan.caseStatus != "solved":
                        raise RuntimeError(
                            f"Case failed: {scenario['id']} {with_medicare} max={max_roth}"
                        )
                    record_report_row(
                        report_rows,
                        report_path,
                        {
                            "scenario": scenario["id"],
                            "scenario_label": scenario["label"],
                            "medicare": with_medicare,
                            "max_roth_k": max_roth,
                            "absTol": tolerances["absTol"],
                            "relTol": tolerances["relTol"],
                            "gap": tolerances["gap"],
                            "case_status": plan.caseStatus,
                            "convergence": plan.convergenceType,
                            "objective": objective,
                            "net_spending_k": net_spending_k,
                            "heirs_tax_rate": heirs_tax_rate,
                        },
                        report_title,
                    )


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate Roth case study tables.")
    parser.add_argument(
        "--base-case",
        type=Path,
        default=DEFAULT_BASE_CASE,
        help="Path to the base TOML case file.",
    )
    parser.add_argument(
        "--output",
        type=Path,
        default=DEFAULT_OUTPUT,
        help="Markdown output file (written to current directory if relative).",
    )
    parser.add_argument(
        "--output-bequest",
        type=Path,
        default=DEFAULT_OUTPUT_BEQUEST,
        help="Markdown output file for bequest study.",
    )
    parser.add_argument(
        "--report",
        type=Path,
        default=None,
        help="Convergence report markdown file (default: convergence_report.md).",
    )
    parser.add_argument(
        "--report-bequest",
        type=Path,
        default=None,
        help="Convergence report markdown file for bequest study (default: convergence_report_bequest.md).",
    )
    parser.add_argument(
        "--scenario",
        action="append",
        default=[],
        help="Filter to a scenario id (e.g., 'Case 4' or '4'). Can be repeated.",
    )
    parser.add_argument(
        "--medicare",
        action="append",
        default=[],
        help="Filter to Medicare mode: optimize, loop, or none. Can be repeated.",
    )
    parser.add_argument(
        "--quiet",
        action="store_true",
        help="Disable verbose solver logging.",
    )
    parser.add_argument(
        "--report-only",
        action="store_true",
        help="Only run cases and write convergence report.",
    )
    parser.add_argument(
        "--study",
        choices=["spending", "bequest", "both"],
        default="spending",
        help="Which study to run (spending, bequest, or both).",
    )
    parser.add_argument(
        "--solver-verbose",
        action="store_true",
        help="Enable MILP solver verbosity (HiGHS output).",
    )
    parser.add_argument(
        "--heirs-tax-rate",
        action="append",
        type=int,
        default=[],
        help="Filter bequest study to heirs' tax rate (can repeat).",
    )
    parser.add_argument(
        "--max-roth",
        type=parse_max_roth,
        default=",".join(str(v) for v in DEFAULT_MAX_ROTH),
        help="Comma-separated list of max Roth conversions in nominal k$.",
    )
    parser.add_argument(
        "--skip-no-medicare",
        action="store_true",
        help="Skip Case 1c (no Medicare calculations).",
    )
    args = parser.parse_args()

    base_case = args.base_case
    output_path = args.output
    report_path = args.report or DEFAULT_REPORT
    output_bequest_path = args.output_bequest
    report_bequest_path = args.report_bequest or DEFAULT_REPORT_BEQUEST
    max_roth_list = args.max_roth
    verbose = not args.quiet
    solver_verbose = args.solver_verbose
    bequest_heirs_rates = BEQUEST_TAX_RATES
    if args.heirs_tax_rate:
        bequest_heirs_rates = args.heirs_tax_rate

    if args.scenario:
        selected = set()
        for item in args.scenario:
            token = item.strip()
            if token.isdigit():
                token = f"Case {int(token)}"
            if token.lower().startswith("case"):
                parts = token.split()
                if len(parts) == 2 and parts[1].isdigit():
                    token = f"Case {int(parts[1])}"
            selected.add(token)
        scenarios = [s for s in SCENARIOS if s["id"] in selected]
        if not scenarios:
            raise ValueError(f"No scenarios matched: {args.scenario}")
    else:
        scenarios = SCENARIOS

    medicare_modes = MEDICARE_MODES
    include_no_medicare = not args.skip_no_medicare
    if args.medicare:
        med_tokens = {t.strip().lower() for t in args.medicare}
        medicare_modes = [
            mode for mode in MEDICARE_MODES if mode[0] in med_tokens
        ]
        include_no_medicare = include_no_medicare and ("none" in med_tokens)

    if args.study in ["spending", "both"]:
        if args.report_only:
            if args.report is not None and len(scenarios) > 1:
                print(
                    "Warning: --report provided; the same file will be overwritten "
                    "for each scenario."
                )
            for scenario in scenarios:
                case_path = (
                    args.report
                    if args.report is not None
                    else case_report_path(scenario["id"], "spending")
                )
                report_title = report_title_for_case(scenario["id"], "spending")
                report_rows: list[dict[str, object]] = []
                run_report_only(
                    base_case,
                    max_roth_list,
                    report_rows,
                    case_path,
                    verbose,
                    [scenario],
                    medicare_modes,
                    report_title=report_title,
                    objective="maxSpending",
                    solver_verbose=solver_verbose,
                )
                print(f"Wrote {case_path}")
        else:
            report_rows: list[dict[str, object]] = []
            markdown = build_markdown(
                base_case,
                max_roth_list,
                include_no_medicare,
                report_rows,
                report_path,
                verbose,
                scenarios,
                medicare_modes,
                objective="maxSpending",
                solver_verbose=solver_verbose,
            )
            output_path.write_text(markdown, encoding="utf-8")
            write_report(report_path, report_rows)
            print(f"Wrote {output_path}")
            print(f"Wrote {report_path}")

    if args.study in ["bequest", "both"]:
        if args.report_only:
            if args.report_bequest is not None and len(scenarios) > 1:
                print(
                    "Warning: --report-bequest provided; the same file will be "
                    "overwritten for each scenario."
                )
            for scenario in scenarios:
                case_path = (
                    args.report_bequest
                    if args.report_bequest is not None
                    else case_report_path(scenario["id"], "bequest")
                )
                report_title = report_title_for_case(scenario["id"], "bequest")
                report_rows: list[dict[str, object]] = []
                run_report_only(
                    base_case,
                    max_roth_list,
                    report_rows,
                    case_path,
                    verbose,
                    [scenario],
                    medicare_modes,
                    report_title=report_title,
                    objective="maxBequest",
                    net_spending_k=BEQUEST_NET_SPENDING,
                    heirs_tax_rates=bequest_heirs_rates,
                    solver_verbose=solver_verbose,
                )
                print(f"Wrote {case_path}")
        else:
            report_rows: list[dict[str, object]] = []
            markdown = build_markdown(
                base_case,
                max_roth_list,
                include_no_medicare=False,
                report_rows=report_rows,
                report_path=report_bequest_path,
                verbose=verbose,
                scenarios=scenarios,
                medicare_modes=medicare_modes,
                objective="maxBequest",
                net_spending_k=BEQUEST_NET_SPENDING,
                heirs_tax_rates=bequest_heirs_rates,
                solver_verbose=solver_verbose,
            )
            output_bequest_path.write_text(markdown, encoding="utf-8")
            write_report(report_bequest_path, report_rows)
            print(f"Wrote {output_bequest_path}")
            print(f"Wrote {report_bequest_path}")


if __name__ == "__main__":
    main()
