#!/usr/bin/env python3
"""Generate the synthetic Swift package used in the parallel xcodebuild experiments.

Usage: gen.py OUT_DIR FILES_PER_MODULE   (the article used 100: 501 Swift files, 47,709 lines)

Five library targets in a dependency graph plus one executable; Network and Storage are siblings:
  Core <- Model <- (Network, Storage) <- Features <- LabApp
Every file declares a protocol with an associated type, a generic struct that
conforms to it, conditional conformances, a Codable model and a few generic
helpers, so the type checker has real work to do.
"""
import os
import sys

out = sys.argv[1]
N = int(sys.argv[2])

MODULES = [
    ("Core", []),
    ("Model", ["Core"]),
    ("Network", ["Core", "Model"]),
    ("Storage", ["Core", "Model"]),
    ("Features", ["Core", "Model", "Network", "Storage"]),
]


def file_body(mod, deps, i):
    p = f"{mod}{i}"
    imports = "\n".join(f"import {d}" for d in deps)
    dep_use = ""
    if deps:
        d = deps[-1]
        j = i % N
        dep_use = f"""
public struct {p}Bridge {{
    public let inner: {d}{j}Box<Int>
    public let model: {d}{j}Model
    public init(inner: {d}{j}Box<Int>, model: {d}{j}Model) {{ self.inner = inner; self.model = model }}
    public func score() -> Int {{
        {d}{j}Ops.combine(inner, {d}{j}Box(model.count)) + {d}{j}Ops.pipeline([model.count, inner.value, {i}])
    }}
}}
"""
    return f"""// Generated file. Module {mod}, file {i}.
import Foundation
{imports}

public protocol {p}Shape: Sendable {{
    associatedtype Value: Hashable & Sendable
    var value: Value {{ get }}
    func merged<Other: {p}Shape>(with other: Other) -> [Value] where Other.Value == Value
}}

public struct {p}Box<Element: Hashable & Sendable>: {p}Shape {{
    public let value: Element
    public init(_ value: Element) {{ self.value = value }}
    public func merged<Other: {p}Shape>(with other: Other) -> [Element] where Other.Value == Element {{
        [value, other.value]
    }}
    public func map<U: Hashable & Sendable>(_ f: (Element) throws -> U) rethrows -> {p}Box<U> {{
        {p}Box<U>(try f(value))
    }}
}}

extension {p}Box: Equatable {{}}
extension {p}Box: Hashable {{}}
extension {p}Box: Comparable where Element: Comparable {{
    public static func < (lhs: Self, rhs: Self) -> Bool {{ lhs.value < rhs.value }}
}}
extension {p}Box: Codable where Element: Codable {{}}

public struct {p}Model: Codable, Hashable, Sendable {{
    public var id: UUID
    public var name: String
    public var count: Int
    public var ratio: Double
    public var tags: [String]
    public var attributes: [String: Int]
    public var createdAt: Date
    public var parent: Int?
    public var flags: Set<String>
    public var nested: [[Int]]
    public init(id: UUID = UUID(), name: String = "{p}", count: Int = {i}, ratio: Double = 0.5,
                tags: [String] = [], attributes: [String: Int] = [:], createdAt: Date = Date(timeIntervalSince1970: 0),
                parent: Int? = nil, flags: Set<String> = [], nested: [[Int]] = []) {{
        self.id = id; self.name = name; self.count = count; self.ratio = ratio; self.tags = tags
        self.attributes = attributes; self.createdAt = createdAt; self.parent = parent; self.flags = flags; self.nested = nested
    }}
}}

public enum {p}Event: Codable, Hashable, Sendable {{
    case created({p}Model)
    case renamed(from: String, to: String)
    case counted(Int, ratio: Double)
    case tagged([String])
    case removed
}}

public enum {p}Ops {{
    public static func combine<A: {p}Shape, B: {p}Shape>(_ a: A, _ b: B) -> Int where A.Value == B.Value {{
        a.merged(with: b).count
    }}
    public static func pipeline(_ xs: [Int]) -> Int {{
        let a = xs.map {{ $0 * 3 }}.filter {{ $0 % 2 == 0 }}.reduce(0, +)
        let b = xs.lazy.map {{ $0 + 1 }}.compactMap {{ $0 > 2 ? $0 : nil }}.reduce(0) {{ $0 &+ $1 }}
        let c = Dictionary(grouping: xs, by: {{ $0 % 7 }}).mapValues {{ $0.count }}.values.sorted().first ?? 0
        let d = zip(xs, xs.dropFirst()).map {{ ($0 - $1) * ($0 + $1) }}.max() ?? 0
        return a + b + c + d
    }}
    public static func summarize<S: Sequence>(_ s: S) -> [String: Int] where S.Element == {p}Model {{
        var out: [String: Int] = [:]
        for m in s {{ out[m.name, default: 0] += m.count + m.tags.count + m.attributes.values.reduce(0, +) }}
        return out
    }}
    public static func roundTrip(_ m: {p}Model) throws -> {p}Model {{
        let data = try JSONEncoder().encode(m)
        return try JSONDecoder().decode({p}Model.self, from: data)
    }}
    public static func reduceEvents(_ events: [{p}Event]) -> (Int, Double, [String]) {{
        events.reduce(into: (0, 0.0, [String]())) {{ acc, e in
            switch e {{
            case .created(let m): acc.0 += m.count; acc.1 += m.ratio
            case .renamed(_, let to): acc.2.append(to)
            case .counted(let n, let r): acc.0 += n; acc.1 += r
            case .tagged(let t): acc.2 += t
            case .removed: acc.0 -= 1
            }}
        }}
    }}
}}
{dep_use}"""


def write(path, text):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        f.write(text)


targets = []
for mod, deps in MODULES:
    for i in range(N):
        write(f"{out}/Sources/{mod}/{mod}{i}.swift", file_body(mod, deps, i))
    dep_list = ", ".join(f'"{d}"' for d in deps)
    targets.append(f'        .target(name: "{mod}", dependencies: [{dep_list}]),')

main_lines = ["import Foundation", "import Core", "import Model", "import Network", "import Storage", "import Features", "", "var total = 0"]
for i in range(N):
    main_lines.append(f"total &+= Features{i}Ops.pipeline([{i}, {i + 1}, {i + 2}])")
main_lines.append('print("LabApp total:", total)')
write(f"{out}/Sources/LabApp/main.swift", "\n".join(main_lines) + "\n")

package = f"""// swift-tools-version: 5.10
import PackageDescription

let package = Package(
    name: "XcbLab",
    platforms: [.macOS(.v14)],
    products: [
        .executable(name: "LabApp", targets: ["LabApp"]),
    ],
    targets: [
{chr(10).join(targets)}
        .executableTarget(name: "LabApp", dependencies: ["Core", "Model", "Network", "Storage", "Features"]),
    ]
)
"""
write(f"{out}/Package.swift", package)
print(f"generated {len(MODULES) * N + 1} Swift files in {out}")
