! See copyright notice in the COPYRIGHT file.
! ***************************************************************************** !
!> In this module, all parameter files are read in 
!! as lua script or a sample configuration is being loaded
!!
!! Possible Parameter configuration
!!
!! - General Parameters
!! - [[mus_scheme_layout_module]] for Scheme Definitions
!! - [[tem_debug_module]] for Debug Parameters
!!
module mus_config_module

  ! include musubi modules
  use mpi
  use mus_param_module,         only: mus_param_type, mus_load_param
  use mus_geom_module,          only: mus_geom_type, mus_build_posInProp
  use mus_scheme_type_module,   only: mus_scheme_type
  use mus_scheme_module,        only: mus_load_scheme
  use mus_geomIncrHead_module,  only: mus_geomIncrHead_load
  use mus_physics_module,       only: mus_create_funcStr, mus_load_physics
  use mus_varSys_module,        only: mus_varSys_solverData_type
  use mus_timer_module,         only: mus_timerHandles
  use mus_tools_module,         only: dump_linear_partition

  ! include treelm modules
  use env_module,               only: pathLen, rk, long_k, globalMaxLevels
  use treelmesh_module,         only: load_tem
  use tem_global_module,        only: tem_global_mesh_read
  use tem_aux_module,           only: tem_open_distconf_array, tem_abort
  use tem_property_module,      only: prp_hasBnd, prp_hasQVal
  use tem_bc_prop_module,       only: init_tem_bc_prop, &
    &                                 load_tem_bc_qVal
  use tem_restart_module,       only: tem_load_restart
  use tem_timer_module,         only: tem_startTimer, &
    &                                 tem_stopTimer
  use tem_tools_module,         only: tem_horizontalSpacer
  use tem_geometry_module,      only: tem_setEffBoundingBox,                   &
    &                                 tem_build_treeToProp_pointer
  use tem_general_module,       only: tem_load_general
  use tem_logging_module,       only: logUnit, tem_logging_load_primary
  use tem_comm_env_module,      only: tem_comm_env_type
  use tem_timeControl_module,   only: tem_timeControl_start_at_sim
  use tem_debug_module,         only: dbgUnit, tem_debug_load_main

  use tem_adaptation_config_module, only: tem_adapt_type, tem_load_adapt

  ! include aotus modules
  use aotus_module,    only: flu_State, open_config_chunk

  implicit none

  private

  public :: mus_load_config
  public :: mus_open_config
  public :: mus_load_bc_data

contains

! ****************************************************************************** !
  !> Read in LUA parameter file 
  !! See http://www.lua.org for a reference on how to use
  !! Lua is a scripting language in itself which allows 
  !! more complex parameter files including comments 
  !! And load / create the mesh depending on the configuration
  subroutine mus_load_config( scheme, solverData, geometry, params, adapt )
    ! ---------------------------------------------------------------------------
    !> scheme type
    type( mus_scheme_type ), target :: scheme
    !> contains pointer to scheme, physics types
    type( mus_varSys_solverData_type ), target :: solverData
    !> Treelmesh data
    type( mus_geom_type ), intent(out), target :: geometry
    !> Global parameters
    type( mus_param_type ), target, intent(inout) :: params
    !> mesh adaptation
    type(tem_adapt_type), intent(inout) :: adapt
    ! ---------------------------------------------------------------------------
    character(len=PathLen) :: filename
    integer :: iLevel
    integer :: minLevel, maxLevel
    real(kind=rk) :: level_weights(globalMaxLevels)
    ! ---------------------------------------------------------------------------

    call tem_startTimer( timerHandle = mus_timerHandles%loadMesh )

    ! check whether params%general%solver%configFile is defined. When loading 
    ! musubi from apes, musubi config file is provided via params
    if ( trim(params%general%solver%configFile) == '' ) then
      ! Get filename from command line argument
      call get_command_argument( 1,filename )
      if( trim(filename) == '' ) then
        filename = 'musubi.lua'
      end if

      params%general%solver%configFile = filename
    else
      filename = params%general%solver%configFile
    endif

    if (params%general%proc%rank == 0) then
      write(logUnit(1),*) "Loading configuration file: "//trim( filename )
      call tem_horizontalSpacer(fUnit = logUnit(1))
    end if

    ! open musubi config file and solver specific lua functions as chunk
    call mus_open_config( conf     = params%general%solver%conf, &
      &                   filename = filename,                   &
      &                   proc     = params%general%proc         )

    ! load and initialize logUnit
    call tem_logging_load_primary(conf = params%general%solver%conf(1), &
      &                           rank = params%general%proc%rank       )

    ! load and initialize debug unit
    call tem_debug_load_main(conf = params%general%solver%conf(1), &
      &                      rank = params%general%proc%rank       )

    ! load general information
    call tem_load_general( me   = params%general,               &
      &                    conf = params%general%solver%conf(1) )

    ! load global musubi params
    call mus_load_param( params = params,                       &
      &                  conf   = params%general%solver%conf(1) )

    ! -------------------------------------------------------------------------
    !  Load mesh                                                             !
    ! -------------------------------------------------------------------------
    ! First check, if we are starting from a restart
    ! KJ: Now the restart is read from initial conditions, which are loaded
    ! much later than the config in the process flow
    call tem_load_restart( me       = params%general%restart,        &
      &                    conf     = params%general%solver%conf(1), &
      &                    tree     = geometry%tree,                 &
      &                    timing   = params%general%simControl%now, &
      &                    globProc = params%general%proc            )

    if( .not. params%general%restart%controller%readRestart ) then

      write(logUnit(1),*) 'No read restart given. Loading mesh file'

      ! First load global info
      call tem_global_mesh_read( geometry%tree%global, &
        &                        params%general%solver%conf(1), &
        &                        params%general%proc%rank,      &
        &                        params%general%proc%comm_size, &
        &                        params%general%proc%comm )

      minLevel = geometry%tree%global%minLevel
      maxLevel = geometry%tree%global%maxLevel

      if( (minLevel /= maxLevel) .and. params%initial_balance ) then

        call tem_horizontalSpacer(fUnit = logUnit(1))
        write(logUnit(1),"(A)") 'Do initial level-wise load balancing.'

        ! Calculate level wise weights to scaleFactor^( level - minlevel )
        !                 acoustic      diffusive
        !  ------------------------------------------------
        !  fac =         |   2              4
        !  ------------------------------------------------
        !  minLevel      |   1              1
        !  minLevel + 1  |   2              4
        !  minLevel + 2  |   4              16
        !  ...
        !  ------------------------------------------------
        do iLevel = minLevel, maxLevel
          level_weights( iLevel ) = dble( params%scaleFactor ** (iLevel-minLevel) )
        end do

        call load_tem( me      = geometry%tree,                   &
          &            conf    = params%general%solver%conf(1),   &
          &            myPart  = params%general%proc%rank,        &
          &            nParts  = params%general%proc%comm_size,   &
          &            comm    = params%general%proc%comm,        &
          &            levelWeight = level_weights,               &
          &            meshDir = params%general%solver%meshFolder )
      else
        ! load the tree from the mesh = '' definition in case no restartRead is
        ! given in this case the mesh from the restart header is read (in tem_load_restart)
        call load_tem( me      = geometry%tree,                   &
          &            conf    = params%general%solver%conf(1),   &
          &            myPart  = params%general%proc%rank,        &
          &            nParts  = params%general%proc%comm_size,   &
          &            comm    = params%general%proc%comm,        &
          &            meshDir = params%general%solver%meshFolder )
      end if ! minLevel /= maxLevel .and. initial_balance
    else
      minLevel = geometry%tree%global%minLevel
      maxLevel = geometry%tree%global%maxLevel

      ! If there is a restart, the timings in the params type have to be
      ! updated to those read from the restart
      call tem_timeControl_start_at_sim(                           &
        &             me = params%general%simControl%timeControl,  &
        &             now = params%general%simControl%now )
    end if
    ! CODE BLOCK ABOUT RESETTING PARAMS TIME ETC

    ! calculate the effective origin and effective length
    ! KM :@todo get effective bounding box from seeder header
    call tem_setEffBoundingBox( geometry%tree )

    if ( params%dump_linear_partition ) then
      call dump_linear_partition( treeID = geometry%tree%treeID, &
        &                         nElems = geometry%tree%nElems, &
        &                         offset = geometry%tree%elemOffset, &
        &                         myRank = params%general%proc%rank, &
        &                         iter   = 0 )
    end if
    ! ---------------------------------------------------------------------------
    ! Done loading mesh.                                                       !
    ! ---------------------------------------------------------------------------

    ! Load boundary and qval
    call mus_load_bc_data( geometry, params%general%proc%rank, &
      &                              params%general%proc%comm  )

    ! Initialize requiredInterval for multilevel to determine one complete cycle
    params%reqInterval = params%scaleFactor**(maxLevel-minLevel)

    ! load physics table for unit converstion
    call mus_load_physics( me          = params%physics,                &
      &                    conf        = params%general%solver%conf(1), &
      &                    tree        = geometry%tree,                 &
      &                    scaleFactor = params%scaleFactor             )

    ! The scheme information is loaded from the configuration file by invoking
    ! the function. THIS IS REQUIRED EVEN IN THE CASE OF DYNAMIC LOAD BALANCING
    ! DUE TO PRESENCE OF DERIVED VARIABLE LISTS BUILDING INSIDE THE LOAD SCHEME
    ! SINGLE ROUTINE
    call mus_load_scheme( me         = scheme,                        &
      &                   solverData = solverData,                    &
      &                   geometry   = geometry,                      &
      &                   conf       = params%general%solver%conf(1), &
      &                   params     = params )

    call tem_horizontalSpacer(fUnit = logUnit(1))

    ! load adaptation table
    call tem_load_adapt( me   = adapt,                        &
      &                  conf = params%general%solver%conf(1) )

    ! Load solidification/fluidification settings
    call mus_geomIncrHead_load( me          = geometry%geomIncr,             &
      &                         conf        = params%general%solver%conf(1), &
      &                         dynamicGeom = geometry%dynamicGeom           )

    call tem_stopTimer( timerHandle = mus_timerHandles%loadMesh )

  end subroutine mus_load_config
! ****************************************************************************** !


! ****************************************************************************** !
  !> This routine invokes the treelm routines to load the boundary conditions
  !!
  subroutine mus_load_bc_data( geometry, rank, comm )
    ! --------------------------------------------------------------------------
    type( mus_geom_type ),  intent(inout)  :: geometry !< Treelmesh data
    integer, intent(in) :: rank, comm
    ! --------------------------------------------------------------------------
    integer :: iProp
    ! --------------------------------------------------------------------------

    ! ----------------------Load the boundary conditions------------------------
    ! Boundary conditions are loaded even in the case of dynamic load balancing
    ! when the restart is done at requisite load balancing intervals
    call init_tem_bc_prop( geometry%tree, rank, &
      &                    comm, geometry%boundary )

    do iProp = 1, geometry%tree%global%nProperties
      if( geometry%tree%global%property( iProp )%nElems > 0 ) then
        select case( geometry%tree%global%property( iProp )%bitpos )
        case( prp_hasBnd )
          ! Already loaded
        case( prp_hasQVal )
          ! Load qVal from disk
          ! prp_hasQVal is the 2nd property in mesh
          write(logUnit(1),"(A)") 'Loading qVal data from directory: '&
            &                     //trim(geometry%tree%global%dirname)
          call load_tem_BC_qVal(                                               &
            &           me       = geometry%boundary,                          &
            &           offset   = geometry%tree%Property(iProp)%Offset,       &
            &           nElems   = geometry%tree%Property(iProp)%nElems,       &
            &           basename = trim(geometry%tree%global%dirname)//'qval', &
            &           mypart   = rank,                   &
            &           comm     = comm                    )
          write(logUnit(1),*)'Done, reading the qVal!'

        end select ! property( iProp )%bitpos
      endif ! property( iProp )%nElems > 0
    enddo ! iProp

    call mus_build_posInProp( geometry )

    ! when qVal exist, it is allocated inside load_tem_BC_qVal
    ! otherwise allocate it with size 0
    if ( .not. allocated( geometry%boundary%qVal ) ) then
      allocate( geometry%boundary%qVAl(0,0) )
    end if

  end subroutine mus_load_bc_data
! ****************************************************************************** !


! ****************************************************************************** !
  !> This routine loads musubi specific lua function from string and musubi
  !! input configuration file
  subroutine mus_open_config( conf, filename, proc )
    ! ---------------------------------------------------------------------------
    !> lua state to be stored
    type(flu_State), allocatable :: conf(:)
    !> name of the config file to be opened
    character(len=*), intent(in) :: filename
    !> process description to use
    type(tem_comm_env_type), intent(in) :: proc
    ! ---------------------------------------------------------------------------
    character(len=2048) :: fun_str !contains function to be used in musubi 
    integer :: iThread
    ! ---------------------------------------------------------------------------

    ! allocate the array of lua states
    allocate( conf( proc%nThreads ))

    call mus_create_funcStr( fun_str = fun_str )

    !pre-loading musubi specific lua functions using same conf used to load
    !main musubi config file
    do iThread=1, proc%nThreads
      call open_config_chunk(L = conf(iThread), chunk = fun_str) 
    end do

    ! Open the lua config
    call tem_open_distconf_array( L        = conf,                             &
      &                           filename = trim(filename),                   &
      &                           proc     = proc )

  end subroutine mus_open_config
! ****************************************************************************** !

end module mus_config_module
! ****************************************************************************** !

