! SUMMA - Structure for Unifying Multiple Modeling Alternatives
! Copyright (C) 2014-2020 NCAR/RAL; University of Saskatchewan; University of Washington
!
! This file is part of SUMMA
!
! For more information see: http://www.ral.ucar.edu/projects/summa
!
! This program is free software: you can redistribute it and/or modify
! it under the terms of the GNU 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 General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with this program.  If not, see <http://www.gnu.org/licenses/>.

module checkStruc_module
USE nrtype
USE globalData,only:integerMissing
implicit none
private
public::checkStruc
contains


 ! ************************************************************************************************
 ! public subroutine checkStruc: check data structures
 ! ************************************************************************************************
 subroutine checkStruc(err,message)
 ! ascii utilities
 USE ascii_util_module,only:split_line
 ! summary of data structures
 USE globalData,only:structInfo
 ! metadata structures
 USE globalData,only:time_meta,forc_meta,attr_meta,type_meta,id_meta  ! metadata structures
 USE globalData,only:prog_meta,diag_meta,flux_meta,deriv_meta         ! metadata structures
 USE globalData,only:mpar_meta,indx_meta                              ! metadata structures
 USE globalData,only:bpar_meta,bvar_meta                              ! metadata structures
 ! named variables defining strructure elements
 USE var_lookup,only:iLookTIME,iLookFORCE,iLookATTR,iLookTYPE,iLookID ! named variables showing the elements of each data structure
 USE var_lookup,only:iLookPROG,iLookDIAG,iLookFLUX,iLookDERIV         ! named variables showing the elements of each data structure
 USE var_lookup,only:iLookPARAM,iLookINDEX                            ! named variables showing the elements of each data structure
 USE var_lookup,only:iLookBPAR,iLookBVAR                              ! named variables showing the elements of each data structure
 implicit none
 ! dummy variables
 integer(i4b),intent(out)             :: err         ! error code
 character(*),intent(out)             :: message     ! error message
 ! local variables
 integer(i4b)                         :: iStruct     ! index of data structure
 integer(i4b),parameter               :: nStruct=size(structInfo)  ! number of data structures
 character(len=8192)                  :: longString  ! string containing the indices defined in the structure constructor
 character(len=32),allocatable        :: words(:)    ! vector of words extracted from the long string
 integer(i4b)                         :: ix          ! index of the variable in the data structure
 integer(i4b)                         :: ixTest      ! test the structure constructor = (1,2,3,...,nVar)
 character(len=256)                   :: cmessage    ! error message of downwind routine
 ! -----------------------------------------------------------------------------------------------------------------------------------
 ! initialize errors
 err=0; message="checkStruc/"

 ! -----
 ! * check that the structure constructors are correct...
 ! ------------------------------------------------------

 ! loop through data structures
 do iStruct=1,nStruct
  ! convert the lookup structures to a character string
  ! expect the lookup structures to be a vector (1,2,3,...,n)
  select case(trim(structInfo(iStruct)%structName))
   case('time');  write(longString,*) iLookTIME
   case('forc');  write(longString,*) iLookFORCE
   case('attr');  write(longString,*) iLookATTR
   case('type');  write(longString,*) iLookTYPE
   case('id');    write(longString,*) iLookID
   case('mpar');  write(longString,*) iLookPARAM
   case('bpar');  write(longString,*) iLookBPAR
   case('bvar');  write(longString,*) iLookBVAR
   case('indx');  write(longString,*) iLookINDEX
   case('prog');  write(longString,*) iLookPROG
   case('diag');  write(longString,*) iLookDIAG
   case('flux');  write(longString,*) iLookFLUX
   case('deriv'); write(longString,*) iLookDERIV
   case default; err=20; message=trim(message)//'unable to identify lookup structure'; return
  end select
  ! check that the length of the lookup structure matches the number of variables in the data structure
  call split_line(longString,words,err,cmessage) ! convert the long character string to a vector of "words"
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if
  if(size(words)/=structInfo(iStruct)%nVar)then; err=20; message=trim(message)//'unexpected number of elements'; return; end if
  ! check that the elements in the lookup structure are sequential integers (1,2,3,...,n)
  do ix=1,structInfo(iStruct)%nVar
   read(words(ix),*) ixTest  ! convert character to integer; store in ixTest
   if(ixTest/=ix)then ! expect that the ix-th word is equal to ix
    write(message,'(a,i0,a)')trim(message)//'problem with structure constructor iLook'//trim(structInfo(iStruct)%lookName)//' [element=',ix,']'
    err=20; return
   end if
  end do
 end do  ! looping through data structures

 ! -----
 ! * check that the metadata is fully populated...
 ! -----------------------------------------------
 ! loop through data structures
 do iStruct=1,nStruct
  ! check that the metadata is fully populated
  select case(trim(structInfo(iStruct)%structName))
   case('time');  call checkPopulated(iStruct,time_meta,err,cmessage)
   case('forc');  call checkPopulated(iStruct,forc_meta,err,cmessage)
   case('attr');  call checkPopulated(iStruct,attr_meta,err,cmessage)
   case('type');  call checkPopulated(iStruct,type_meta,err,cmessage)
   case('id');    call checkPopulated(iStruct,id_meta,  err,cmessage)
   case('mpar');  call checkPopulated(iStruct,mpar_meta,err,cmessage)
   case('bpar');  call checkPopulated(iStruct,bpar_meta,err,cmessage)
   case('bvar');  call checkPopulated(iStruct,bvar_meta,err,cmessage)
   case('indx');  call checkPopulated(iStruct,indx_meta,err,cmessage)
   case('prog');  call checkPopulated(iStruct,prog_meta,err,cmessage)
   case('diag');  call checkPopulated(iStruct,diag_meta,err,cmessage)
   case('flux');  call checkPopulated(iStruct,flux_meta,err,cmessage)
   case('deriv'); call checkPopulated(iStruct,deriv_meta,err,cmessage)
   case default; err=20; message=trim(message)//'unable to identify lookup structure'; return
  end select
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
 end do  ! looping through data structures

 contains

  ! ************************************************************************************************
  ! internal subroutine checkPopulated: check that the metadata is fully populated...
  ! ************************************************************************************************
  subroutine checkPopulated(iStruct,metadata,err,message)
  ! access the data type for the metadata structures
  USE data_types,only:var_info
  ! get index from character string
  USE get_ixname_module,only: get_ixUnknown! variable lookup structure
  implicit none
  ! dummy variables
  integer(i4b),intent(in)   :: iStruct     ! index of data structure
  type(var_info)            :: metadata(:) ! metadata structure
  integer(i4b),intent(out)  :: err         ! error code
  character(*),intent(out)  :: message     ! error message
  ! local variables
  integer(i4b)              :: iVar        ! index of variable within a data structure
  integer(i4b)              :: jVar        ! index of variable within a data structure (returned from the variable name)
  character(LEN=100)        :: typeName    ! name of variable type to be returned by get_ixUnknown
  character(len=256)        :: cmessage    ! error message of downwind routine
  ! initialize error control
  err=0; message='checkPopulated/'

  ! loop through variables
  do iVar=1,size(metadata)

 
   ! check that this variable is populated
   if (trim(metadata(iVar)%varname)=='empty') then
    write(message,'(a,i0,a)') trim(message)//trim(structInfo(iStruct)%structName)//'_meta structure is not populated for named variable # ',iVar, ' in structure iLook'//trim(structInfo(iStruct)%lookName)
    err=20; return
   end if

   ! look for the populated variable
   call get_ixUnknown(trim(metadata(iVar)%varname),typeName,jVar,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
 
   ! check that the variable was found at all
   if (jVar==integerMissing) then
    message = trim(message)//'cannot find variable '//trim(metadata(iVar)%varname)//' in structure '//trim(structInfo(iStruct)%structName)//'_meta; you need to add variable to get_ix'//trim(structInfo(iStruct)%structName)
    err=20; return
   end if

   ! check that the variable was found in the correct structure
   if (trim(structInfo(iStruct)%structName)/=typeName) then
    message=trim(message)//'variable '//trim(metadata(iVar)%varname)//' from structure '//trim(structInfo(iStruct)%structName)//'_meta is in structure '//trim(typeName)//'_meta'
    err=20; return
   end if

   ! check that the variable index is correct
   ! This can occur because (1) the code in popMetadat is corrupt (e.g., mis-match in look-up variable); or (2) var_lookup is corrupt.
   if (jVar/=iVar) then
    write(message,'(a,i0,a,i0,a)') trim(message)//'variable '//trim(metadata(iVar)%varname)//' has index ', iVar, ' (expect index ', jVar, '); problem possible in popMetadat, get_ix'//trim(structInfo(iStruct)%structName)//', or var_lookup'
    err=20; return
   end if

  end do  ! looping through variables in structure iStruct

  end subroutine checkPopulated

 end subroutine checkStruc

end module checkStruc_module