// CorePilot is a small, honest "performance console" prototype.
//
// The full CorePilot product concept (process priority rules, CPU affinity
// pinning, temperature and fan-curve monitoring, power profiles) needs
// OS-privileged and vendor-specific hardware APIs that a dependency-free,
// cross-platform Go CLI cannot reach. This prototype instead does the part
// that IS honestly portable with the stdlib alone: a live single-thread vs.
// multi-thread CPU benchmark that demonstrates real parallel scaling on the
// machine it runs on, plus a snapshot of what the Go runtime itself can see
// about memory usage. See README.txt and ../plan.md for the full scope and
// roadmap.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"
)

// reorderFlags moves all flag tokens (and their values, for flags listed in
// valueFlags) before any positional arguments, working around the stdlib
// flag package's behavior of stopping parsing at the first positional arg.
// CorePilot's "bench" subcommand takes no positional arguments today, but
// this helper is kept for consistency with the other CorePilot-family tools
// and in case flags appear in an unexpected order.
func reorderFlags(args []string, valueFlags map[string]bool) []string {
	var flags, positional []string
	for i := 0; i < len(args); i++ {
		a := args[i]
		name := strings.TrimLeft(a, "-")
		if strings.HasPrefix(a, "-") && valueFlags[name] {
			flags = append(flags, a)
			if i+1 < len(args) {
				i++
				flags = append(flags, args[i])
			}
			continue
		}
		if strings.HasPrefix(a, "-") {
			flags = append(flags, a)
			continue
		}
		positional = append(positional, a)
	}
	return append(flags, positional...)
}

func usage() {
	fmt.Fprint(os.Stderr, `CorePilot - performance console prototype (CPU benchmark + runtime memory stats)

Usage:
  corepilot bench [--duration 500ms] [--json]

Commands:
  bench    Run a live single-thread vs. multi-thread CPU benchmark and
           report Go runtime memory stats.

Flags for "bench":
  --duration duration   How long to run each phase (single-thread, then
                         multi-thread) for. Accepts Go duration syntax,
                         e.g. 500ms, 2s, 1m. (default 500ms)
  --json                Emit structured JSON instead of a text report.

  -h, --help             Show this help.

Notes:
  - Total wall-clock time is roughly 2x --duration, since the single-thread
    and multi-thread phases run one after another, not concurrently.
  - Memory stats reported are for the CorePilot process's own Go runtime,
    NOT total system RAM or other processes' usage.
  - Temperatures, fan curves, CPU affinity control, and per-app performance
    profiles are on the roadmap; see ../plan.md. They need OS-privileged,
    vendor-specific hardware APIs not available to a portable, dependency-
    free Go CLI.
`)
}

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		// Double-clicked in Explorer rather than run from a prompt: ask the
		// one question the program needs and stay on screen. Printing usage
		// and exiting here is what made the window vanish instantly.
		if interactiveConsole() {
			runGuided()
			return
		}
		usage()
		os.Exit(1)
	}

	switch args[0] {
	case "-h", "--help", "help":
		usage()
		os.Exit(0)
	case "bench":
		runBench(args[1:])
	default:
		fmt.Fprintf(os.Stderr, "corepilot: unknown command %q\n\n", args[0])
		usage()
		os.Exit(1)
	}
}

// isPrime reports whether n is prime using simple trial division. This is
// the CPU-bound workload used by the benchmark: cheap enough per-check to
// run millions of times per second, expensive enough (once n grows) to
// exercise real integer arithmetic rather than being optimized away.
func isPrime(n uint64) bool {
	if n < 2 {
		return false
	}
	if n%2 == 0 {
		return n == 2
	}
	if n%3 == 0 {
		return n == 3
	}
	for i := uint64(5); i*i <= n; i += 6 {
		if n%i == 0 || n%(i+2) == 0 {
			return false
		}
	}
	return true
}

// runWorkload runs trial-division primality checks starting at "start" and
// advancing by "stride" each step, for the given duration, on the calling
// goroutine. It returns the total number of primality checks performed (the
// "ops" count).
//
// A goroutine that calls this with start=base+idx and stride=numWorkers
// checks a distinct interleaved slice of the same number range as every
// other worker (0,1,2,3,... split round-robin) - no shared counters, no
// locks, no contention, and critically no worker racing ahead into much
// larger (and therefore much more expensive to trial-divide) numbers than
// the others. That keeps the per-check cost comparable to the single-thread
// phase (stride=1, one worker) so the two phases' ops/sec numbers are an
// apples-to-apples comparison.
func runWorkload(start, stride uint64, duration time.Duration) uint64 {
	n := start
	var ops uint64
	deadline := time.Now().Add(duration)
	const checkMask = uint64(2047) // check the clock every 2048 iterations
	for {
		isPrime(n)
		n += stride
		ops++
		if ops&checkMask == 0 && time.Now().After(deadline) {
			break
		}
	}
	return ops
}

// benchResult is the JSON/text-report payload for "corepilot bench".
type benchResult struct {
	NumCPU     int   `json:"num_cpu"`
	GOMAXPROCS int   `json:"gomaxprocs"`
	DurationMS int64 `json:"duration_ms"`

	SingleThreadOps       uint64  `json:"single_thread_ops"`
	SingleThreadOpsPerSec float64 `json:"single_thread_ops_per_sec"`

	MultiThreadWorkers   int     `json:"multi_thread_workers"`
	MultiThreadOps       uint64  `json:"multi_thread_ops"`
	MultiThreadOpsPerSec float64 `json:"multi_thread_ops_per_sec"`

	ScalingFactor float64 `json:"scaling_factor"`

	MemSysBytes       uint64 `json:"mem_sys_bytes"`
	MemHeapAllocBytes uint64 `json:"mem_heap_alloc_bytes"`
	NumGC             uint32 `json:"num_gc"`
	MemNote           string `json:"mem_note"`
}

func runBench(args []string) {
	for _, a := range args {
		if a == "-h" || a == "--help" || a == "help" {
			usage()
			os.Exit(0)
		}
	}

	valueFlags := map[string]bool{"duration": true}
	args = reorderFlags(args, valueFlags)

	fs := flag.NewFlagSet("bench", flag.ExitOnError)
	fs.Usage = usage
	duration := fs.Duration("duration", 500*time.Millisecond, "duration to run each benchmark phase for")
	jsonOut := fs.Bool("json", false, "emit structured JSON output")
	if err := fs.Parse(args); err != nil {
		os.Exit(1)
	}

	if *duration <= 0 {
		fmt.Fprintln(os.Stderr, "corepilot: --duration must be positive")
		os.Exit(1)
	}

	numCPU := runtime.NumCPU()
	gomaxprocs := runtime.GOMAXPROCS(0)

	if !*jsonOut {
		fmt.Printf("CorePilot bench - logical cores: %d, GOMAXPROCS: %d, phase duration: %s\n\n", numCPU, gomaxprocs, duration.String())
		fmt.Println("Running single-thread phase...")
	}

	// Both phases check the same starting number range, restricted to odd
	// numbers only, so the per-check cost (trial division up to sqrt(n))
	// is comparable between them - only the parallelism differs. (Odd-only
	// striding is a standard, deliberate optimization: it also keeps every
	// worker's sequence away from the degenerate "always even" case, where
	// isPrime would trivially reject on the first modulo check and make
	// that worker's ops artificially - and unfairly - cheap.)
	const workloadBase = uint64(1_000_003) // odd

	// Single-thread phase: one goroutine, stride 2, checks every odd
	// number in the range sequentially.
	singleOps := runWorkload(workloadBase, 2, *duration)
	singleOpsPerSec := float64(singleOps) / duration.Seconds()

	if !*jsonOut {
		fmt.Printf("Running multi-thread phase (%d workers)...\n", numCPU)
	}

	// Multi-thread phase: numCPU goroutines round-robin the SAME odd
	// number range single-thread would check (worker i checks
	// workloadBase+2i, workloadBase+2i+2*numCPU, ...), so each worker's
	// own state is entirely independent - no shared counters, no locks,
	// no contention - while covering equivalent-cost numbers to the
	// single-thread phase. Pure embarrassingly-parallel CPU-bound work.
	results := make([]uint64, numCPU)
	var wg sync.WaitGroup
	for i := 0; i < numCPU; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			start := workloadBase + uint64(idx)*2
			results[idx] = runWorkload(start, uint64(numCPU)*2, *duration)
		}(i)
	}
	wg.Wait()

	var multiOps uint64
	for _, r := range results {
		multiOps += r
	}
	multiOpsPerSec := float64(multiOps) / duration.Seconds()

	scaling := 0.0
	if singleOpsPerSec > 0 {
		scaling = multiOpsPerSec / singleOpsPerSec
	}

	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	res := benchResult{
		NumCPU:     numCPU,
		GOMAXPROCS: gomaxprocs,
		DurationMS: duration.Milliseconds(),

		SingleThreadOps:       singleOps,
		SingleThreadOpsPerSec: singleOpsPerSec,

		MultiThreadWorkers:   numCPU,
		MultiThreadOps:       multiOps,
		MultiThreadOpsPerSec: multiOpsPerSec,

		ScalingFactor: scaling,

		MemSysBytes:       m.Sys,
		MemHeapAllocBytes: m.HeapAlloc,
		NumGC:             m.NumGC,
		MemNote:           "Memory stats are for the CorePilot process's own Go runtime only - not total system RAM, and not other processes' usage.",
	}

	if *jsonOut {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		if err := enc.Encode(res); err != nil {
			fmt.Fprintf(os.Stderr, "corepilot: failed to encode JSON: %v\n", err)
			os.Exit(1)
		}
		return
	}

	fmt.Println()
	fmt.Println("=== CorePilot bench report ===")
	fmt.Printf("Logical cores (NumCPU):     %d\n", res.NumCPU)
	fmt.Printf("GOMAXPROCS:                 %d\n", res.GOMAXPROCS)
	fmt.Printf("Phase duration:             %s\n", duration.String())
	fmt.Println()
	fmt.Printf("Single-thread ops:          %d\n", res.SingleThreadOps)
	fmt.Printf("Single-thread ops/sec:      %.0f\n", res.SingleThreadOpsPerSec)
	fmt.Println()
	fmt.Printf("Multi-thread workers:       %d\n", res.MultiThreadWorkers)
	fmt.Printf("Multi-thread total ops:     %d\n", res.MultiThreadOps)
	fmt.Printf("Multi-thread ops/sec:       %.0f\n", res.MultiThreadOpsPerSec)
	fmt.Println()
	fmt.Printf("Scaling factor (multi/single ops-per-sec): %.2fx\n", res.ScalingFactor)
	fmt.Println()
	fmt.Printf("Go runtime memory - Sys:       %d bytes (%.2f MB)\n", res.MemSysBytes, float64(res.MemSysBytes)/1024/1024)
	fmt.Printf("Go runtime memory - HeapAlloc: %d bytes (%.2f MB)\n", res.MemHeapAllocBytes, float64(res.MemHeapAllocBytes)/1024/1024)
	fmt.Printf("Go runtime GC cycles so far:   %d\n", res.NumGC)
	fmt.Println()
	fmt.Println("NOTE: the memory numbers above are this CorePilot process's own Go")
	fmt.Println("runtime usage, NOT total system RAM and NOT other processes' usage.")
	fmt.Println()
	fmt.Println("Roadmap (not in this prototype): CPU/case temperatures, fan curves,")
	fmt.Println("CPU affinity pinning, and per-app performance profiles all need")
	fmt.Println("OS-privileged, vendor-specific hardware APIs. See ../plan.md.")
}
