!> 主ループへ渡す出力先（history の unit と間隔、終了時のメッシュ電位）を 1 つの型にまとめる。
!! unit が -1 の history は書かない。CLI は root rank で writer を開いてこの型へ詰め、終了時に閉じる。
module bem_run_outputs
  use bem_kinds, only: dp, i32
  use bem_app_config_types, only: app_config
  use bem_output_writer, only: open_history_writer, open_potential_history_writer, open_top_reference_history_writer, &
                               open_matching_plane_history_writer, open_fixed_current_history_writer, &
                               open_matching_plane_spectrum_history_writer, open_secondary_emission_history_writer, &
                               remove_previous_run_outputs, trim_history_files, summary_identity_type
  use bem_periodic_checkpoint, only: invalidate_output_checkpoints
  implicit none
  private

  public :: open_run_outputs
  public :: flush_run_outputs
  public :: close_run_outputs

  type, public :: run_outputs_type
    !> 電荷 history（output.history.stride_batches ごと）。
    integer :: history_unit = -1
    !> 要素電位 history。間隔は history_stride。
    integer :: potential_history_unit = -1
    !> z-high 参照電位 history。要素電位 history と同じ batch で書くので、potential_history_unit が有効なときだけ使う。
    integer :: top_reference_history_unit = -1
    integer :: matching_plane_history_unit = -1
    !> 外部源のスペクトル history。外部根を更新した batch で、間隔によらず書く。
    integer :: matching_plane_spectrum_history_unit = -1
    integer :: fixed_current_history_unit = -1
    !> SEE history。毎 batch 書く。
    integer :: secondary_emission_history_unit = -1
    !> history を書く accepted batch の間隔。0 以下なら matching-plane・spectrum・固定電流 history を書かず、
    !! 電荷・要素電位 history は毎 batch の扱いになる（CLI の writer はこのとき開かれない）。
    integer(i32) :: history_stride = 1_i32
    !> 終了時に要素重心の電位を mesh_potential_v へ求める（root rank だけ）。
    logical :: mesh_potential = .false.
    real(dp), allocatable :: mesh_potential_v(:)
    !> 主ループが開始時に作った summary の不変量。最終出力の summary.txt はこれを使う。
    type(summary_identity_type) :: summary_identity
  end type run_outputs_type

contains

  !> root rank で出力先をこの run のものにしてから、設定どおりに history writer を開く。root 以外は全て無効（unit=-1）。
  !! @param[in] resume_batch 再開した checkpoint の batch 数（`resumed` のときだけ使う）。
  subroutine open_run_outputs(app, resumed, resume_batch, is_root, outputs)
    type(app_config), intent(in) :: app
    logical, intent(in) :: resumed, is_root
    integer(i32), intent(in) :: resume_batch
    type(run_outputs_type), intent(out) :: outputs
    logical :: opened
    integer :: unit_id

    outputs%history_stride = app%history_stride
    outputs%mesh_potential = app%write_mesh_potential
    if (.not. is_root) return
    if (app%write_output) call claim_output_dir(app, resumed, resume_batch)
    call open_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%history_unit = unit_id
    call open_potential_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%potential_history_unit = unit_id
    call open_top_reference_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%top_reference_history_unit = unit_id
    call open_matching_plane_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%matching_plane_history_unit = unit_id
    call open_fixed_current_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%fixed_current_history_unit = unit_id
    call open_matching_plane_spectrum_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%matching_plane_spectrum_history_unit = unit_id
    call open_secondary_emission_history_writer(app, resumed, opened, unit_id)
    if (opened) outputs%secondary_emission_history_unit = unit_id
  end subroutine open_run_outputs

  !> 出力先に残る BEACH の成果物を、これから続ける run と同じ世代にそろえる。root rank だけが呼ぶ。
  !! 再開では、checkpoint より後の history 行（巻き戻した試行の行）を除き、checkpoint のファイルは変更しない。
  !! `run.restart.from` が別の directory なら、その中は何も変更しない。
  !! fresh run では、まず前の run の checkpoint を再開候補から外し、次に今回書かないかもしれない任意出力を消す。
  !! 出力先を読み書きするのは root だけで（checkpoint の slot 選択も root が行って broadcast する）、
  !! fresh run の他の rank は出力先を読まないので、追加の barrier は要らない。
  subroutine claim_output_dir(app, resumed, resume_batch)
    type(app_config), intent(in) :: app
    logical, intent(in) :: resumed
    integer(i32), intent(in) :: resume_batch

    if (resumed) then
      call trim_history_files(trim(app%output_dir), resume_batch)
    else
      call invalidate_output_checkpoints(trim(app%output_dir))
      call remove_previous_run_outputs(trim(app%output_dir))
    end if
  end subroutine claim_output_dir

  !> 開いている history の行を書き出す。checkpoint の完了 manifest を公開する前に呼び、
  !! 再開できる batch 以下の history 行がすべて改行まで書かれていることを保証する。
  subroutine flush_run_outputs(outputs)
    type(run_outputs_type), intent(in) :: outputs

    call flush_unit(outputs%history_unit)
    call flush_unit(outputs%potential_history_unit)
    call flush_unit(outputs%top_reference_history_unit)
    call flush_unit(outputs%matching_plane_history_unit)
    call flush_unit(outputs%fixed_current_history_unit)
    call flush_unit(outputs%secondary_emission_history_unit)
    call flush_unit(outputs%matching_plane_spectrum_history_unit)
  end subroutine flush_run_outputs

  subroutine flush_unit(unit_id)
    integer, intent(in) :: unit_id

    if (unit_id /= -1) flush (unit_id)
  end subroutine flush_unit

  !> 開いている history writer を閉じる。
  subroutine close_run_outputs(outputs)
    type(run_outputs_type), intent(inout) :: outputs

    call close_unit(outputs%history_unit)
    call close_unit(outputs%potential_history_unit)
    call close_unit(outputs%top_reference_history_unit)
    call close_unit(outputs%matching_plane_history_unit)
    call close_unit(outputs%fixed_current_history_unit)
    call close_unit(outputs%secondary_emission_history_unit)
    call close_unit(outputs%matching_plane_spectrum_history_unit)
  end subroutine close_run_outputs

  subroutine close_unit(unit_id)
    integer, intent(inout) :: unit_id

    if (unit_id == -1) return
    close (unit_id)
    unit_id = -1
  end subroutine close_unit

end module bem_run_outputs
