!=============================================================================
! expression parser utility --
!   for parsing simple linear mathematical expressions of the form
!   X = a*Y + b*Z + ...
!
!=============================================================================
module shr_expr_parser_mod
  use shr_kind_mod,only : r8 => shr_kind_r8
  use shr_kind_mod,only : cx => shr_kind_cx

  implicit none
  private

  public :: shr_exp_parse     ! parses simple strings which contain expressions
  public :: shr_exp_item_t    ! user defined type which contains an expression component
  public :: shr_exp_list_destroy ! destroy the linked list returned by shr_exp_parse

  ! contains componets of expression
  type shr_exp_item_t
     character(len=64) :: name
     character(len=64),pointer :: vars(:) => null()
     real(r8)         ,pointer :: coeffs(:) => null()
     integer           :: n_terms = 0
     type(shr_exp_item_t), pointer :: next_item => null()
  end type shr_exp_item_t

contains

  ! -----------------------------------------------------------------
  ! parses expressions provided in array of strings
  ! -----------------------------------------------------------------
  function shr_exp_parse( exp_array, nitems ) result(exp_items_list)

    character(len=*), intent(in)   :: exp_array(:) ! contains a expressions
    integer, optional, intent(out) :: nitems       ! number of expressions parsed
    type(shr_exp_item_t), pointer  :: exp_items_list ! linked list of items returned

    integer :: i,j, jj, nmax, nterms, n_exp_items
    character(len=cx) :: tmp_str
    type(shr_exp_item_t), pointer :: exp_item, list_item

    nullify( exp_items_list )
    nullify( exp_item )
    nullify( list_item )

    n_exp_items = 0
    nmax = size( exp_array )

    do i = 1,nmax
       if (len_trim(exp_array(i))>0) then

          j = scan( exp_array(i), '=' )

          if ( j>0 ) then

             n_exp_items = n_exp_items + 1

             allocate( exp_item )
             exp_item%n_terms = 0
             exp_item%name = trim(adjustl(exp_array(i)(:j-1)))

             tmp_str = trim(adjustl(exp_array(i)(j+1:)))

             nterms = 1
             jj = scan( tmp_str, '+' )
             do while(jj>0)
                nterms = nterms + 1
                tmp_str = tmp_str(jj+1:)
                jj = scan( tmp_str, '+' )
             enddo

             allocate( exp_item%vars(nterms) )
             allocate( exp_item%coeffs(nterms) )

             tmp_str = trim(adjustl(exp_array(i)(j+1:)))

             j = scan( tmp_str, '+' )

             if (j>0) then
                call set_coefvar( tmp_str(:j-1), exp_item )
                tmp_str = tmp_str(j-1:)
             else
                call set_coefvar( tmp_str, exp_item )
             endif

          else

             tmp_str = trim(adjustl(exp_array(i))) ! assumed to begin with '+'

          endif

          ! at this point tmp_str begins with '+'
          j = scan( tmp_str, '+' )

          if (j>0) then

             ! remove the leading + ...
             tmp_str = tmp_str(j+1:)
             j = scan( tmp_str, '+' )

             do while(j>0)

                call set_coefvar( tmp_str(:j-1), exp_item )

                tmp_str = tmp_str(j+1:)
                j = scan( tmp_str, '+' )

             enddo

             call set_coefvar( tmp_str, exp_item )

          endif


          if (associated(exp_item)) then
             if (associated(exp_items_list)) then
                list_item => exp_items_list
                do while(associated(list_item%next_item))
                   list_item => list_item%next_item
                enddo
                list_item%next_item => exp_item
             else
                exp_items_list => exp_item
             endif
          endif

       endif
    enddo

    if ( present(nitems) ) then
       nitems = n_exp_items
    endif

  end function shr_exp_parse

  ! -----------------------------------------------------------------
  ! deallocates memory occupied by linked list
  ! -----------------------------------------------------------------
  subroutine  shr_exp_list_destroy( list )
    type(shr_exp_item_t), pointer, intent(inout) :: list

    type(shr_exp_item_t), pointer :: item, next

    item => list
    do while(associated(item))
       next => item%next_item
       if (associated(item%vars)) then
          deallocate(item%vars)
          nullify(item%vars)
          deallocate(item%coeffs)
          nullify(item%coeffs)
       endif
       deallocate(item)
       nullify(item)
       item => next
    enddo

  end subroutine  shr_exp_list_destroy

  !==========================
  ! Private Methods

  ! -----------------------------------------------------------------
  ! -----------------------------------------------------------------
  subroutine set_coefvar( term, item )
    character(len=*), intent(in)  :: term
    type(shr_exp_item_t) , intent(inout) :: item

    integer :: k, n

    item%n_terms = item%n_terms + 1
    n = item%n_terms

    k = scan( term, '*' )
    if (k>0) then
       item%vars(n) = trim(adjustl(term(k+1:)))
       read( term(:k-1), *) item%coeffs(n)
    else
       item%vars(n) = trim(adjustl(term))
       item%coeffs(n) = 1.0_r8
    endif

  end subroutine set_coefvar

end module shr_expr_parser_mod
