!> 7グループの入力を既存の内部TOMLへ変換する。物理検証と既定値は既存readerが所有する。
!! 公開入力は7グループ形式のみ。変換先のキーは内部readerの実装である。
module bem_config_layout
  use bem_kinds, only: i32
  use bem_string_utils, only: lower_ascii
  use bem_config_toml, only: stop_config_error, require_toml_success, get_toml_int
  use bem_config_layout_paths, only: reader_layout_path, grouped_layout_table
  use tomlf, only: toml_table, toml_array, toml_keyval, toml_value, toml_key, get_value, set_value, &
                   new_table, add_table, toml_len => len
  use tomlf_type, only: new_array
  implicit none
  private
  public :: normalize_config_layout
contains

  !> 7グループの公開入力を内部reader用のdocumentへ正規化する。
  subroutine normalize_config_layout(document)
    type(toml_table), allocatable, intent(inout) :: document
    type(toml_table), allocatable :: normalized
    type(toml_key), allocatable :: keys(:)
    integer :: i

    call document%get_keys(keys)
    do i = 1, size(keys)
      select case (lower_ascii(keys(i)%key))
      case ('sim', 'field_boundary', 'particle_boundary', 'surface_current_model', 'periodic2')
        call stop_config_error('flat beach.toml layout is not supported; use the grouped layout '// &
                               '(run/domain/mesh/particles/fields/sheath/output)')
      end select
    end do

    if (.not. is_grouped_layout(document, '')) then
      call stop_config_error('flat beach.toml layout is not supported; use the grouped layout '// &
                             '(run/domain/mesh/particles/fields/sheath/output)')
    end if
    allocate (normalized)
    call new_table(normalized)
    call translate_table(document, '', normalized, .false.)
    call document%destroy()
    call move_alloc(normalized, document)
  end subroutine normalize_config_layout

  recursive logical function is_grouped_layout(table, prefix) result(found)
    type(toml_table), intent(inout) :: table
    character(len=*), intent(in) :: prefix
    type(toml_key), allocatable :: keys(:)
    class(toml_value), pointer :: node, item
    character(len=:), allocatable :: name, path
    integer :: i, j

    found = .false.
    call table%get_keys(keys)
    do i = 1, size(keys)
      name = lower_ascii(keys(i)%key)
      path = joined_path(prefix, name)
      select case (path)
      case ('run', 'fields', 'sheath', 'mesh', 'output.enabled', 'output.history', &
            'output.final', 'output.checkpoint', 'output.diagnostics', 'particles.boundary', &
            'particles.reservoir', 'particles.tracking', 'particles.raycast', 'particles.secondary_emission', &
            'particles.species.charge_c', 'particles.species.mass_kg', 'particles.species.distribution', &
            'particles.species.source', 'particles.species.sampling', 'particles.species.inflow', &
            'particles.species.charging', 'particles.species.surface_interaction')
        found = .true.
        return
      end select
      call table%get(keys(i)%key, node)
      select type (node)
      type is (toml_table)
        if (path == 'particles' .or. path == 'mesh' .or. path == 'output') then
          if (is_grouped_layout(node, path)) then
            found = .true.
            return
          end if
        end if
      type is (toml_array)
        if (path /= 'particles.species') cycle
        do j = 1, toml_len(node)
          call node%get(j, item)
          select type (item)
          type is (toml_table)
            if (is_grouped_layout(item, path)) then
              found = .true.
              return
            end if
          end select
        end do
      end select
    end do
  end function is_grouped_layout

  recursive subroutine translate_table(table, prefix, target, species_context)
    type(toml_table), intent(inout) :: table
    character(len=*), intent(in) :: prefix
    type(toml_table), intent(inout), target :: target
    logical, intent(in) :: species_context
    type(toml_key), allocatable :: keys(:)
    class(toml_value), pointer :: node
    character(len=:), allocatable :: path, reader_path, text
    integer :: i, stat

    if (.not. grouped_layout_table(prefix)) call stop_config_error('Unknown or mixed-layout table: '//prefix)
    if (prefix == 'run.restart') call put_logical(target, 'output.resume', .true.)
    if (prefix == 'particles.species') call validate_source_presence(table)
    if (prefix == 'particles.species.source') then
      call find_node(table, 'mode', node)
      if (.not. associated(node)) call stop_config_error('particles.species.source requires mode')
    end if
    call table%get_keys(keys)
    do i = 1, size(keys)
      path = joined_path(prefix, lower_ascii(keys(i)%key))
      call table%get(keys(i)%key, node)
      reader_path = reader_layout_path(path)
      if (path == 'particles.species') then
        select type (node)
        type is (toml_array)
          call translate_species(node, target)
        class default
          call stop_config_error('particles.species must be an array of tables')
        end select
      else if (path == 'fields.periodic.backend') then
        call get_value(table, keys(i), text, stat=stat)
        call require_toml_success(stat, path)
        if (.not. allocated(text)) call stop_config_error(path//' must be a string')
        call translate_backend(lower_ascii(trim(text)), target)
      else if (path == 'sheath.closure') then
        call get_value(table, keys(i), text, stat=stat)
        call require_toml_success(stat, path)
        if (.not. allocated(text)) call stop_config_error(path//' must be a string')
        call translate_choice(path, lower_ascii(trim(text)), target)
      else if (len(reader_path) > 0) then
        if (species_context) reader_path = reader_path(len('particles.species.') + 1:)
        call put_node(target, reader_path, node)
      else
        select type (node)
        type is (toml_table)
          call translate_table(node, path, target, species_context)
        class default
          call stop_config_error('Unknown or mixed-layout key: '//path)
        end select
      end if
    end do
    ! No split settings may silently activate a backend different from the input.
    if (prefix == 'fields.periodic') then
      call find_node(table, 'backend', node)
      text = 'finite_images'
      if (associated(node)) then
        call get_value(table, node%key, text, stat=stat)
        call require_toml_success(stat, 'fields.periodic.backend')
        text = lower_ascii(trim(text))
      end if
      if (text == 'finite_images') then
        call find_node(table, 'lower_boundary_model', node)
        if (associated(node)) call stop_config_error('split periodic settings require an explicit split backend')
        call find_node(table, 'upper_fourier', node)
        if (associated(node)) call stop_config_error('split periodic settings require an explicit split backend')
        call find_node(table, 'reference', node)
        if (associated(node)) call stop_config_error('split periodic settings require an explicit split backend')
      end if
    end if
    if (prefix == 'sheath') then
      call find_node(table, 'closure', node)
      text = 'none'
      if (associated(node)) then
        call get_value(table, node%key, text, stat=stat)
        call require_toml_success(stat, 'sheath.closure')
      end if
      if (lower_ascii(trim(text)) == 'zero_current') then
        call find_node(table, 'response', node)
        if (associated(node)) then
          call get_value(table, node%key, text, stat=stat)
          call require_toml_success(stat, 'sheath.response')
          if (lower_ascii(trim(text)) /= 'zhao') call stop_config_error('zero_current requires sheath.response=zhao')
        end if
      end if
    end if
  end subroutine translate_table

  subroutine translate_species(array, target)
    type(toml_array), intent(inout) :: array
    type(toml_table), intent(inout), target :: target
    type(toml_array) :: normalized
    class(toml_value), pointer :: item
    class(toml_value), allocatable :: flat
    integer :: i, stat

    call new_array(normalized)
    do i = 1, toml_len(array)
      call array%get(i, item)
      allocate (toml_table :: flat)
      select type (flat)
      type is (toml_table)
        call new_table(flat)
        select type (item)
        type is (toml_table)
          call translate_table(item, 'particles.species', flat, .true.)
        class default
          call stop_config_error('particles.species entries must be tables')
        end select
      end select
      call normalized%push_back(flat, stat)
      call require_toml_success(stat, 'particles.species')
    end do
    call put_node(target, 'particles.species', normalized)
  end subroutine translate_species

  subroutine translate_backend(backend, target)
    character(len=*), intent(in) :: backend
    type(toml_table), intent(inout), target :: target

    select case (backend)
    case ('finite_images')
      call put_string(target, 'sim.field_periodic_far_correction', 'none')
    case ('cached_kneq0', 'panel_spectral_reference')
      if (backend == 'cached_kneq0') then
        call put_string(target, 'sim.field_periodic_far_correction', 'cached_kneq0')
      else
        call put_string(target, 'sim.field_periodic_far_correction', 'none')
      end if
      call put_string(target, 'periodic2.nonzero_mode_backend', backend)
      call put_string(target, 'periodic2.zero_mode_policy', 'exclude_k0')
    case default
      call stop_config_error('invalid fields.periodic.backend')
    end select
  end subroutine translate_backend

  subroutine translate_choice(path, value, target)
    character(len=*), intent(in) :: path, value
    type(toml_table), intent(inout), target :: target

    select case (path)
    case ('sheath.closure')
      select case (value)
      case ('none')
        call put_string(target, 'surface_current_model.model', 'none')
      case ('zero_current')
        call put_string(target, 'surface_current_model.model', 'zhao_stationary')
      case default
        call stop_config_error('invalid sheath.closure')
      end select
    end select
  end subroutine translate_choice

  subroutine validate_source_presence(table)
    type(toml_table), intent(inout) :: table
    class(toml_value), pointer :: node, count
    type(toml_key) :: key
    integer(i32) :: volume_count

    call find_node(table, 'source', node)
    if (associated(node)) return
    call find_node(table, 'sampling', node)
    if (.not. associated(node)) return
    select type (node)
    type is (toml_table)
      call find_node(node, 'volume_macro_particles_per_batch', count)
      if (.not. associated(count)) return
      key%key = count%key
      call get_toml_int(node, key, volume_count, 'particles.species.sampling.volume_macro_particles_per_batch')
      if (volume_count /= 0_i32) call stop_config_error('volume samples require particles.species.source')
    end select
  end subroutine validate_source_presence

  subroutine find_node(table, name, node)
    type(toml_table), intent(inout) :: table
    character(len=*), intent(in) :: name
    class(toml_value), pointer, intent(out) :: node
    type(toml_key), allocatable :: keys(:)
    integer :: i

    call table%get_keys(keys)
    nullify (node)
    do i = 1, size(keys)
      if (lower_ascii(keys(i)%key) /= name) cycle
      call table%get(keys(i)%key, node)
      return
    end do
  end subroutine find_node

  function joined_path(prefix, key) result(path)
    character(len=*), intent(in) :: prefix, key
    character(len=:), allocatable :: path
    path = key
    if (len(prefix) > 0) path = prefix//'.'//key
  end function joined_path

  subroutine put_node(target, path, node)
    type(toml_table), intent(inout), target :: target
    character(len=*), intent(in) :: path
    class(toml_value), intent(inout) :: node
    class(toml_value), allocatable :: copied
    type(toml_table), pointer :: parent, child
    integer :: start, finish, stat
    character(len=:), allocatable :: key

    parent => target
    start = 1
    do
      finish = index(path(start:), '.')
      if (finish == 0) exit
      key = path(start:start + finish - 2)
      call get_value(parent, key, child, requested=.false., stat=stat)
      call require_toml_success(stat, path)
      if (.not. associated(child)) then
        call add_table(parent, key, child, stat=stat)
        call require_toml_success(stat, path)
      end if
      parent => child
      start = start + finish
    end do
    call clone_node(node, copied)
    copied%key = path(start:)
    call parent%push_back(copied, stat)
    call require_toml_success(stat, 'duplicate or invalid setting: '//path)
  end subroutine put_node

  !> 再帰的なpolymorphic containerを構成要素ごとに複製し、所有権を分離する。
  recursive subroutine clone_node(node, copied)
    class(toml_value), intent(inout) :: node
    class(toml_value), allocatable, intent(out) :: copied
    class(toml_value), pointer :: child
    class(toml_value), allocatable :: copied_child
    type(toml_key), allocatable :: keys(:)
    integer :: i, stat

    select type (node)
    type is (toml_table)
      allocate (toml_table :: copied)
      select type (copied)
      type is (toml_table)
        call new_table(copied)
        call node%get_keys(keys)
        do i = 1, size(keys)
          call node%get(keys(i)%key, child)
          call clone_node(child, copied_child)
          call copied%push_back(copied_child, stat)
          call require_toml_success(stat, 'copy TOML table')
        end do
      end select
    type is (toml_array)
      allocate (toml_array :: copied)
      select type (copied)
      type is (toml_array)
        call new_array(copied)
        copied%inline = node%inline
        do i = 1, toml_len(node)
          call node%get(i, child)
          call clone_node(child, copied_child)
          call copied%push_back(copied_child, stat)
          call require_toml_success(stat, 'copy TOML array')
        end do
      end select
    type is (toml_keyval)
      allocate (copied, source=node)
    class default
      call stop_config_error('Unsupported TOML value')
    end select
    if (allocated(node%key)) copied%key = node%key
    copied%origin = node%origin
  end subroutine clone_node

  subroutine put_string(target, path, value)
    type(toml_table), intent(inout), target :: target
    character(len=*), intent(in) :: path, value
    type(toml_table) :: scratch
    class(toml_value), pointer :: node
    integer :: stat

    call new_table(scratch)
    call set_value(scratch, 'value', value, stat=stat)
    call require_toml_success(stat, path)
    call scratch%get('value', node)
    call put_node(target, path, node)
    call scratch%destroy()
  end subroutine put_string

  subroutine put_logical(target, path, value)
    type(toml_table), intent(inout), target :: target
    character(len=*), intent(in) :: path
    logical, intent(in) :: value
    type(toml_table) :: scratch
    class(toml_value), pointer :: node
    integer :: stat

    call new_table(scratch)
    call set_value(scratch, 'value', value, stat=stat)
    call require_toml_success(stat, path)
    call scratch%get('value', node)
    call put_node(target, path, node)
    call scratch%destroy()
  end subroutine put_logical
end module bem_config_layout
