///|
pub struct CostEstimate {
  name : String
  annual_cost : Double
  annual_energy_kwh : Double
  annual_emissions_kg : Double
  utility_flow_kg_per_s : Double
} derive(Debug)

///|
pub fn utility_operating_cost(
  utility : UtilityDesign,
  operating_hours_per_year : Double,
  unit_cost_per_kwh : Double,
) -> Double {
  utility.required_mass_flow_kg_per_s.abs() *
  utility.duty_w.abs() /
  utility.required_mass_flow_kg_per_s.max(1.0e-12) /
  1000.0 *
  operating_hours_per_year.max(0.0) *
  unit_cost_per_kwh.max(0.0)
}

///|
pub fn utility_energy_kwh_per_year(
  utility : UtilityDesign,
  operating_hours_per_year : Double,
) -> Double {
  utility.duty_w.abs() / 1000.0 * operating_hours_per_year.max(0.0)
}

///|
pub fn utility_emissions_kg_per_h(
  utility : UtilityDesign,
  emission_factor_kg_per_kwh : Double,
) -> Double {
  utility.duty_w.abs() / 1000.0 * emission_factor_kg_per_kwh.max(0.0)
}

///|
pub fn annualized_capital_cost(
  capital_cost : Double,
  interest_rate : Double,
  lifetime_years : Int,
  operating_hours_per_year : Double,
) -> Double {
  if lifetime_years <= 0 || operating_hours_per_year <= 0.0 {
    0.0
  } else {
    let rate = interest_rate.max(0.0)
    let years = lifetime_years.to_double()
    let recovery = if rate == 0.0 {
      1.0 / years
    } else {
      rate * @math.pow(1.0 + rate, years) / (@math.pow(1.0 + rate, years) - 1.0)
    }
    capital_cost.abs() * recovery
  }
}

///|
pub fn total_annual_cost(
  annualized_capital : Double,
  utility_cost_per_hour : Double,
  operating_hours_per_year : Double,
) -> Double {
  annualized_capital.max(0.0) +
  utility_cost_per_hour.max(0.0) * operating_hours_per_year.max(0.0)
}

///|
pub fn maintenance_cost_per_year(
  capital_cost : Double,
  maintenance_fraction : Double,
) -> Double {
  capital_cost.abs() * maintenance_fraction.max(0.0)
}

///|
pub fn contingency_cost(base_cost : Double, fraction : Double) -> Double {
  base_cost.abs() * fraction.max(0.0)
}

///|
pub fn inflation_adjusted_cost(
  base_cost : Double,
  inflation_rate : Double,
  years : Int,
) -> Double {
  base_cost.abs() *
  @math.pow(1.0 + inflation_rate.max(-0.99), years.max(0).to_double())
}

///|
pub fn discount_factor(discount_rate : Double, year : Int) -> Double {
  1.0 / @math.pow(1.0 + discount_rate.max(-0.99), year.max(0).to_double())
}

///|
pub fn net_present_value(
  cash_flows : Array[Double],
  discount_rate : Double,
) -> Double {
  let mut total = 0.0
  for i, cash_flow in cash_flows {
    total += cash_flow * discount_factor(discount_rate, i)
  }
  total
}

///|
pub fn payback_years(
  initial_investment : Double,
  annual_saving : Double,
) -> Double {
  if annual_saving <= 0.0 {
    1.0e30
  } else {
    initial_investment.abs() / annual_saving
  }
}

///|
pub fn levelized_cost(
  capital_cost : Double,
  annual_operating_cost : Double,
  annual_output_kwh : Double,
  lifetime_years : Int,
) -> Double {
  if annual_output_kwh <= 0.0 || lifetime_years <= 0 {
    0.0
  } else {
    (
      capital_cost.abs() / lifetime_years.to_double() +
      annual_operating_cost.abs()
    ) /
    annual_output_kwh
  }
}

///|
pub fn service_cost_estimate(
  utility : UtilityDesign,
  operating_hours_per_year : Double,
  unit_cost_per_kwh : Double,
  emission_factor_kg_per_kwh : Double,
) -> CostEstimate {
  let annual_energy = utility_energy_kwh_per_year(
    utility, operating_hours_per_year,
  )
  {
    name: utility.name,
    annual_cost: annual_energy * unit_cost_per_kwh.max(0.0),
    annual_energy_kwh: annual_energy,
    annual_emissions_kg: annual_energy * emission_factor_kg_per_kwh.max(0.0),
    utility_flow_kg_per_s: utility.required_mass_flow_kg_per_s,
  }
}

///|
pub fn least_cost_heating_service(
  duty_w : Double,
  candidates : Array[UtilityDesign],
  operating_hours_per_year : Double,
  unit_cost_per_kwh : Double,
) -> CostEstimate {
  let selected : CostEstimate = {
    name: "none",
    annual_cost: 1.0e30,
    annual_energy_kwh: 0.0,
    annual_emissions_kg: 0.0,
    utility_flow_kg_per_s: 0.0,
  }
  let mut best = selected
  for candidate in candidates {
    if candidate.duty_w >= duty_w {
      let estimate = service_cost_estimate(
        candidate, operating_hours_per_year, unit_cost_per_kwh, 0.2,
      )
      if estimate.annual_cost < best.annual_cost {
        best = estimate
      }
    }
  }
  best
}

///|
pub fn cost_saving_percent(
  reference_cost : Double,
  improved_cost : Double,
) -> Double {
  if reference_cost.abs() < 1.0e-12 {
    0.0
  } else {
    (reference_cost - improved_cost) / reference_cost.abs() * 100.0
  }
}

///|
pub fn emissions_saving_kg(
  reference : CostEstimate,
  improved : CostEstimate,
) -> Double {
  reference.annual_emissions_kg - improved.annual_emissions_kg
}

///|
pub fn cost_summary(estimate : CostEstimate) -> String {
  "\{estimate.name}: annual cost=\{estimate.annual_cost}, energy=\{estimate.annual_energy_kwh} kWh, emissions=\{estimate.annual_emissions_kg} kg"
}