///|
/// Deterministic workload suite used for release smoke tests and published
/// performance measurements. The report intentionally excludes wall-clock
/// time so that the values remain reproducible across machines.
fn hash_double(hash : UInt64, value : Double) -> UInt64 {
  let mut next = hash
  let mut bits = value.reinterpret_as_uint64()
  let prime : UInt64 = 0x100000001b3UL
  let mut i = 0
  while i < 8 {
    next = (next ^ (bits & 0xffUL)) * prime
    bits = bits >> 8
    i = i + 1
  }
  next
}

///|
fn times_grid(steps : Int, maturity : Double) -> Array[Double] {
  let times = Array::make(steps + 1, 0.0)
  let dt = maturity / steps.to_double()
  let mut i = 0
  while i <= steps {
    times[i] = i.to_double() * dt
    i = i + 1
  }
  times
}

///|
fn gbm_checksum() -> UInt64 {
  let process = @stochastic.GBMProcess::new(100.0, 0.04, 0.2)
  let times = times_grid(64, 1.0)
  let paths = process.generate_matrix(
    2000,
    times,
    @core.RngEngine::splitmix(20260823UL),
  )
  let mut hash : UInt64 = 0xcbf29ce484222325UL
  let mut p = 0
  while p < paths.num_paths() {
    hash = hash_double(hash, paths.get(p, paths.num_steps() - 1))
    p = p + 1
  }
  hash
}

///|
fn sobol_checksum() -> UInt64 {
  let sequence = @qmc.SobolSequence::new(32)
  let mut hash : UInt64 = 0xcbf29ce484222325UL
  let mut i = 0
  while i < 4096 {
    let point = sequence.next_point()
    let mut d = 0
    while d < point.length() {
      hash = hash_double(hash, point[d])
      d = d + 1
    }
    i = i + 1
  }
  hash
}

///|
fn price_european() -> @pricing.OptionPriceResult {
  @pricing.price_european_option_mc(
    @pricing.Call,
    @stochastic.GBMProcess::new(100.0, 0.03, 0.2),
    100.0,
    0.03,
    1.0,
    2000,
    @core.RngEngine::splitmix(1001UL),
  )
}

///|
fn price_barrier() -> @pricing.OptionPriceResult {
  @pricing.price_barrier_option_mc(
    @pricing.Call,
    @pricing.DownAndOut,
    @stochastic.GBMProcess::new(100.0, 0.03, 0.2),
    100.0,
    80.0,
    0.03,
    times_grid(16, 1.0),
    2000,
    @core.RngEngine::splitmix(1002UL),
  )
}

///|
fn portfolio_checksum() -> UInt64 {
  let first = @portfolio.Position::new(
    "asset-0",
    1.0,
    100.0,
    100.5,
    @portfolio.Equity,
  ).unwrap()
  let positions = Array::make(64, first)
  let mut i = 0
  while i < positions.length() {
    positions[i] = @portfolio.Position::new(
      "asset-" + i.to_string(),
      (i + 1).to_double(),
      100.0 + (i % 7).to_double(),
      100.5 + (i % 11).to_double(),
      @portfolio.Equity,
    ).unwrap()
    i = i + 1
  }
  let portfolio = @portfolio.Portfolio::new(positions, 10000.0).unwrap()
  let mut hash : UInt64 = 0xcbf29ce484222325UL
  let mut scenario = 0
  while scenario < 256 {
    let shocks = Array::make(64, @portfolio.ScenarioShock::{
      id: "asset-0",
      additive: 0.0,
      proportional: 0.0,
    })
    let mut p = 0
    while p < shocks.length() {
      shocks[p] = @portfolio.ScenarioShock::{
        id: "asset-" + p.to_string(),
        additive: (scenario % 5).to_double() - 2.0,
        proportional: ((p + scenario) % 9).to_double() / 100.0 - 0.04,
      }
      p = p + 1
    }
    let report = portfolio.report(shocks).unwrap()
    hash = hash_double(hash, report.pnl)
    hash = hash_double(hash, report.concentration)
    scenario = scenario + 1
  }
  hash
}

///|
fn implied_volatility_result() -> @calibration.ImpliedVolResult {
  let target = @pricing.black_scholes_price(
    @pricing.Call,
    100.0,
    105.0,
    0.03,
    0.2,
    1.0,
  )
  @calibration.implied_volatility(
    @pricing.Call,
    100.0,
    105.0,
    0.03,
    1.0,
    target,
    1.0e-10,
    100,
  ).unwrap()
}

///|
/// Build the deterministic benchmark report consumed by CI and documentation.
pub fn benchmark_report() -> String {
  let european = price_european()
  let barrier = price_barrier()
  let implied = implied_volatility_result()
  let sb = StringBuilder::new()
  sb.write_string(
    "benchmark=gbm_terminal_paths paths=2000 steps=64 checksum=" +
    gbm_checksum().to_string() +
    "\n",
  )
  sb.write_string(
    "benchmark=sobol_32d points=4096 checksum=" +
    sobol_checksum().to_string() +
    "\n",
  )
  sb.write_string(
    "benchmark=european_call paths=2000 price=" +
    european.price.to_string() +
    " standard_error=" +
    european.standard_error.to_string() +
    "\n",
  )
  sb.write_string(
    "benchmark=barrier_call paths=2000 price=" +
    barrier.price.to_string() +
    " standard_error=" +
    barrier.standard_error.to_string() +
    "\n",
  )
  sb.write_string(
    "benchmark=portfolio_scenario positions=64 scenarios=256 checksum=" +
    portfolio_checksum().to_string() +
    "\n",
  )
  sb.write_string(
    "benchmark=implied_volatility iterations=" +
    implied.iterations.to_string() +
    " residual=" +
    implied.residual.to_string() +
    " volatility=" +
    implied.volatility.to_string() +
    "\n",
  )
  sb.to_string()
}

///|
/// Release benchmark executable entry point.
fn main {
  println(benchmark_report())
}