#!/usr/bin/env python
import argparse
import subprocess
import sys
from pathlib import Path

import numpy as np
from scipy.optimize import minimize


def run_cengine(a, b, exe="cengine.exe", workdir="."):
    workdir = Path(workdir)
    input_path = workdir / "input.in"
    output_path = workdir / "input.out"

    input_path.write_text(f"{a:.17g}\n{b:.17g}\n", encoding="utf-8")

    try:
        result = subprocess.run(
            [str(exe)],
            cwd=str(workdir),
            capture_output=True,
            text=True,
            check=False,
        )
    except Exception as e:
        print(f"Error: failed to run {exe}: {e}", file=sys.stderr)
        raise

    if result.returncode != 0:
        print("Error: cengine.exe failed", file=sys.stderr)
        print(f"return code = {result.returncode}", file=sys.stderr)
        if result.stderr:
            print(result.stderr, file=sys.stderr)
        raise RuntimeError("cengine.exe returned non-zero status")

    try:
        y = float(output_path.read_text(encoding="utf-8").strip())
    except Exception as e:
        print(f"Error: failed to read input.out: {e}", file=sys.stderr)
        raise

    return y


def objective(x, exe, workdir):
    a, b = x
    y = run_cengine(a, b, exe=exe, workdir=workdir)

    print(f"a={a:.8g}, b={b:.8g}, y={y:.12g}")

    return y


def main():
    parser = argparse.ArgumentParser(
        description="Optimize y = a**2 + b**4 using external C engine."
    )
    parser.add_argument("a", type=float, help="initial value of a")
    parser.add_argument("b", type=float, help="initial value of b")
    parser.add_argument("--exe", type=str, default="cengine.exe")
    parser.add_argument("--workdir", type=str, default=".")
    parser.add_argument("--method", type=str, default="Nelder-Mead")
    parser.add_argument("--maxiter", type=int, default=500)

    # ←追加
    parser.add_argument("--tol", type=float, default=None, help="global tolerance")
    parser.add_argument("--xatol", type=float, default=None, help="x tolerance (Nelder-Mead)")
    parser.add_argument("--fatol", type=float, default=None, help="f tolerance (Nelder-Mead)")

    args = parser.parse_args()

    x0 = np.array([args.a, args.b], dtype=float)

    # options を動的に構築
    options = {
        "maxiter": args.maxiter,
        "disp": True,
    }
    if args.xatol is not None:
        options["xatol"] = args.xatol
    if args.fatol is not None:
        options["fatol"] = args.fatol

    result = minimize(
        objective,
        x0,
        args=(args.exe, args.workdir),
        method=args.method,
        tol=args.tol,  # ←ここが追加ポイント
        options=options,
    )

    print("\n=== Optimization result ===")
    print(f"success = {result.success}")
    print(f"message = {result.message}")
    print(f"a = {result.x[0]:.17g}")
    print(f"b = {result.x[1]:.17g}")
    print(f"y = {result.fun:.17g}")

    return 0 if result.success else 1


if __name__ == "__main__":
    sys.exit(main())