! SPDX-License-Identifier: AGPL-3.0-or-later
! Copyright 2024-2025 Max-Planck-Institut für Nachhaltige Materialien GmbH
! Copyright 2011-2024 Max-Planck-Institut für Eisenforschung GmbH
!
! DAMASK is free software: you can redistribute it and/or modify
! it under the terms of the GNU Affero General Public License as
! published by the Free Software Foundation, either version 3 of the
! License, or (at your option) any later version.
!
! This program is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU Affero General Public License for more details.
!
! You should have received a copy of the GNU Affero General Public License
! along with this program.  If not, see <https://www.gnu.org/licenses/>.
!--------------------------------------------------------------------------------------------------
!> @author Sharan Roongta, Max-Planck-Institut für Eisenforschung GmbH
!> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH
!> @brief Data types to create a scalar, a list, and a dictionary/hash
!> @details module describes the various functions to store and get the yaml data.
!! A node is the base class for scalar, list and dictionary, list items and dictionary entries point
!! to a node.
!--------------------------------------------------------------------------------------------------

module types
  use IO
  use prec
  use misc

  implicit none(type,external)
  private

  type, abstract, public :: tNode
    contains
    procedure(asFormattedStr), deferred :: &
      asFormattedStr
    procedure :: &
      asScalar => tNode_asScalar, &
      asList   => tNode_asList, &
      asDict   => tNode_asDict
  end type tNode

  type, extends(tNode), public :: tScalar
    character(len=:), allocatable, private :: &
      value
    contains
    procedure :: &
      asFormattedStr => tScalar_asFormattedStr, &
      asReal => tScalar_asReal, &
      asInt  => tScalar_asInt, &
      asBool => tScalar_asBool, &
      asStr  => tScalar_asStr
  end type tScalar

  type, extends(tNode), public :: tList
    type(tItem), pointer :: &
      first => NULL(), &
      last => NULL()
    integer, private :: &
      length = 0
    contains
    procedure :: &
      asFormattedStr => tList_asFormattedStr, &
      append         => tList_append, &
      as1dReal       => tList_as1dReal, &
      as2dReal       => tList_as2dReal, &
      as1dInt        => tList_as1dInt, &
      as1dBool       => tList_as1dBool, &
      as1dStr        => tList_as1dStr, &
      contains       => tList_contains, &
      tList_get, &
      tList_get_scalar, &
      tList_get_list, &
      tList_get_dict, &
      tList_get_asReal, &
      tList_get_as1dReal, &
      tList_get_asInt, &
      tList_get_as1dInt, &
      tList_get_asBool, &
      tList_get_as1dBool, &
      tList_get_asStr, &
      tList_get_as1dStr
    generic :: get          => tList_get
    generic :: get_scalar   => tList_get_scalar
    generic :: get_list     => tList_get_list
    generic :: get_dict     => tList_get_dict
    generic :: get_asReal   => tList_get_asReal
    generic :: get_as1dReal => tList_get_as1dReal
    generic :: get_asInt    => tList_get_asInt
    generic :: get_as1dInt  => tList_get_as1dInt
    generic :: get_asBool   => tList_get_asBool
    generic :: get_as1dBool => tList_get_as1dBool
    generic :: get_asStr    => tList_get_asStr
    generic :: get_as1dStr  => tList_get_as1dStr
    final :: tList_finalize
  end type tList

  type, extends(tList), public :: tDict
    contains
    procedure ::  &
      asFormattedStr => tDict_asFormattedStr, &
      set            => tDict_set, &
      index          => tDict_index, &
      key            => tDict_key, &
      keys           => tDict_keys, &
      contains       => tDict_contains, &
      tDict_get, &
      tDict_get_scalar, &
      tDict_get_list, &
      tDict_get_dict, &
      tDict_get_asReal, &
      tDict_get_as1dReal_sized, &
      tDict_get_as1dReal_chunked, &
#ifdef __GFORTRAN__
      tDict_get_as2dReal_a, &
      tDict_get_as2dReal_b, &
#else
      tDict_get_as2dReal, &
#endif
      tDict_get_asInt, &
      tDict_get_as1dInt, &
      tDict_get_asBool, &
      tDict_get_as1dBool, &
      tDict_get_asStr, &
      tDict_get_as1dStr
    generic :: get          => tDict_get
    generic :: get_scalar   => tDict_get_scalar
    generic :: get_list     => tDict_get_list
    generic :: get_dict     => tDict_get_dict
    generic :: get_asReal   => tDict_get_asReal
    generic :: get_as1dReal => tDict_get_as1dReal_sized
    generic :: get_as1dReal => tDict_get_as1dReal_chunked
#ifdef __GFORTRAN__
    generic :: get_as2dReal => tDict_get_as2dReal_a
    generic :: get_as2dReal => tDict_get_as2dReal_b
#else
    generic :: get_as2dReal => tDict_get_as2dReal
#endif
    generic :: get_asInt    => tDict_get_asInt
    generic :: get_as1dInt  => tDict_get_as1dInt
    generic :: get_asBool   => tDict_get_asBool
    generic :: get_as1dBool => tDict_get_as1dBool
    generic :: get_asStr    => tDict_get_asStr
    generic :: get_as1dStr  => tDict_get_as1dStr
   end type tDict


  type, public :: tItem
    character(len=:), allocatable :: key
    class(tNode),     pointer     :: node => NULL()
    type(tItem),      pointer     :: next => NULL()
    contains
    final :: tItem_finalize
  end type tItem

  type(tDict), target, public :: &
    emptyDict
  type(tList), target, public :: &
    emptyList

  abstract interface

    recursive function asFormattedStr(self)
      import tNode
      character(len=:), allocatable      :: asFormattedStr
      class(tNode), intent(in), target   :: self
    end function asFormattedStr

  end interface

  interface tScalar
    module procedure tScalar_init__
  end interface tScalar

  interface assignment (=)
    module procedure tScalar_assign__
  end interface assignment (=)

  interface size
    module procedure tList_size
  end interface size

  public :: &
    types_init, &
    types_selfTest, &
#ifdef __GFORTRAN__
    output_as1dStr, &                                       !ToDo: Hack for GNU. Remove later
#endif
    assignment(=), &
    size

contains

!--------------------------------------------------------------------------------------------------
!> @brief Do sanity checks.
!--------------------------------------------------------------------------------------------------
subroutine types_init

  print'(/,1x,a)', '<<<+-  types init  -+>>>'

  call types_selfTest()

end subroutine types_init


!--------------------------------------------------------------------------------------------------
!> @brief Check correctness of some type bound procedures.
!--------------------------------------------------------------------------------------------------
subroutine types_selfTest()

  character(len=5), dimension(3), parameter :: array = ['ab   ', '12435', 'x    ']


  if ('[ab, 12435, x]' /= join_str_array(array)) error stop 'join_str_array'

  scalar: block
    type(tScalar), target :: s
    type(tScalar), pointer :: s_pointer


    s_pointer => s%asScalar()
    s = '1'
    if (s%asInt() /= 1)                error stop 'tScalar_asInt'
    if (s_pointer%asInt() /= 1)        error stop 'tScalar_asInt(pointer)'
    if (dNeq(s%asReal(),1.0_pREAL))    error stop 'tScalar_asReal'
    s = 'true'
    if (.not. s%asBool())              error stop 'tScalar_asBool'
    if (.not. s_pointer%asBool())      error stop 'tScalar_asBool(pointer)'
    if (s%asStr() /= 'true')           error stop 'tScalar_asStr'
    if (s%asFormattedStr() /= 'true')  error stop 'tScalar_asFormattedStr'


  end block scalar

  list: block
    type(tList),   pointer :: l, l_pointer
    type(tScalar), pointer :: s1,s2


    allocate(s1)
    allocate(s2)
    s1 = '1'
    s2 = '2'
    allocate(l)
    l_pointer => l%asList()
    if (l%contains('1'))                               error stop 'empty tList_contains'
    if (l_pointer%contains('1'))                       error stop 'empty tList_contains(pointer)'
    call l%append(s1)
    call l%append(s2)
    if (size(l) /= 2)                                  error stop 'tList%length/size(tList)'
    if (dNeq(l%get_asReal(1),1.0_pREAL))               error stop 'tList_get_asReal'
    if (l%get_asInt(1) /= 1)                           error stop 'tList_get_asInt'
    if (l%get_asStr(2) /= '2')                         error stop 'tList_get_asStr'
    if (any(l%as1dInt() /= [1,2]))                     error stop 'tList_as1dInt'
    if (any(dNeq(l%as1dReal(),real([1.0,2.0],pREAL)))) error stop 'tList_as1dReal'
    s1 = 'true'
    s2 = 'false'
    if (any(l%as1dBool() .neqv. [.true.,.false.]))     error stop 'tList_as1dBool'
    if (any(l%as1dStr() /= ['true ','false']))         error stop 'tList_as1dStr'
    if (l%asFormattedStr() /= '[true, false]')         error stop 'tList_asFormattedStr'
    if (   .not. l%contains('true') &
      .or. .not. l%contains('false'))                  error stop 'tList_contains'

  end block list

  dict: block
    type(tDict),   pointer :: d, d_pointer
    type(tList),   pointer :: l
    type(tScalar), pointer :: s1,s2,s3,s4


    allocate(s1)
    allocate(s2)
    s1 = '1'
    s2 = '2'
    allocate(l)
    call l%append(s1)
    call l%append(s2)

    allocate(s3)
    allocate(s4)
    s3 = '3'
    s4 = '4'
    allocate(d)
    d_pointer => d%asDict()
    if (d%contains('one-two'))                        error stop 'empty tDict_contains'
    if (d_pointer%contains('one-two'))                error stop 'empty tDict_contains(pointer)'
    if (d%get_asInt('one-two',defaultVal=-1) /= -1)   error stop 'empty tDict_get'
    call d%set('one-two',l)
    call d%set('three',s3)
    call d%set('four',s4)
    if (d%asFormattedStr() /= '{one-two: [1, 2], three: 3, four: 4}') &
                                                      error stop 'tDict_asFormattedStr'
    if (d%get_asInt('three') /= 3)                    error stop 'tDict_get_asInt'
    if (dNeq(d%get_asReal('three'),3.0_pREAL))        error stop 'tDict_get_asReal'
    if (any(d%get_as1dReal('one-two') /= real([1,2],pREAL))) &
                                                      error stop 'tDict_get_as1dReal'
    if (any(d%get_as1dReal('three',requiredSize=3) /= real([3,3,3],pREAL))) &
                                                      error stop 'tDict_get_as1dReal/size'
    if (d%get_asStr('three') /= '3')                  error stop 'tDict_get_asStr'
    if (any(d%get_as1dInt('one-two') /= [1,2]))       error stop 'tDict_get_as1dInt'
    if (any(d%get_as1dInt('three',requiredSize=3) /= [3,3,3])) &
                                                      error stop 'tDict_get_as1dInt/size'
    call d%set('one-two',s4)
    if (d%asFormattedStr() /= '{one-two: 4, three: 3, four: 4}') &
                                                      error stop 'tDict_set overwrite'
    if (   .not. d%contains('one-two') &
      .or. .not. d%contains('three') &
      .or. .not. d%contains('four') &
      )                                               error stop 'tDict_contains'


  end block dict

#ifdef __GFORTRAN__
  dict_get_as1dReal_chunked: block
    type(tDict),   pointer :: d
    type(tList),   pointer :: l_outer, l_inner
    type(tScalar), pointer :: s1,s2,s3
    real(pREAL), dimension(5) :: a


    allocate(s1)
    allocate(s2)
    allocate(s3)
    s1 = '1.'
    s2 = '2'
    s3 = '3.0'

    allocate(l_inner)
    call l_inner%append(s1)
    call l_inner%append(s2)

    allocate(l_outer)
    call l_outer%append(l_inner)
    call l_outer%append(s3)

    allocate(d)
    call d%set('list',l_outer)
    call d%set('scalar',s1)

    a = d%get_as1dReal('list',requiredChunks=[2,3])
    if (any(dNeq(a,real([1.0,2.0,3.0,3.0,3.0],pREAL)))) &
      error stop 'dict_get_as1dReal_shape list'

    if (any(dNeq(d%get_as1dReal('non-existing',a,[2,3]),a))) &
      error stop 'dict_get_as1dReal_shape default individual'

    a = real([42.0, 42.0, 5.0, 5.0, 5.0],pREAL)
    if (any(dNeq(d%get_as1dReal('non-existing',[42._pREAL, 5._pREAL],[2,3]),a))) &
      error stop 'dict_get_as1dReal_shape default group'

    if (any(dNeq(d%get_as1dReal('scalar',requiredChunks=[3,5,2]),misc_ones(10)))) &
      error stop 'dict_get_as1dReal_shape scalar'

  end block dict_get_as1dReal_chunked
#endif

end subroutine types_selfTest


!---------------------------------------------------------------------------------------------------
!> @brief Init from string.
!---------------------------------------------------------------------------------------------------
type(tScalar) pure function tScalar_init__(value)

  character(len=*), intent(in) ::  value


  tScalar_init__%value = value

end function tScalar_init__


!---------------------------------------------------------------------------------------------------
!> @brief Set value from string.
!---------------------------------------------------------------------------------------------------
elemental pure subroutine tScalar_assign__(self,value)

  type(tScalar),    intent(out) :: self
  character(len=*), intent(in)  :: value


  self%value = value

end subroutine tScalar_assign__


!--------------------------------------------------------------------------------------------------
!> @brief Format as string (YAML flow style).
!--------------------------------------------------------------------------------------------------
recursive function tScalar_asFormattedStr(self) result(str)

  class (tScalar), intent(in), target    :: self
  character(len=:), allocatable          :: str


  str = trim(self%value)

end function tScalar_asFormattedStr


!--------------------------------------------------------------------------------------------------
!> @brief Type guard, guarantee scalar.
!--------------------------------------------------------------------------------------------------
function tNode_asScalar(self) result(scalar)

  class(tNode),   intent(in), target :: self
  type(tScalar),  pointer            :: scalar


  select type(self)
    class is(tScalar)
      scalar => self
    class default
      nullify(scalar)
      call IO_error(706_pI16,trim(self%asFormattedStr()),'is not a scalar',emph=[1])
  end select

end function tNode_asScalar


!--------------------------------------------------------------------------------------------------
!> @brief Type guard, guarantee list.
!--------------------------------------------------------------------------------------------------
function tNode_asList(self) result(list)

  class(tNode), intent(in), target :: self
  class(tList), pointer            :: list


  select type(self)
    class is(tList)
      list => self
    class default
      nullify(list)
      call IO_error(706_pI16,trim(self%asFormattedStr()),'is not a list',emph=[1])
  end select

end function tNode_asList


!--------------------------------------------------------------------------------------------------
!> @brief Type guard, guarantee dict.
!--------------------------------------------------------------------------------------------------
function tNode_asDict(self) result(dict)

  class(tNode), intent(in), target :: self
  class(tDict), pointer            :: dict


  select type(self)
    class is(tDict)
      dict => self
    class default
      nullify(dict)
      call IO_error(706_pI16,trim(self%asFormattedStr()),'is not a dict',emph=[1])
  end select

end function tNode_asDict


!--------------------------------------------------------------------------------------------------
!> @brief Convert to real.
!--------------------------------------------------------------------------------------------------
function tScalar_asReal(self)

  class(tScalar), intent(in), target :: self
  real(pREAL) :: tScalar_asReal


  tScalar_asReal = IO_strAsReal(self%value)

end function tScalar_asReal


!--------------------------------------------------------------------------------------------------
!> @brief Convert to int.
!--------------------------------------------------------------------------------------------------
function tScalar_asInt(self)

  class(tScalar), intent(in), target :: self
  integer :: tScalar_asInt


  tScalar_asInt = IO_strAsInt(self%value)

end function tScalar_asInt


!--------------------------------------------------------------------------------------------------
!> @brief Convert to bool.
!--------------------------------------------------------------------------------------------------
function tScalar_asBool(self)

  class(tScalar), intent(in), target :: self
  logical :: tScalar_asBool


  tScalar_asBool = IO_strAsBool(self%value)

end function tScalar_asBool


!--------------------------------------------------------------------------------------------------
!> @brief Convert to string.
!--------------------------------------------------------------------------------------------------
function tScalar_asStr(self)

  class(tScalar), intent(in), target :: self
  character(len=:), allocatable :: tScalar_asStr


  tScalar_asStr = self%value

end function tScalar_asStr


!--------------------------------------------------------------------------------------------------
!> @brief Format as string (YAML flow style).
!--------------------------------------------------------------------------------------------------
recursive function tList_asFormattedStr(self) result(str)

  class(tList),intent(in),target      :: self

  type(tItem), pointer  :: item
  character(len=:), allocatable :: str
  integer :: i

  str = '['
  item => self%first
  do i = 2, size(self)
    str = str//item%node%asFormattedStr()//', '
    item => item%next
  end do
  str = str//item%node%asFormattedStr()//']'

end function tList_asFormattedStr


!--------------------------------------------------------------------------------------------------
!> @brief Number of elements.
!--------------------------------------------------------------------------------------------------
pure integer function tList_size(list)

  class(tList), intent(in) :: list


  tList_size = list%length

end function tList_size


!--------------------------------------------------------------------------------------------------
!> @brief Append element.
!--------------------------------------------------------------------------------------------------
subroutine tList_append(self,node)

  class(tList), intent(inout)      :: self
  class(tNode), intent(in), target :: node

  type(tItem), pointer :: item


  if (.not. associated(self%first)) then
    allocate(item)
    self%first => item
    self%last => item
  else
    allocate(self%last%next)
    item => self%last%next
    self%last => item
  end if

  item%node => node
  self%length = self%length + 1

end subroutine tList_append


!--------------------------------------------------------------------------------------------------
!> @brief Convert to real array (1D).
!--------------------------------------------------------------------------------------------------
function tList_as1dReal(self)

  class(tList), intent(in), target :: self
  real(pREAL), dimension(:), allocatable :: tList_as1dReal

  integer :: i
  type(tItem),   pointer :: item
  type(tScalar), pointer :: scalar


  allocate(tList_as1dReal(size(self)))
  item => self%first
  do i = 1, size(self)
    scalar => item%node%asScalar()
    tList_as1dReal(i) = scalar%asReal()
    item => item%next
  end do

end function tList_as1dReal


!--------------------------------------------------------------------------------------------------
!> @brief Convert to real array (2D).
!--------------------------------------------------------------------------------------------------
function tList_as2dReal(self)

  class(tList), intent(in), target :: self
  real(pREAL), dimension(:,:), allocatable :: tList_as2dReal

  integer :: i
  type(tList), pointer :: row_data


  row_data => self%get_list(1)
  allocate(tList_as2dReal(size(self),size(row_data)))

  do i = 1, size(self)
    row_data => self%get_list(i)
    if (size(row_data) /= size(tList_as2dReal,2)) call IO_error(709,ext_msg='inconsistent column count in tList_as2dReal')
    tList_as2dReal(i,:) = self%get_as1dReal(i)
  end do

end function tList_as2dReal


!--------------------------------------------------------------------------------------------------
!> @brief Convert to int array (1D).
!--------------------------------------------------------------------------------------------------
function tList_as1dInt(self)

  class(tList), intent(in), target :: self
  integer, dimension(:), allocatable :: tList_as1dInt

  integer :: i
  type(tItem),   pointer :: item
  type(tScalar), pointer :: scalar


  allocate(tList_as1dInt(size(self)))
  item => self%first
  do i = 1, size(self)
    scalar => item%node%asScalar()
    tList_as1dInt(i) = scalar%asInt()
    item => item%next
  end do

end function tList_as1dInt


!--------------------------------------------------------------------------------------------------
!> @brief Convert to bool array (1D).
!--------------------------------------------------------------------------------------------------
function tList_as1dBool(self)

  class(tList), intent(in), target :: self
  logical, dimension(:), allocatable :: tList_as1dBool

  integer :: i
  type(tItem),   pointer :: item
  type(tScalar), pointer :: scalar


  allocate(tList_as1dBool(size(self)))
  item => self%first
  do i = 1, size(self)
    scalar => item%node%asScalar()
    tList_as1dBool(i) = scalar%asBool()
    item => item%next
  end do

end function tList_as1dBool


!--------------------------------------------------------------------------------------------------
!> @brief Convert to string array (1D).
!--------------------------------------------------------------------------------------------------
function tList_as1dStr(self)

  class(tList), intent(in), target :: self
#ifdef __GFORTRAN__
  character(len=pSTRLEN), allocatable, dimension(:) :: tList_as1dStr
#else
  character(len=:), allocatable, dimension(:) :: tList_as1dStr
#endif

  integer :: j
  type(tItem),   pointer :: item
  type(tScalar), pointer :: scalar


#ifdef __GFORTRAN__
  allocate(tList_as1dStr(size(self)))
#else
  integer :: len_max
  len_max = 0
  item => self%first
  do j = 1, size(self)
    scalar => item%node%asScalar()
    len_max = max(len_max, len_trim(scalar%asStr()))
    item => item%next
  end do

  allocate(character(len=len_max) :: tList_as1dStr(size(self)))
#endif
  item => self%first
  do j = 1, size(self)
    scalar => item%node%asScalar()
    tList_as1dStr(j) = scalar%asStr()
    item => item%next
  end do

end function tList_as1dStr


!-------------------------------------------------------------------------------------------------
!> @brief Check for existence of (string) value.
!-------------------------------------------------------------------------------------------------
function tList_contains(self,element) result(exists)

  class(tList),     intent(in), target :: self
  character(len=*), intent(in)         :: element                                                   !< value to search for
  logical                              :: exists

  integer :: j
  type(tItem),   pointer :: item
  type(tScalar), pointer :: scalar


  item => self%first
  exists = .false.
  j = 1
  do while (j <= size(self) .and. .not. exists)
    scalar => item%node%asScalar()
    exists = scalar%value == element
    item => item%next
    j = j + 1
  end do

end function tList_contains


!--------------------------------------------------------------------------------------------------
!> @brief Get by index.
!--------------------------------------------------------------------------------------------------
function tList_get(self,idx) result(node)

  class(tList), intent(in), target :: self
  integer,      intent(in)         :: idx                                                           !< index
  class(tNode), pointer :: node

  type(tItem), pointer :: item
  integer :: i


  if (idx < 1 .or. idx > size(self)) &
    call IO_error(150,ext_msg='tList_get @ '//IO_intAsStr(idx)//' of '//IO_intAsStr(size(self)) )
  item => self%first
  do i = 2, idx
    item => item%next
  end do
  node => item%node

end function tList_get


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by index.
!--------------------------------------------------------------------------------------------------
function tList_get_scalar(self,idx) result(nodeAsScalar)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  type(tScalar), pointer :: nodeAsScalar

  class(tNode),  pointer :: node


  node => self%get(idx)
  nodeAsScalar => node%asScalar()

end function tList_get_scalar


!--------------------------------------------------------------------------------------------------
!> @brief Get list by index.
!--------------------------------------------------------------------------------------------------
function tList_get_list(self,idx) result(nodeAsList)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  type(tList),  pointer :: nodeAsList

  class(tNode),  pointer :: node


  node => self%get(idx)
  nodeAsList => node%asList()

end function tList_get_list


!--------------------------------------------------------------------------------------------------
!> @brief Get dict by index.
!--------------------------------------------------------------------------------------------------
function tList_get_dict(self,idx) result(nodeAsDict)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  type(tDict),  pointer :: nodeAsDict

  class(tNode),  pointer :: node


  node => self%get(idx)
  nodeAsDict => node%asDict()

end function tList_get_dict


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by index and convert to real.
!--------------------------------------------------------------------------------------------------
function tList_get_asReal(self,idx) result(nodeAsReal)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  real(pREAL) :: nodeAsReal

  type(tScalar),   pointer :: scalar


  scalar => self%get_scalar(idx)
  nodeAsReal = scalar%asReal()

end function tList_get_asReal


!--------------------------------------------------------------------------------------------------
!> @brief Get list by index and convert to real array (1D).
!--------------------------------------------------------------------------------------------------
function tList_get_as1dReal(self,idx) result(nodeAs1dReal)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  real(pREAL), dimension(:), allocatable :: nodeAs1dReal

  class(tList),  pointer :: list


  list => self%get_list(idx)
  nodeAs1dReal = list%as1dReal()

end function tList_get_as1dReal


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by index and convert to int.
!--------------------------------------------------------------------------------------------------
function tList_get_asInt(self,idx) result(nodeAsInt)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  integer :: nodeAsInt

  type(tScalar),   pointer :: scalar


  scalar => self%get_scalar(idx)
  nodeAsInt = scalar%asInt()

end function tList_get_asInt


!--------------------------------------------------------------------------------------------------
!> @brief Get list by index and convert to int array (1D).
!--------------------------------------------------------------------------------------------------
function tList_get_as1dInt(self,idx) result(nodeAs1dInt)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  integer, dimension(:), allocatable :: nodeAs1dInt

  class(tList),  pointer :: list


  list => self%get_list(idx)
  nodeAs1dInt = list%as1dInt()

end function tList_get_as1dInt


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by index and convert to bool
!--------------------------------------------------------------------------------------------------
function tList_get_asBool(self,idx) result(nodeAsBool)

  class(tList), intent(in) :: self
  integer,      intent(in) :: idx                                                                   !< index
  logical :: nodeAsBool

  type(tScalar),   pointer :: scalar


  scalar => self%get_scalar(idx)
  nodeAsBool = scalar%asBool()

end function tList_get_asBool


!--------------------------------------------------------------------------------------------------
!> @brief Get list by index and convert to bool array (1D).
!--------------------------------------------------------------------------------------------------
function tList_get_as1dBool(self,i) result(nodeAs1dBool)

  class(tList), intent(in) :: self
  integer,      intent(in) :: i
  logical, dimension(:), allocatable :: nodeAs1dBool

  class(tList),  pointer :: list


  list => self%get_list(i)
  nodeAs1dBool = list%as1dBool()

end function tList_get_as1dBool


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by index and convert to string.
!--------------------------------------------------------------------------------------------------
function tList_get_asStr(self,i) result(nodeAsStr)

  class(tList), intent(in) :: self
  integer,      intent(in) :: i
  character(len=:), allocatable :: nodeAsStr

  type(tScalar),   pointer :: scalar


  scalar => self%get_scalar(i)
  nodeAsStr = scalar%asStr()

end function tList_get_asStr


!--------------------------------------------------------------------------------------------------
!> @brief Get list by index and convert to string array (1D).
!--------------------------------------------------------------------------------------------------
function tList_get_as1dStr(self,i) result(nodeAs1dStr)

  class(tList), intent(in) :: self
  integer,      intent(in) :: i
  character(len=:), allocatable, dimension(:) :: nodeAs1dStr

  type(tList),  pointer :: list


  list => self%get_list(i)
  nodeAs1dStr = list%as1dStr()

end function tList_get_as1dStr


!--------------------------------------------------------------------------------------------------
!> @brief Free associated memory.
!--------------------------------------------------------------------------------------------------
recursive subroutine tList_finalize(self)

  type (tList),intent(inout) :: self

  deallocate(self%first)

end subroutine tList_finalize


!--------------------------------------------------------------------------------------------------
!> @brief Format as string (YAML flow style).
!--------------------------------------------------------------------------------------------------
recursive function tDict_asFormattedStr(self) result(str)

  class(tDict),intent(in),target      :: self

  type(tItem),pointer  :: item
  character(len=:), allocatable :: str
  integer :: i


  str = '{'
  item => self%first
  do i = 2, size(self)
    str = str//trim(item%key)//': '//item%node%asFormattedStr()//', '
    item => item%next
  end do
  str = str//trim(item%key)//': '//item%node%asFormattedStr()//'}'

end function tDict_asFormattedStr


!--------------------------------------------------------------------------------------------------
!> @brief Set value (either replace or add new).
!--------------------------------------------------------------------------------------------------
subroutine tDict_set(self,key,node)

  class (tDict),    intent(inout)         :: self
  character(len=*), intent(in)            :: key
  class(tNode),     intent(in), target    :: node

  type(tItem), pointer :: item


  if (.not. associated(self%first)) then
    allocate(self%first)
    item => self%first
    self%length = 1
  else
    item => self%first
    searchExisting: do while (associated(item%next) .and. item%key /= key)
      item => item%next
    end do searchExisting
    if (item%key /= key) then
      allocate(item%next)
      item => item%next
      self%length = self%length + 1
    end if
  end if

  item%key = key
  item%node => node

end subroutine tDict_set


!--------------------------------------------------------------------------------------------------
!> @brief Return the index of a key.
!--------------------------------------------------------------------------------------------------
function tDict_index(self,key) result(keyIndex)

  class(tDict),     intent(in), target :: self
  character(len=*), intent(in)         :: key

  integer              :: keyIndex
  type(tItem), pointer :: item


  item => self%first
  keyIndex = 1
  do while (associated(item%next) .and. item%key /= key)
    item => item%next
    keyIndex = keyIndex+1
  end do

  if (item%key /= key) &
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif

end function tDict_index


!--------------------------------------------------------------------------------------------------
!> @brief Get key of given index.
!--------------------------------------------------------------------------------------------------
function tDict_key(self,idx) result(key)

  class(tDict),     intent(in), target :: self
  integer,          intent(in)         :: idx                                                       !< index

  character(len=:), allocatable        :: key
  integer              :: i
  type(tItem), pointer :: item


  if (idx < 1 .or. idx > size(self)) &
    call IO_error(150,ext_msg='tDict_key @ '//IO_intAsStr(idx)//' of '//IO_intAsStr(size(self)) )
  item => self%first
  do i = 2, idx
    item => item%next
  end do

  key = item%key

end function tDict_key


!--------------------------------------------------------------------------------------------------
!> @brief Get all keys.
!--------------------------------------------------------------------------------------------------
function tDict_keys(self) result(keys)

  class(tDict), intent(in) :: self
  character(len=:), dimension(:), allocatable :: keys

  character(len=pSTRLEN), dimension(:), allocatable :: temp
  integer :: i, l


  allocate(temp(size(self)))
  l = 0
  do i = 1, size(self)
    temp(i) = self%key(i)
    l = max(len_trim(temp(i)),l)
  end do

  allocate(character(l)::keys(size(self)))
  do i = 1, size(self)
    keys(i) = trim(temp(i))
  end do

end function tDict_keys


!-------------------------------------------------------------------------------------------------
!> @brief Check whether a given key is present.
!-------------------------------------------------------------------------------------------------
function tDict_contains(self,element) result(exists)

  class(tDict),     intent(in), target :: self
  character(len=*), intent(in)         :: element                                                    !< key to check for
  logical                              :: exists

  integer :: j


  exists = .false.
  j = 1
  do while(j <= size(self) .and. .not. exists)
    exists = self%key(j) == element
    j = j + 1
  end do

end function tDict_contains


!--------------------------------------------------------------------------------------------------
!> @brief Get by key.
!--------------------------------------------------------------------------------------------------
function tDict_get(self,key,defaultVal) result(node)

  class(tDict),     intent(in), target         :: self
  character(len=*), intent(in)                 :: key
  class(tNode),     intent(in),optional,target :: defaultVal
  class(tNode),     pointer :: node

  type(tItem), pointer :: item
  integer :: j

  item => self%first

  do j=1, size(self)
    if (item%key == key) then
      node => item%node
      return
    end if
    item => item%next
  end do

  if (present(defaultVal)) then
    node => defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by key.
!--------------------------------------------------------------------------------------------------
function tDict_get_scalar(self,key,defaultVal) result(nodeAsScalar)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  type(tScalar),    intent(in), optional, target :: defaultVal
  type(tScalar), pointer :: nodeAsScalar

  class(tNode),  pointer :: node


  node => self%get(key,defaultVal)
  nodeAsScalar => node%asScalar()

end function tDict_get_scalar


!--------------------------------------------------------------------------------------------------
!> @brief Get list by key.
!--------------------------------------------------------------------------------------------------
function tDict_get_list(self,key,defaultVal) result(nodeAsList)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  type(tList),      intent(in), optional, target :: defaultVal
  type(tList),  pointer :: nodeAsList

  class(tNode), pointer :: node


  node => self%get(key,defaultVal)
  nodeAsList => node%asList()

end function tDict_get_list


!--------------------------------------------------------------------------------------------------
!> @brief Get dict by key.
!--------------------------------------------------------------------------------------------------
function tDict_get_dict(self,key,defaultVal) result(nodeAsDict)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  type(tDict),      intent(in), optional, target :: defaultVal
  type(tDict),  pointer :: nodeAsDict

  class(tNode), pointer :: node


  node => self%get(key,defaultVal)
  nodeAsDict => node%asDict()

end function tDict_get_dict


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by key and convert to real.
!--------------------------------------------------------------------------------------------------
function tDict_get_asReal(self,key,defaultVal) result(nodeAsReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  real(pREAL),      intent(in), optional :: defaultVal
  real(pREAL) :: nodeAsReal

  type(tScalar), pointer :: scalar


  if (self%contains(key)) then
    scalar => self%get_scalar(key)
    nodeAsReal = scalar%asReal()
  elseif (present(defaultVal)) then
    nodeAsReal = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_asReal


!--------------------------------------------------------------------------------------------------
!> @brief Get list by key and convert to real array (1D).
!> @details If a size is required, scalars are valid input and are broadcasted to the required size.
!--------------------------------------------------------------------------------------------------
function tDict_get_as1dReal_sized(self,key,defaultVal,requiredSize) result(nodeAs1dReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  real(pREAL),      intent(in), dimension(:), optional :: defaultVal
  integer,          intent(in),               optional :: requiredSize
  real(pREAL), dimension(:), allocatable :: nodeAs1dReal

  class(tNode), pointer :: content


  if (self%contains(key)) then
    content => self%get(key)
    select type(content)
      class is(tScalar)
        if (present(requiredSize)) then
          allocate(nodeAs1dReal(requiredSize),source = content%asReal())
        else
          call IO_error(706_pI16,trim(content%asFormattedStr()),'is not a list of reals',emph=[1])
        end if
      class is(tList)
        nodeAs1dReal = content%as1dReal()
    end select
  elseif (present(defaultVal)) then
    nodeAs1dReal = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

  if (present(requiredSize)) then
    if (requiredSize /= size(nodeAs1dReal)) &
      call IO_error(146,ext_msg=key, &
                    label1='actual',ID1=size(nodeAs1dReal), &
                    label2='required',ID2=requiredSize)
  end if

end function tDict_get_as1dReal_sized


!--------------------------------------------------------------------------------------------------
!> @brief Get entry by key and convert to real array (1D).
!> @details Values will be broadcasted. A List content can be composed from mixture of scalar
!> or list entries. [2., [1., 3.]] with required chunks [3, 2] gives [2., 2., 2., 1., 3.].
!--------------------------------------------------------------------------------------------------
function tDict_get_as1dReal_chunked(self,key,defaultVal,requiredChunks) result(nodeAs1dReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  real(pREAL),      intent(in), dimension(:), optional :: defaultVal
  integer,          intent(in), dimension(:)           :: requiredChunks
  real(pREAL),                  dimension(sum(requiredChunks)) :: nodeAs1dReal

  type(tList), pointer :: list_outer, list_inner
  class(tNode), pointer :: node_outer, node_inner
  integer :: i


  if (self%contains(key)) then
    node_outer => self%get(key)
    select type(node_outer)
      class is(tScalar)
        nodeAs1dReal = node_outer%asReal()
      class is(tList)
        list_outer => self%get_list(key)
        if (size(list_outer) /= size(requiredChunks)) &
          call IO_error(709,'list "'//list_outer%asFormattedStr()//'" is not of length '//IO_intAsStr(size(requiredChunks)))
        do i = 1, size(requiredChunks)
          node_inner => list_outer%get(i)
          select type(node_inner)
            class is(tScalar)
              nodeAs1dReal(sum(requiredChunks(:i-1))+1:sum(requiredChunks(:i))) = node_inner%asReal()
            class is(tList)
              list_inner => node_inner%asList()
              if (size(list_inner%as1dReal()) /= requiredChunks(i)) &
                  call IO_error(709,'entry "'//key//'" is not of length '//IO_intAsStr(requiredChunks(i)),&
                                'position',i)
              nodeAs1dReal(sum(requiredChunks(:i-1))+1:sum(requiredChunks(:i))) = list_inner%as1dReal()
            class default
              call IO_error(706_pI16,trim(node_inner%asFormattedStr()),'is neither a real nor a list of reals',emph=[1])
          end select
        end do
    end select
  elseif (present(defaultVal)) then
    if (size(defaultVal) == size(nodeAs1dReal)) then
      nodeAs1dReal = defaultVal
    elseif (size(defaultVal) == size(requiredChunks)) then
      do i = 1, size(requiredChunks)
        nodeAs1dReal(sum(requiredChunks(:i-1))+1:sum(requiredChunks(:i))) = defaultVal(i)
      end do
    else
      call IO_error(709,'default values not of required shape')
    end if
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_as1dReal_chunked


#ifndef __GFORTRAN__
!--------------------------------------------------------------------------------------------------
!> @brief Get list of lists by key and convert to real array (2D).
!--------------------------------------------------------------------------------------------------
function tDict_get_as2dReal(self,key,defaultVal,requiredShape) result(nodeAs2dReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  real(pREAL),      intent(in), dimension(:,:), optional :: defaultVal
  integer,          intent(in), dimension(2),   optional :: requiredShape
  real(pREAL), dimension(:,:), allocatable :: nodeAs2dReal

  type(tList), pointer :: list


  if (self%contains(key)) then
    list => self%get_list(key)
    nodeAs2dReal = list%as2dReal()
  elseif (present(defaultVal)) then
    nodeAs2dReal = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

  if (present(requiredShape)) then
    if (any(requiredShape /= shape(nodeAs2dReal))) call IO_error(146,ext_msg=key)
  end if

end function tDict_get_as2dReal

#else
!--------------------------------------------------------------------------------------------------
! Older gfortran versions have issues with optional arguments of size zero.
! it somehow works for the predefined parameters 'emptyXXXArray', but reshaping them to a
! multidimensional array breaks things.
! gfortran 10.x does not work, gfortran 13.x is known to work
!--------------------------------------------------------------------------------------------------

!--------------------------------------------------------------------------------------------------
!> @brief Get list of lists by key and convert to real array (2D).
!--------------------------------------------------------------------------------------------------
function tDict_get_as2dReal_a(self,key,requiredShape) result(nodeAs2dReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  integer,          intent(in), dimension(2),   optional :: requiredShape
  real(pREAL), dimension(:,:), allocatable :: nodeAs2dReal

  type(tList), pointer :: list


  if (self%contains(key)) then
    list => self%get_list(key)
    nodeAs2dReal = list%as2dReal()
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

  if (present(requiredShape)) then
    if (any(requiredShape /= shape(nodeAs2dReal))) call IO_error(146,ext_msg=key)
  end if

end function tDict_get_as2dReal_a


!--------------------------------------------------------------------------------------------------
!> @brief Get list of lists by key and convert to real array (2D).
!--------------------------------------------------------------------------------------------------
function tDict_get_as2dReal_b(self,key,defaultVal,requiredShape) result(nodeAs2dReal)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  real(pREAL),      intent(in), dimension(:,:) :: defaultVal
  integer,          intent(in), dimension(2),  optional :: requiredShape
  real(pREAL), dimension(:,:), allocatable :: nodeAs2dReal

  type(tList), pointer :: list


  if (self%contains(key)) then
    list => self%get_list(key)
    nodeAs2dReal = list%as2dReal()
  else
    nodeAs2dReal = defaultVal
  end if

  if (present(requiredShape)) then
    if (any(requiredShape /= shape(nodeAs2dReal))) call IO_error(146,ext_msg=key)
  end if

end function tDict_get_as2dReal_b
#endif

!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by key and convert to int.
!--------------------------------------------------------------------------------------------------
function tDict_get_asInt(self,key,defaultVal) result(nodeAsInt)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  integer,          intent(in), optional :: defaultVal
  integer :: nodeAsInt

 type(tScalar), pointer :: scalar


  if (self%contains(key)) then
    scalar => self%get_scalar(key)
    nodeAsInt = scalar%asInt()
  elseif (present(defaultVal)) then
    nodeAsInt = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_asInt


!--------------------------------------------------------------------------------------------------
!> @brief Get list by key and convert to int array (1D).
!> @details If a size is required, scalars are valid input and are broadcasted to the required size.
!--------------------------------------------------------------------------------------------------
function tDict_get_as1dInt(self,key,defaultVal,requiredSize) result(nodeAs1dInt)

  class(tDict),          intent(in) :: self
  character(len=*),      intent(in) :: key
  integer, dimension(:), intent(in), optional :: defaultVal
  integer,               intent(in), optional :: requiredSize
  integer, dimension(:), allocatable :: nodeAs1dInt

  class(tNode), pointer :: content


  if (self%contains(key)) then
    content => self%get(key)
    select type(content)
      class is(tScalar)
        if (present(requiredSize)) then
          allocate(nodeAs1dInt(requiredSize),source = content%asInt())
        else
          call IO_error(706_pI16,trim(content%asFormattedStr()),'is not a list of integers',emph=[1])
        end if
      class is(tList)
        nodeAs1dInt = content%as1dInt()
    end select
  elseif (present(defaultVal)) then
    nodeAs1dInt = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

  if (present(requiredSize)) then
    if (requiredSize /= size(nodeAs1dInt)) &
      call IO_error(146,ext_msg=key, &
                    label1='actual',ID1=size(nodeAs1dInt), &
                    label2='required',ID2=requiredSize)
  end if

end function tDict_get_as1dInt


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by key and convert to bool.
!--------------------------------------------------------------------------------------------------
function tDict_get_asBool(self,key,defaultVal) result(nodeAsBool)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  logical,          intent(in), optional :: defaultVal
  logical :: nodeAsBool

  type(tScalar), pointer :: scalar


  if (self%contains(key)) then
    scalar => self%get_scalar(key)
    nodeAsBool = scalar%asBool()
  elseif (present(defaultVal)) then
    nodeAsBool = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_asBool


!--------------------------------------------------------------------------------------------------
!> @brief Get list by key and convert to bool array (1D).
!--------------------------------------------------------------------------------------------------
function tDict_get_as1dBool(self,key,defaultVal) result(nodeAs1dBool)

  class(tDict),          intent(in) :: self
  character(len=*),      intent(in) :: key
  logical, dimension(:), intent(in), optional :: defaultVal
  logical, dimension(:), allocatable          :: nodeAs1dBool

  type(tList), pointer :: list


  if (self%contains(key)) then
    list => self%get_list(key)
    nodeAs1dBool = list%as1dBool()
  elseif (present(defaultVal)) then
    nodeAs1dBool = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_as1dBool


!--------------------------------------------------------------------------------------------------
!> @brief Get scalar by key and convert to string.
!--------------------------------------------------------------------------------------------------
function tDict_get_asStr(self,key,defaultVal) result(nodeAsStr)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  character(len=*), intent(in), optional :: defaultVal
  character(len=:), allocatable :: nodeAsStr

  type(tScalar), pointer :: scalar


  if (self%contains(key)) then
    scalar => self%get_scalar(key)
    nodeAsStr = scalar%asStr()
  elseif (present(defaultVal)) then
    nodeAsStr = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_asStr


!--------------------------------------------------------------------------------------------------
!> @brief Get list by key and convert to string array (1D).
!--------------------------------------------------------------------------------------------------
function tDict_get_as1dStr(self,key,defaultVal) result(nodeAs1dStr)

  class(tDict),     intent(in) :: self
  character(len=*), intent(in) :: key
  character(len=*), intent(in), dimension(:), optional :: defaultVal
  character(len=:), allocatable, dimension(:)          :: nodeAs1dStr

  type(tList), pointer :: list


  if (self%contains(key)) then
    list => self%get_list(key)
    nodeAs1dStr = list%as1dStr()
  elseif (present(defaultVal)) then
    nodeAs1dStr = defaultVal
  else
#ifndef __GFORTRAN__
    call IO_error(143_pI16,key,'not in',join_str_array(self%keys()),emph=[1,3])
#else
    call IO_error(143_pI16,key,'not found',emph=[1])
#endif
  end if

end function tDict_get_as1dStr


#ifdef __GFORTRAN__
!--------------------------------------------------------------------------------------------------
!> @brief Returns string output array (1D) (hack for GNU).
!--------------------------------------------------------------------------------------------------
function output_as1dStr(self) result(output)

  class(tDict), pointer,intent(in)   ::  self
  character(len=pSTRLEN), allocatable, dimension(:) :: output

  type(tList), pointer :: output_list
  integer :: o


  output_list => self%get_list('output',defaultVal=emptyList)
  allocate(output(size(output_list)))
  do o = 1, size(output_list)
    output(o) = output_list%get_asStr(o)
  end do

end function output_as1dStr
#endif


!--------------------------------------------------------------------------------------------------
!> @brief Free associated memory.
!--------------------------------------------------------------------------------------------------
recursive subroutine tItem_finalize(self)

  type(tItem),intent(inout) :: self


  deallocate(self%node)
  if (associated(self%next)) deallocate(self%next)

end subroutine tItem_finalize


!--------------------------------------------------------------------------------------------------
!> @brief Join strings from array.
!> @details Separated by comma, enclosed by square brackets.
!--------------------------------------------------------------------------------------------------
pure function join_str_array(array) result(joined)

  character(len=*), dimension(:), intent(in) :: array
  character(len=:), allocatable :: joined

  integer :: i


  joined = '['
  if (size(array) > 0) joined = joined//trim(array(1))
  do i = 2, size(array)
    joined = joined//', '//trim(array(i))
  end do
  joined = joined//']'

end function join_str_array

end module types
