!> メッシュの TOML 読み取りと、モード・表面モデルの preflight。
submodule(bem_app_config_parser) bem_app_config_parser_read_mesh
  implicit none
contains

  module procedure ensure_template_capacity
  type(template_spec), allocatable :: grown(:)
  integer :: old_capacity, new_capacity

  if (required_size <= 0) return
  if (allocated(cfg%templates)) then
    old_capacity = size(cfg%templates)
  else
    old_capacity = 0
  end if
  if (old_capacity >= required_size) return

  new_capacity = max(required_size, max(max_templates, max(1, 2*old_capacity)))
  allocate (grown(new_capacity))
  if (old_capacity > 0) grown(1:old_capacity) = cfg%templates(1:old_capacity)
  call move_alloc(grown, cfg%templates)
  end procedure ensure_template_capacity

  module procedure apply_mesh_toml_table
  type(toml_key), allocatable :: keys(:)
  integer :: ikey
  character(len=:), allocatable :: k

  call table%get_keys(keys)
  do ikey = 1, size(keys)
    k = lower_ascii(trim(keys(ikey)%key))
    select case (trim(k))
    case ('mode')
      call get_toml_string(table, keys(ikey), cfg%mesh_mode, 'mesh.mode')
      cfg%mesh_mode = lower_ascii(trim(cfg%mesh_mode))
    case ('obj_path')
      call get_toml_string(table, keys(ikey), cfg%obj_path, 'mesh.obj_path')
    case ('surface_model')
      call get_toml_string(table, keys(ikey), cfg%mesh_surface_model, 'mesh.surface_model')
      cfg%mesh_surface_model = lower_ascii(trim(cfg%mesh_surface_model))
    case ('surface_side')
      call get_toml_string(table, keys(ikey), cfg%mesh_surface_side_policy, 'mesh.surface_side')
      cfg%mesh_surface_side_policy = lower_ascii(trim(cfg%mesh_surface_side_policy))
    case ('obj_scale')
      call get_toml_real(table, keys(ikey), cfg%obj_scale, 'mesh.obj_scale')
    case ('obj_rotation')
      call get_toml_real_array(table, keys(ikey), cfg%obj_rotation, 'mesh.obj_rotation')
    case ('obj_offset')
      call get_toml_real_array(table, keys(ikey), cfg%obj_offset, 'mesh.obj_offset')
    case ('templates')
      call read_template_array(cfg, table, keys(ikey), authoring)
    case ('groups')
      call read_mesh_groups_table(table, keys(ikey), authoring)
    case default
      error stop 'Unknown key in [mesh]: '//trim(keys(ikey)%key)
    end select
  end do
  end procedure apply_mesh_toml_table

  module procedure read_mesh_groups_table
  type(config_toml_table), pointer :: groups_table, group_table
  type(toml_key), allocatable :: group_keys(:)
  integer :: igroup, stat

  nullify (groups_table)
  call get_value(table, key, groups_table, stat=stat)
  call require_toml_success(stat, 'mesh.groups')
  if (.not. associated(groups_table)) error stop 'mesh.groups must be a table.'

  call groups_table%get_keys(group_keys)
  call ensure_authoring_group_capacity(authoring, size(group_keys))
  authoring%n_groups = int(size(group_keys), i32)
  if (size(group_keys) > 0) authoring%groups(1:size(group_keys)) = mesh_group_authoring_spec()
  do igroup = 1, size(group_keys)
    nullify (group_table)
    call get_value(groups_table, group_keys(igroup), group_table, stat=stat)
    call require_toml_success(stat, 'mesh.groups entry')
    if (.not. associated(group_table)) error stop 'mesh.groups entries must be tables.'
    if (len_trim(group_keys(igroup)%key) > len(authoring%groups(igroup)%name)) then
      error stop 'mesh.groups name is too long.'
    end if
    authoring%groups(igroup)%name = trim(group_keys(igroup)%key)
    call apply_mesh_group_toml_table(authoring%groups(igroup), group_table)
  end do
  end procedure read_mesh_groups_table

  module procedure apply_mesh_group_toml_table
  type(toml_key), allocatable :: keys(:)
  integer :: ikey
  character(len=:), allocatable :: k

  call table%get_keys(keys)
  do ikey = 1, size(keys)
    k = lower_ascii(trim(keys(ikey)%key))
    select case (trim(k))
    case ('placement_mode')
      call get_toml_string(table, keys(ikey), group%placement_mode, 'mesh.groups.placement_mode')
      group%placement_mode = lower_ascii(trim(group%placement_mode))
      group%has_placement_mode = .true.
    case ('anchor')
      call get_toml_string(table, keys(ikey), group%anchor, 'mesh.groups.anchor')
      group%anchor = lower_ascii(trim(group%anchor))
      group%has_anchor = .true.
    case ('offset')
      call get_toml_real_array(table, keys(ikey), group%offset, 'mesh.groups.offset')
      group%has_offset = .true.
    case ('offset_frac')
      call get_toml_real_array(table, keys(ikey), group%offset_frac, 'mesh.groups.offset_frac')
      group%has_offset_frac = .true.
    case ('scale')
      call get_toml_real(table, keys(ikey), group%scale, 'mesh.groups.scale')
      group%has_scale = .true.
    case ('scale_from')
      call get_toml_string(table, keys(ikey), group%scale_from, 'mesh.groups.scale_from')
      group%scale_from = lower_ascii(trim(group%scale_from))
      group%has_scale_from = .true.
    case ('scale_factor')
      call get_toml_real(table, keys(ikey), group%scale_factor, 'mesh.groups.scale_factor')
      group%has_scale_factor = .true.
    case default
      error stop 'Unknown key in [mesh.groups.*]: '//trim(keys(ikey)%key)
    end select
  end do
  end procedure apply_mesh_group_toml_table

  module procedure read_template_array
  type(config_toml_array), pointer :: array
  type(config_toml_table), pointer :: child
  integer :: itemplate, n, stat

  nullify (array)
  call get_value(table, key, array, stat=stat)
  call require_toml_success(stat, 'mesh.templates')
  if (.not. associated(array)) error stop 'mesh.templates must be an array of tables.'

  n = toml_len(array)
  call ensure_template_capacity(cfg, n)
  call ensure_authoring_template_capacity(authoring, n)
  if (n > 0) cfg%templates(1:n) = template_spec()
  if (n > 0) authoring%templates(1:n) = template_authoring_spec()
  do itemplate = 1, n
    nullify (child)
    call get_value(array, itemplate, child, stat=stat)
    call require_toml_success(stat, 'mesh.templates entry')
    if (.not. associated(child)) error stop 'mesh.templates entries must be tables.'
    cfg%templates(itemplate)%enabled = .true.
    call apply_template_toml_table(cfg%templates(itemplate), child, authoring%templates(itemplate))
  end do
  cfg%n_templates = int(n, i32)
  end procedure read_template_array

  module procedure apply_template_toml_table
  type(toml_key), allocatable :: keys(:)
  integer :: ikey
  character(len=:), allocatable :: k

  call table%get_keys(keys)
  do ikey = 1, size(keys)
    k = lower_ascii(trim(keys(ikey)%key))
    select case (trim(k))
    case ('enabled')
      call get_toml_logical(table, keys(ikey), spec%enabled, 'mesh.templates.enabled')
    case ('kind')
      call get_toml_string(table, keys(ikey), spec%kind, 'mesh.templates.kind')
    case ('surface_model')
      call get_toml_string(table, keys(ikey), spec%surface_model, 'mesh.templates.surface_model')
      spec%surface_model = lower_ascii(trim(spec%surface_model))
    case ('surface_side')
      call get_toml_string(table, keys(ikey), spec%surface_side_policy, 'mesh.templates.surface_side')
      spec%surface_side_policy = lower_ascii(trim(spec%surface_side_policy))
    case ('center')
      call get_toml_real_array(table, keys(ikey), spec%center, 'mesh.templates.center')
      auth%has_center = .true.
    case ('size_x')
      call get_toml_real(table, keys(ikey), spec%size_x, 'mesh.templates.size_x')
      auth%has_size_x = .true.
    case ('size_y')
      call get_toml_real(table, keys(ikey), spec%size_y, 'mesh.templates.size_y')
      auth%has_size_y = .true.
    case ('size')
      call get_toml_real_array(table, keys(ikey), spec%size, 'mesh.templates.size')
      auth%has_size = .true.
    case ('nx')
      call get_toml_int(table, keys(ikey), spec%nx, 'mesh.templates.nx')
    case ('ny')
      call get_toml_int(table, keys(ikey), spec%ny, 'mesh.templates.ny')
    case ('nz')
      call get_toml_int(table, keys(ikey), spec%nz, 'mesh.templates.nz')
    case ('radius')
      call get_toml_real(table, keys(ikey), spec%radius, 'mesh.templates.radius')
      auth%has_radius = .true.
    case ('inner_radius')
      call get_toml_real(table, keys(ikey), spec%inner_radius, 'mesh.templates.inner_radius')
      auth%has_inner_radius = .true.
    case ('height')
      call get_toml_real(table, keys(ikey), spec%height, 'mesh.templates.height')
      auth%has_height = .true.
    case ('n_theta')
      call get_toml_int(table, keys(ikey), spec%n_theta, 'mesh.templates.n_theta')
    case ('n_r')
      call get_toml_int(table, keys(ikey), spec%n_r, 'mesh.templates.n_r')
    case ('n_z')
      call get_toml_int(table, keys(ikey), spec%n_z, 'mesh.templates.n_z')
    case ('cap')
      call get_toml_logical(table, keys(ikey), spec%cap, 'mesh.templates.cap')
    case ('cap_top')
      call get_toml_logical(table, keys(ikey), spec%cap_top, 'mesh.templates.cap_top')
      spec%has_cap_top = .true.
    case ('cap_bottom')
      call get_toml_logical(table, keys(ikey), spec%cap_bottom, 'mesh.templates.cap_bottom')
      spec%has_cap_bottom = .true.
    case ('n_lon')
      call get_toml_int(table, keys(ikey), spec%n_lon, 'mesh.templates.n_lon')
    case ('n_lat')
      call get_toml_int(table, keys(ikey), spec%n_lat, 'mesh.templates.n_lat')
    case ('group')
      call get_toml_string(table, keys(ikey), auth%group, 'mesh.templates.group')
      auth%has_group = .true.
    case ('center_local')
      call get_toml_real_array(table, keys(ikey), auth%center_local, 'mesh.templates.center_local')
      auth%has_center_local = .true.
    case ('placement_mode')
      call get_toml_string(table, keys(ikey), auth%placement_mode, 'mesh.templates.placement_mode')
      auth%placement_mode = lower_ascii(trim(auth%placement_mode))
      auth%has_placement_mode = .true.
    case ('anchor')
      call get_toml_string(table, keys(ikey), auth%anchor, 'mesh.templates.anchor')
      auth%anchor = lower_ascii(trim(auth%anchor))
      auth%has_anchor = .true.
    case ('offset')
      call get_toml_real_array(table, keys(ikey), auth%offset, 'mesh.templates.offset')
      auth%has_offset = .true.
    case ('offset_frac')
      call get_toml_real_array(table, keys(ikey), auth%offset_frac, 'mesh.templates.offset_frac')
      auth%has_offset_frac = .true.
    case ('size_mode')
      call get_toml_string(table, keys(ikey), auth%size_mode, 'mesh.templates.size_mode')
      auth%size_mode = lower_ascii(trim(auth%size_mode))
      auth%has_size_mode = .true.
    case ('size_frac')
      call get_toml_real_scalar_or_array3( &
        table, keys(ikey), auth%size_frac, auth%size_frac_len, 'mesh.templates.size_frac' &
        )
      auth%has_size_frac = .true.
    case default
      error stop 'Unknown key in [[mesh.templates]]: '//trim(keys(ikey)%key)
    end select
  end do
  end procedure apply_template_toml_table

end submodule bem_app_config_parser_read_mesh
