!> `bem_app_config_parser` の入力検証・物理量導出手続きを実装する submodule。
submodule(bem_app_config_parser) bem_app_config_parser_validate
  implicit none
contains

  !> `batch_duration` 系パラメータの排他条件と値域を検証し、確定値を反映する。
  module procedure resolve_batch_duration
  real(dp) :: batch_duration

  if (cfg%sim%has_batch_duration .and. cfg%sim%has_batch_duration_step) then
    error stop 'sim.batch_duration and sim.batch_duration_step cannot be used together.'
  end if

  if (cfg%sim%has_batch_duration_step) then
    if (.not. ieee_is_finite(cfg%sim%batch_duration_step) .or. cfg%sim%batch_duration_step <= 0.0d0) then
      error stop 'sim.batch_duration_step must be > 0.'
    end if
    if (.not. ieee_is_finite(cfg%sim%dt) .or. cfg%sim%dt <= 0.0d0) then
      error stop 'sim.dt must be > 0 when sim.batch_duration_step is set.'
    end if
    batch_duration = cfg%sim%dt*cfg%sim%batch_duration_step
    if (.not. ieee_is_finite(batch_duration) .or. batch_duration <= 0.0d0) then
      error stop 'sim.batch_duration_step produced invalid sim.batch_duration.'
    end if
    cfg%sim%batch_duration = batch_duration
    cfg%sim%has_batch_duration = .true.
  else if (cfg%sim%has_batch_duration) then
    if (.not. ieee_is_finite(cfg%sim%batch_duration)) then
      error stop 'sim.batch_duration must be finite.'
    end if
  end if
  end procedure resolve_batch_duration

  !> `reservoir_face` 粒子種の必須項目と幾何条件を検証する。
  module procedure validate_reservoir_species
  integer :: axis, axis_t1, axis_t2
  real(dp) :: boundary_value, area
  real(dp) :: number_density_m3, temperature_k, gamma_in, w_particle
  real(dp) :: inward_normal(3)
  type(particle_species_spec) :: spec

  spec = cfg%particle_species(species_idx)

  if (spec%has_npcls_per_step) then
    error stop 'particles.species.npcls_per_step is auto-computed for reservoir_face.'
  end if
  if (abs(spec%emit_current_density_a_m2) > 0.0d0 .or. spec%rays_per_batch /= 0_i32 .or. &
      spec%has_ray_direction .or. spec%has_deposit_opposite_charge_on_emit) then
    error stop 'photo_raycast keys are not allowed for reservoir_face.'
  end if
  if (spec%has_w_particle .and. spec%has_target_macro_particles_per_batch) then
    error stop 'reservoir_face does not allow both w_particle and target_macro_particles_per_batch.'
  end if
  if (.not. spec%has_w_particle .and. .not. spec%has_target_macro_particles_per_batch) then
    error stop 'reservoir_face requires either w_particle or target_macro_particles_per_batch.'
  end if
  if (spec%has_w_particle) then
    if (spec%w_particle <= 0.0d0) error stop 'particles.species.w_particle must be > 0 for reservoir_face.'
  end if
  if (spec%has_target_macro_particles_per_batch) then
    if (spec%target_macro_particles_per_batch == 0_i32 .or. spec%target_macro_particles_per_batch < -1_i32) then
      error stop 'particles.species.target_macro_particles_per_batch must be > 0 or -1.'
    end if
    if (spec%target_macro_particles_per_batch == -1_i32) then
      if (species_idx == 1) then
        error stop 'particles.species[1].target_macro_particles_per_batch cannot be -1.'
      end if
      if (.not. cfg%particle_species(1)%enabled) then
        error stop 'target_macro_particles_per_batch=-1 requires particles.species[1] to be enabled.'
      end if
      if (trim(lower(cfg%particle_species(1)%source_mode)) /= 'reservoir_face') then
        error stop 'target_macro_particles_per_batch=-1 requires particles.species[1].source_mode="reservoir_face".'
      end if
      if (.not. cfg%particle_species(1)%has_w_particle .or. cfg%particle_species(1)%w_particle <= 0.0d0) then
        error stop 'target_macro_particles_per_batch=-1 requires species[1] to resolve a positive w_particle.'
      end if
    end if
  end if
  if (.not. cfg%sim%use_box) then
    error stop 'particles.species.source_mode="reservoir_face" requires sim.use_box = true.'
  end if
  if (cfg%sim%batch_duration <= 0.0d0) then
    error stop 'sim.batch_duration must be > 0 for reservoir_face.'
  end if
  if (spec%has_number_density_cm3 .and. spec%has_number_density_m3) then
    error stop 'Specify either number_density_cm3 or number_density_m3, not both.'
  end if
  if (.not. spec%has_number_density_cm3 .and. .not. spec%has_number_density_m3) then
    error stop 'reservoir_face requires number_density_cm3 or number_density_m3.'
  end if
  if (spec%has_number_density_cm3) then
    if (spec%number_density_cm3 <= 0.0d0) error stop 'number_density_cm3 must be > 0.'
  else
    if (spec%number_density_m3 <= 0.0d0) error stop 'number_density_m3 must be > 0.'
  end if
  if (spec%has_temperature_ev .and. spec%has_temperature_k) then
    error stop 'Specify either temperature_ev or temperature_k, not both.'
  end if
  if (spec%has_temperature_ev) then
    if (spec%temperature_ev < 0.0d0) error stop 'temperature_ev must be >= 0.'
  else if (spec%has_temperature_k) then
    if (spec%temperature_k < 0.0d0) error stop 'temperature_k must be >= 0.'
  else
    if (spec%temperature_k < 0.0d0) error stop 'temperature_k must be >= 0.'
  end if
  if (spec%m_particle <= 0.0d0) then
    error stop 'm_particle must be > 0.'
  end if

  call resolve_inject_face(cfg%sim%box_min, cfg%sim%box_max, spec%inject_face, axis, boundary_value)
  axis_t1 = modulo(axis, 3) + 1
  axis_t2 = modulo(axis + 1, 3) + 1
  if (abs(spec%pos_low(axis) - boundary_value) > 1.0d-12 .or. abs(spec%pos_high(axis) - boundary_value) > 1.0d-12) then
    error stop 'reservoir_face pos_low/pos_high must lie on the selected box face.'
  end if
  if (spec%pos_high(axis_t1) < spec%pos_low(axis_t1) .or. spec%pos_high(axis_t2) < spec%pos_low(axis_t2)) then
    error stop 'reservoir_face tangential bounds must satisfy pos_high >= pos_low.'
  end if
  if ((spec%pos_high(axis_t1) - spec%pos_low(axis_t1)) <= 0.0d0 .or. &
      (spec%pos_high(axis_t2) - spec%pos_low(axis_t2)) <= 0.0d0) then
    error stop 'reservoir_face opening area must be positive.'
  end if
  area = compute_face_area_from_bounds(spec%inject_face, spec%pos_low, spec%pos_high)
  if (area <= 0.0d0) then
    error stop 'reservoir_face opening area must be positive.'
  end if

  if (spec%has_target_macro_particles_per_batch) then
    if (spec%target_macro_particles_per_batch == -1_i32) then
      w_particle = cfg%particle_species(1)%w_particle
    else
      number_density_m3 = species_number_density_m3(spec)
      temperature_k = species_temperature_k(spec)
      call resolve_inward_normal(spec%inject_face, inward_normal)
      gamma_in = compute_inflow_flux_from_drifting_maxwellian( &
                 number_density_m3, temperature_k, spec%m_particle, spec%drift_velocity, inward_normal &
                 )
      w_particle = gamma_in*area*cfg%sim%batch_duration/real(spec%target_macro_particles_per_batch, dp)
    end if
    if (.not. ieee_is_finite(w_particle) .or. w_particle <= 0.0d0) then
      error stop 'target_macro_particles_per_batch produced invalid w_particle.'
    end if
    spec%w_particle = w_particle
    spec%has_w_particle = .true.
  end if

  cfg%particle_species(species_idx) = spec
  end procedure validate_reservoir_species

  !> `photo_raycast` 粒子種の必須項目と幾何/方向条件を検証する。
  module procedure validate_photo_raycast_species
  integer :: axis, axis_t1, axis_t2
  real(dp) :: boundary_value, area, direction_norm, inward_dot
  real(dp) :: inward_normal(3)
  type(particle_species_spec) :: spec

  spec = cfg%particle_species(species_idx)

  if (spec%has_npcls_per_step) then
    error stop 'particles.species.npcls_per_step is not allowed for photo_raycast.'
  end if
  if (spec%has_number_density_cm3 .or. spec%has_number_density_m3) then
    error stop 'number_density_cm3/number_density_m3 are not allowed for photo_raycast.'
  end if
  if (spec%has_w_particle) then
    error stop 'w_particle is not allowed for photo_raycast.'
  end if
  if (spec%has_target_macro_particles_per_batch) then
    error stop 'target_macro_particles_per_batch is not allowed for photo_raycast.'
  end if
  if (.not. cfg%sim%use_box) then
    error stop 'particles.species.source_mode="photo_raycast" requires sim.use_box = true.'
  end if
  if (cfg%sim%batch_duration <= 0.0d0) then
    error stop 'sim.batch_duration must be > 0 for photo_raycast.'
  end if
  if (.not. ieee_is_finite(spec%emit_current_density_a_m2) .or. spec%emit_current_density_a_m2 <= 0.0d0) then
    error stop 'photo_raycast requires emit_current_density_a_m2 > 0.'
  end if
  if (spec%rays_per_batch <= 0_i32) then
    error stop 'photo_raycast requires rays_per_batch > 0.'
  end if
  if (.not. ieee_is_finite(spec%normal_drift_speed)) then
    error stop 'normal_drift_speed must be finite.'
  end if
  if (spec%has_temperature_ev .and. spec%has_temperature_k) then
    error stop 'Specify either temperature_ev or temperature_k, not both.'
  end if
  if (spec%has_temperature_ev) then
    if (.not. ieee_is_finite(spec%temperature_ev) .or. spec%temperature_ev < 0.0d0) then
      error stop 'temperature_ev must be finite and >= 0.'
    end if
  else
    if (.not. ieee_is_finite(spec%temperature_k) .or. spec%temperature_k < 0.0d0) then
      error stop 'temperature_k must be finite and >= 0.'
    end if
  end if
  if (.not. ieee_is_finite(spec%m_particle) .or. spec%m_particle <= 0.0d0) then
    error stop 'm_particle must be finite and > 0.'
  end if
  if (.not. ieee_is_finite(spec%q_particle) .or. abs(spec%q_particle) <= 0.0d0) then
    error stop 'q_particle must be finite and non-zero for photo_raycast.'
  end if

  call resolve_inject_face(cfg%sim%box_min, cfg%sim%box_max, spec%inject_face, axis, boundary_value)
  axis_t1 = modulo(axis, 3) + 1
  axis_t2 = modulo(axis + 1, 3) + 1
  if (abs(spec%pos_low(axis) - boundary_value) > 1.0d-12 .or. abs(spec%pos_high(axis) - boundary_value) > 1.0d-12) then
    error stop 'photo_raycast pos_low/pos_high must lie on the selected box face.'
  end if
  if (spec%pos_high(axis_t1) < spec%pos_low(axis_t1) .or. spec%pos_high(axis_t2) < spec%pos_low(axis_t2)) then
    error stop 'photo_raycast tangential bounds must satisfy pos_high >= pos_low.'
  end if
  if ((spec%pos_high(axis_t1) - spec%pos_low(axis_t1)) <= 0.0d0 .or. &
      (spec%pos_high(axis_t2) - spec%pos_low(axis_t2)) <= 0.0d0) then
    error stop 'photo_raycast opening area must be positive.'
  end if
  area = compute_face_area_from_bounds(spec%inject_face, spec%pos_low, spec%pos_high)
  if (.not. ieee_is_finite(area) .or. area <= 0.0d0) then
    error stop 'photo_raycast opening area must be positive.'
  end if

  call resolve_inward_normal(spec%inject_face, inward_normal)
  if (spec%has_ray_direction) then
    if (.not. all(ieee_is_finite(spec%ray_direction))) then
      error stop 'ray_direction must be finite.'
    end if
    direction_norm = sqrt(sum(spec%ray_direction*spec%ray_direction))
    if (direction_norm <= 0.0d0) then
      error stop 'ray_direction norm must be > 0.'
    end if
    spec%ray_direction = spec%ray_direction/direction_norm
  else
    spec%ray_direction = inward_normal
  end if
  inward_dot = dot_product(spec%ray_direction, inward_normal)
  if (.not. ieee_is_finite(inward_dot) .or. inward_dot <= 0.0d0) then
    error stop 'ray_direction must point inward from inject_face.'
  end if

  cfg%particle_species(species_idx) = spec
  end procedure validate_photo_raycast_species

  !> 注入面識別子から法線軸と対応境界座標を返す。
  module procedure resolve_inject_face
  select case (trim(lower(inject_face)))
  case ('x_low')
    axis = 1
    boundary_value = box_min(1)
  case ('x_high')
    axis = 1
    boundary_value = box_max(1)
  case ('y_low')
    axis = 2
    boundary_value = box_min(2)
  case ('y_high')
    axis = 2
    boundary_value = box_max(2)
  case ('z_low')
    axis = 3
    boundary_value = box_min(3)
  case ('z_high')
    axis = 3
    boundary_value = box_max(3)
  case default
    error stop 'Unknown particles.species.inject_face.'
  end select
  end procedure resolve_inject_face

  !> 注入面識別子から内向き法線ベクトルを返す。
  module procedure resolve_inward_normal
  inward_normal = 0.0d0
  select case (trim(lower(inject_face)))
  case ('x_low')
    inward_normal(1) = 1.0d0
  case ('x_high')
    inward_normal(1) = -1.0d0
  case ('y_low')
    inward_normal(2) = 1.0d0
  case ('y_high')
    inward_normal(2) = -1.0d0
  case ('z_low')
    inward_normal(3) = 1.0d0
  case ('z_high')
    inward_normal(3) = -1.0d0
  case default
    error stop 'Unknown particles.species.inject_face.'
  end select
  end procedure resolve_inward_normal

  !> 粒子種設定から有効粒子数密度 `[1/m^3]` を計算する。
  module procedure species_number_density_m3
  number_density_m3 = spec%number_density_m3
  if (spec%has_number_density_cm3) number_density_m3 = spec%number_density_cm3*1.0d6
  end procedure species_number_density_m3

  !> 粒子種設定から有効温度 `[K]` を計算する。
  module procedure species_temperature_k
  temperature_k = spec%temperature_k
  if (spec%has_temperature_ev) temperature_k = spec%temperature_ev*1.160451812d4
  end procedure species_temperature_k

  !> drifting Maxwellian の片側流入束 `[1/m^2/s]` を評価する。
  module procedure compute_inflow_flux_from_drifting_maxwellian
  real(dp) :: sigma, alpha, u_n

  u_n = dot_product(drift_velocity, inward_normal)
  sigma = sqrt(k_boltzmann*temperature_k/m_particle)
  if (sigma <= 0.0d0) then
    gamma_in = number_density_m3*max(0.0d0, u_n)
    return
  end if

  alpha = u_n/sigma
  gamma_in = number_density_m3*(sigma*standard_normal_pdf(alpha) + u_n*standard_normal_cdf(alpha))
  end procedure compute_inflow_flux_from_drifting_maxwellian

  !> 標準正規分布の PDF を評価する。
  module procedure standard_normal_pdf
  real(dp), parameter :: inv_sqrt_2pi = 3.98942280401432678d-1

  pdf = inv_sqrt_2pi*exp(-0.5d0*x*x)
  end procedure standard_normal_pdf

  !> 標準正規分布の CDF を評価する。
  module procedure standard_normal_cdf
  real(dp), parameter :: inv_sqrt_2 = 7.07106781186547524d-1

  cdf = 0.5d0*(1.0d0 + erf(x*inv_sqrt_2))
  end procedure standard_normal_cdf

  !> 注入面上開口矩形の有効面積 `[m^2]` を計算する。
  module procedure compute_face_area_from_bounds
  integer :: axis_t1, axis_t2

  call resolve_face_axes(inject_face, axis_t1, axis_t2)
  area = (pos_high(axis_t1) - pos_low(axis_t1))*(pos_high(axis_t2) - pos_low(axis_t2))
  end procedure compute_face_area_from_bounds

  !> 注入面識別子から接線2軸インデックスを返す。
  module procedure resolve_face_axes
  select case (trim(lower(inject_face)))
  case ('x_low', 'x_high')
    axis_t1 = 2
    axis_t2 = 3
  case ('y_low', 'y_high')
    axis_t1 = 3
    axis_t2 = 1
  case ('z_low', 'z_high')
    axis_t1 = 1
    axis_t2 = 2
  case default
    error stop 'Unknown particles.species.inject_face.'
  end select
  end procedure resolve_face_axes

end submodule bem_app_config_parser_validate
