#include "obl_def.fi"

program obl_main
    !< @brief ocean boundary layer (OBL) main
    ! --------------------------------------------------------------------------------

#ifdef USE_CONFIG_PARSER
    use iso_c_binding, only: C_NULL_CHAR
    use config_parser
#endif

    ! modules used
    ! --------------------------------------------------------------------------------
    use obl_grid
    use obl_state
    use obl_state_eq
    use obl_common_phys
    use obl_tforcing
    use obl_tslice
    use obl_tseries
    use obl_output
    use obl_init
    use obl_fluxes
    use obl_scm
    use obl_config
    use obl_diag
    use obl_bc
    use obl_pph, only: pphParamType, &
        define_stability_functions_pph => define_stability_functions, & 
        advance_turbulence_closure_pph => advance_turbulence_closure
    use obl_pph_dyn, only: pphDynParamType, &
        define_stability_functions_pph_dyn => define_stability_functions, & 
        advance_turbulence_closure_pph_dyn => advance_turbulence_closure
    use obl_k_epsilon, only: kepsilonParamType, &
        define_stability_functions_k_epsilon => define_stability_functions, & 
        advance_turbulence_closure_k_epsilon => advance_turbulence_closure, &
        TKE_init, EPS_init, TKE_bc, EPS_bc
    use obl_io_plt
    use io
    use io_metadata
 
    ! directives list
    ! --------------------------------------------------------------------------------
    implicit none

    ! model data
    ! --------------------------------------------------------------------------------
    integer :: obl_setup    ! --- OBL builtin setup 
                            ! = setup_kato: Kato-Phillips
                            ! = setup_papa_fluxes: Papa-station (fluxes)
                            ! = setup_papa_meteo: Papa-station (meteo)

    integer :: closure_mode     ! --- OBL closure def.
                                ! = 1 - pacanowski-philander
                                ! = 2 - pacanowski-philander dynamic 
                                ! = 3 - k-epsilon (explicit)
                                ! = 4 - k-epsilon (semiimplicit)
                                ! = 5 - inmom

    type(gridDataType) :: grid

    !< turbulence closure parameters
    type(pphParamType) :: param_pph
    type(pphDynParamType) :: param_pph_dyn
    type(kepsilonParamType) :: param_k_epsilon

    !< boundary conditions data
    type(oblBcType) :: bc

    !< output 
    type(oblOutputStx) :: scm_output

    !< time
    real :: time_begin, time_end, time_current
    real :: dt
    integer :: time_index

    !< screen output parameters
    integer, parameter :: nscreen = 1000

    !< file output parameters
    integer :: output_mode      ! --- OBL output mode
                                ! = 1 -- netcdf
                                ! = 2 -- ascii
                                ! = 3 -- tecplot

    integer, parameter :: noutput = 60

    !< additional variables & parameters
    real, parameter :: Cd0 = 0.001      ! bottom drag coefficient

    real :: mld, mld_ref                    ! mixed layer depth (model & reference), [m]
    real, parameter :: N2_ref = 0.00044     ! reference N**2 (using in mld ref. estimate), [1/s**2]
    
    ! command line arguments & configuration file variables
    ! --------------------------------------------------------------------------------
    integer :: num_args
    character(len = 128) :: arg

    character(len = 128), parameter :: arg_key_help = '--help'
    character(len = 128), parameter :: arg_key_setup = '--setup'
    character(len = 128), parameter :: arg_key_config = "--config"

    integer :: i, status
    integer :: ierr
    ! --------------------------------------------------------------------------------


    !< default setup = setup_kato (Kato-Phiilps)
    !< possible values:
    !<      setup_kato
    !<      setup_papa_fluxes
    !<      setup_papa_meteo
    obl_setup = setup_kato

    !< default closure = 4, k-epsilon (implicit)
    !< poissible values:
    !<      = 1, Pacanowski-Philander
    !<      = 2, Pacanowski-Philander (dynamic)
    !<      = 3, k-epsilon (explicit) *: DEPRECATED
    !<      = 4, k-epsilon (implicit)
    closure_mode = 4

    !< default output = 1, (netcdf)
    !< possible values:
    !<      = 1, netcdf
    !<      = 2, ascii
    !<      = 3, tecplot
    output_mode = 3

    ! --- command line arguments processing
    num_args = command_argument_count()
    do i = 1, num_args

        call get_command_argument(i, arg)
        if (trim(arg) == trim(arg_key_help)) then
            write(*, *) ' obl model, usage:'
            write(*, *) ' --help'
            write(*, *) '    print usage options'
            write(*, *) ' --setup [key]'
            write(*, *) '    key = kato (default) || papa-fluxes || papa'
            write(*, *) ' --config [filename]'
            write(*, *) '    use configuration file'
            return
        end if

        if (trim(arg) == trim(arg_key_setup)) then
            if (i == num_args) then
                write(*, *) ' FAILURE! > missing setup [key] argument'
                ierr = 1        ! signal ERROR
                return
            end if
            
            call get_command_argument(i + 1, arg)
            obl_setup = get_setup_id(arg)
            if (obl_setup == -1) then
                write(*, *) ' FAILURE! > unknown setup [key]: ', trim(arg)
                ierr = 1        ! signal ERROR
                return
            end if
        else if (trim(arg) == trim(arg_key_config)) then
            if (i == num_args) then
                write(*, *) ' FAILURE! > missing configuration file [key] argument'
                ierr = 1        ! signal ERROR
                return
            end if
        
            call get_command_argument(i + 1, arg)
#ifdef USE_CONFIG_PARSER
            call c_config_run(trim(arg)//C_NULL_CHAR, status)
            if (status == 0) then
                write(*, *) ' FAILURE! > unable to parse configuration file: ', trim(arg)
                ierr = 1        ! signal ERROR
                return
            end if

            !< forcing configuration file setup
            obl_setup = 999
#endif
        endif
    enddo

    !< setting grid
    ! ---------------------------------------------------------------------------- 
    call set_grid(grid, obl_setup, ierr)
    if (ierr /= 0) then
        return
    endif 

    !< debug grid print
    call print_grid(grid)
    ! ---------------------------------------------------------------------------- 

    !< setting model time
    ! ----------------------------------------------------------------------------  
    call set_time(time_begin, time_end, dt, obl_setup, ierr)
    if (ierr /= 0) then
        return
    endif 
    time_current = time_begin
    time_index = 1
    ! ----------------------------------------------------------------------------  

    !< allocating state vector
    ! ----------------------------------------------------------------------------  
    call allocate_state_vec(grid%cz)
    ! ----------------------------------------------------------------------------  

    !< initialize scm
    ! ----------------------------------------------------------------------------  
    call init_scm_vec(grid%cz)
    ! ----------------------------------------------------------------------------  

    !< setting phys
    ! ----------------------------------------------------------------------------
    call set_phys(obl_setup, ierr)
    if (ierr /= 0) then
        return
    endif 
    ! ----------------------------------------------------------------------------

    !< setting forcing
    ! ---------------------------------------------------------------------------- 
    call set_forcing(obl_setup, ierr)
    if (ierr /= 0) then
        return
    endif 
    ! ----------------------------------------------------------------------------

    !< initialization of main fields
    ! ---------------------------------------------------------------------------- 
    call set_initial_conditions(grid, obl_setup, ierr)
    if (ierr /= 0) then
        return
    endif

    Theta_dev = Theta - Theta_ref
    Salin_dev = Salin - Salin_ref
    call get_density(Rho, Theta_dev, Salin_dev, grid%cz)
    ! ----------------------------------------------------------------------------

    !< initialization of turbulence closure
    ! ----------------------------------------------------------------------------  
    if (closure_mode.eq.3 .or. closure_mode.eq.4) then
        call TKE_init(TKE, param_k_epsilon, grid%cz)
        call eps_init(EPS, param_k_epsilon, grid%cz, grid%height)
    endif
    ! ---------------------------------------------------------------------------- 

    do while (time_current < time_end )
        ! ----------------------------------------------------------------------------
        
        !< define fluxes & dynamic scales [surface]
        ! ----------------------------------------------------------------------------
        call advance_surface_fluxes(bc, time_current, grid)
        ! ----------------------------------------------------------------------------

        !< define fluxes & dynamic scales [bottom]
        ! ----------------------------------------------------------------------------
        call advance_bottom_fluxes(bc, time_current, grid)
        ! ----------------------------------------------------------------------------

        !< advance turbulence closure
        ! ----------------------------------------------------------------------------
        if (closure_mode.eq.1) then
            call define_stability_functions_pph(param_pph, bc, grid)
            call advance_turbulence_closure_pph(param_pph, bc, grid, dt)
        else if (closure_mode.eq.2) then
            call define_stability_functions_pph_dyn(param_pph_dyn, bc, grid)
            call advance_turbulence_closure_pph_dyn(param_pph_dyn, bc, grid, dt)
        else if (closure_mode.eq.4) then
            call define_stability_functions_k_epsilon(param_k_epsilon, bc, grid)
            call advance_turbulence_closure_k_epsilon(param_k_epsilon, bc, grid, dt)
        endif
        ! ----------------------------------------------------------------------------

        !< advance single-column model eq.
        ! ----------------------------------------------------------------------------
        call advance_scm_eq(bc, grid, dt)
        ! ----------------------------------------------------------------------------

        !> advance time
        ! ----------------------------------------------------------------------------
        time_index = time_index + 1
        time_current = time_current + dt
        ! ----------------------------------------------------------------------------

        !> advance screen output
        ! ----------------------------------------------------------------------------
        if (mod(time_index, nscreen) == 0) then
            call get_mld(mld, N2, grid%dz, grid%cz)

            ! *: add finite check

            write(*, '(a,g0)') ' mld = ', mld
            write(*, '(a,g0)') ' Theta(surface) = ', Theta_dev(grid%cz) + Theta_ref
            write(*, '(a,g0,a,g0,a)') ' current time = ', time_current / 3600.0, ' HRS [ ', & 
                (time_current / time_end) * 100.0, '% ]' 
            write(*, '(a)') '-------------------------------------------------'
        endif
        ! ----------------------------------------------------------------------------

        !> advance file output
        ! ----------------------------------------------------------------------------
        if (mod(time_index, noutput) == 0) then
            call push_state_vec(scm_output, grid%cz)

            call get_mld(mld, N2, grid%dz, grid%cz)
            call get_mld_ref(mld_ref, bc%U_dynH, N2_ref, time_current, grid%height)

            call push_value_to_tseries(scm_output%mld, mld)
            call push_value_to_tseries(scm_output%mld_ref, mld_ref)

            call push_value_to_tseries(scm_output%tau_x, bc%u_momentum_fluxH * Rho_ref)
            call push_value_to_tseries(scm_output%tau_y, bc%v_momentum_fluxH * Rho_ref)

            call push_value_to_tseries(scm_output%Theta_surf, Theta_dev(grid%cz) + Theta_ref)

            call push_value_to_tseries(scm_output%time, time_current / 3600.0)
        endif
        ! ----------------------------------------------------------------------------
    enddo

    !> writing file output
    ! ----------------------------------------------------------------------------
    if (output_mode.eq.1) then
        write(*, *) ' >> writing netcdf output ...'
        call write_netcdf(scm_output, grid%z)
    endif

    if (output_mode.eq.2) then
        write(*, *) ' >> writing ascii output ...'
        call write_ascii(scm_output, grid%z)
    endif


    if (output_mode.eq.3) then
        write(*, *) ' >> writing tecplot output ...'
        call write_tecplot(scm_output, grid%z)
    endif
    ! ----------------------------------------------------------------------------

    !> model cleanup
    ! ----------------------------------------------------------------------------
    !> deallocate state
    call deallocate_state_vec

    !> deallocate scm
    call deallocate_scm_vec

    !> deallocate time-dependent forcing
    call deallocate_fluxes_data
    
    !> removing time slice data
    call output_cleanup(scm_output)

    ! > removing grid data
    call deallocate_grid(grid)
    ! ----------------------------------------------------------------------------

end program
