!===================================================================================================================================
! Copyright (c) 2025 GVEC Contributors, Max Planck Institute for Plasma Physics
! License: MIT
!===================================================================================================================================

!===================================================================================
!>
!!# Module **gvec_to_gene_c_bind**
!!
!!
!!
!===================================================================================

module modgvec_gvec_to_gene_c_bind

  use modgvec_gvec_to_gene, only: &
       init_gvec_to_gene, &
       gvec_to_gene_coords, &
       finalize_gvec_to_gene

  use modgvec_globals, only: WP

  use modgvec_output_vtk, only: WriteDataToVTK

  use, intrinsic :: iso_c_binding, only : C_INT, C_FLOAT, C_DOUBLE, C_LONG_DOUBLE, &
       C_CHAR, C_NULL_CHAR, C_PTR, c_f_pointer

  implicit none
  private

  ! select a proper C working precision (CWP), default C_FLOAT
  ! adjust this parameters according to modgvec_globals
  integer, parameter :: CWP = &
       merge(C_LONG_DOUBLE, &
       merge(C_DOUBLE, C_FLOAT, &
       WP .eq. selected_real_kind(15,307)), &
       WP .eq. selected_real_kind(33,307))

  public init_gvec_to_gene_c, gvec_to_gene_coords_c, finalize_gvec_to_gene_c, &
       test_print_file_name_c, test_pass_arrays_shift_c, test_int_array_c

contains

  subroutine write_data_to_vtk_c( &
       dim1,vecDim,nVal,NPlot,nElems,strlen,VarNames_c,Coord,Values,FileString_c) &
       bind(c,name='write_data_to_vtk')
    ! INPUT VARIABLES
    ! dimension of the data (either 2=quads or 3=hexas)
    INTEGER(kind=C_INT), value :: dim1
    INTEGER(kind=C_INT), value :: vecdim ! dimension of coordinates
    INTEGER(kind=C_INT), value :: nVal ! Number of nodal output variables
    ! Number of output points per element: (nPlot+1)**dim1
    INTEGER(kind=C_INT),INTENT(IN) :: NPlot(dim1)
    INTEGER(kind=C_INT), value :: nElems ! Number of output elements
    ! CoordinatesVector
    REAL(kind=CWP),INTENT(IN) :: Coord(vecdim,1:PRODUCT(Nplot+1),nElems)
    integer(kind=C_INT), value :: strlen ! lenght of strings in VarNames_c
    ! Names of all variables that will be written out
    character(kind=C_CHAR,len=1), intent(in) :: VarNames_c(strlen,nVal)
    REAL(kind=CWP),INTENT(IN) :: Values(nVal,1:PRODUCT(Nplot+1),nElems) ! Statevector
    ! Output file name
    character(kind=C_CHAR, len=1), dimension(*) :: FileString_c

    character(len=strlen) :: VarNames_f(nVal)
    character(len=strlen) :: VarName
    character(len=:), allocatable :: FileString_f
    integer :: val, nchar1, nchar2

    nchar1 = get_c_string_length(FileString_c)

    if(nchar1.gt.256) stop "c string length is restricted to 256"

    ! retrieve file name into Fortran string
    call c_to_f_string(FileString_c, FileString_f)

    ! save all var names into VarNames_f
    do val = 1, nVal
      nchar2 = get_c_string_length(VarNames_c(:,val))
      if(nchar2.gt.strlen) stop "c string length is restricted to strlen"
      VarName = get_fortran_string(VarNames_c(:,val),nchar2)
      VarNames_f(val) = trim(VarName(1:nchar2))
    end do

    call WriteDataToVTK( &
         dim1,vecDim,nVal,NPlot,nElems,VarNames_f,Coord,Values,FileString_f)

  end subroutine write_data_to_vtk_c

  subroutine init_gvec_to_gene_c(fileName) bind(c,name='init_gvec_to_gene')
    character(kind=C_CHAR, len=1), dimension(*) :: fileName
    character(len=:), allocatable :: fileName_f
    integer :: nchar

    nchar = get_c_string_length(fileName)
    if(nchar.gt.256) stop "c string length is restricted to 256"
    call c_to_f_string(fileName, fileName_f)
    call init_gvec_to_gene(fileName_f(1:nchar))

  end subroutine init_gvec_to_gene_c

  subroutine gvec_to_gene_coords_c( &
       nthet, nzeta, spos_in, theta_star_in, &
       zeta_in, theta_out, cart_coords) &
       bind(c,name='gvec_to_gene_coords')
    integer(kind=C_INT), value :: nthet, nzeta
    real(kind=CWP), value :: spos_in
    real(kind=CWP), dimension(nthet,nzeta), intent(in) :: theta_star_in, zeta_in
    real(kind=CWP), intent(out) :: theta_out(nthet,nzeta)
    real(kind=CWP), intent(out) :: cart_coords(3,nthet,nzeta)

    call gvec_to_gene_coords( &
         nthet, nzeta, &
         spos_in, theta_star_in, zeta_in, &
         theta_out, cart_coords)

  end subroutine gvec_to_gene_coords_c

  subroutine finalize_gvec_to_gene_c() bind(c,name='finalize_gvec_to_gene')
    call finalize_gvec_to_gene()
  end subroutine finalize_gvec_to_gene_c

  !===================================================================================

  subroutine test_int_array_c(dim1,Nplot) &
       bind(c,name='test_int_array')

    INTEGER(kind=C_INT), value :: dim1
    INTEGER(kind=C_INT),INTENT(IN) :: NPlot(dim1)

    write(*,*) Nplot, "Nplot"
    write(*,*) "product", PRODUCT(Nplot+1)

  end subroutine test_int_array_c

subroutine test_print_char_rank2_array_c(strlen,nval,varnames_c) &
       bind(c,name='test_print_char_rank2_array')
    integer(kind=C_INT), value, intent(in) :: nval, strlen
    character(kind=C_CHAR,len=1), intent(in) :: varnames_c(strlen,nval)

    character(len=strlen) :: VarNames_f(nval)
    character(len=strlen) :: varname
    integer(kind=C_INT) :: val
    integer :: nchar

    write(*,*) "test_print_char_rank2_array nval: ", nval
    do val = 1, nval
      nchar = get_c_string_length(varnames_c(:,val))
      if(nchar.gt.strlen) stop "c string length is restricted to strlen"
      varname = get_fortran_string(varnames_c(:,val),nchar)
      VarNames_f(val) = trim(varname(1:nchar))
      write(*,*), val, ": VarNames_f: ", VarNames_f(val)
    end do

  end subroutine test_print_char_rank2_array_c

  subroutine test_print_file_name_c(fileName) bind(c,name='test_print_file_name')
    character(kind=C_CHAR, len=1), dimension(*) :: fileName

    character(len=256) :: fileName_f
    integer :: nchar

    nchar = get_c_string_length(fileName)

    if(nchar.gt.256) stop "c string length is restricted to 256"

    fileName_f = get_fortran_string(fileName,nchar)

    write(*,*) "Tests: ", fileName_f(1:nchar)
  end subroutine test_print_file_name_c

  subroutine test_pass_arrays_shift_c( &
       nthet, nzeta, arr_in, arr_out) &
       bind(c, name = "test_pass_arrays_shift")
    integer(kind=C_INT), value :: nthet, nzeta
    real(kind=CWP),intent(in) :: arr_in(nthet,nzeta)
    real(kind=CWP),intent(out) :: arr_out(nthet,nzeta)

    call simple_shift(arr_in, arr_out, nthet*nzeta)

  contains
    subroutine simple_shift(a_in, a_out, size)
      integer(kind=C_INT), intent(in) :: size
      real(kind=CWP),intent(in) :: a_in(size)
      real(kind=CWP),intent(out) :: a_out(size)
      integer :: i
      forall(i=1:size) a_out(i) = a_in(i) + i
    end subroutine simple_shift
  end subroutine test_pass_arrays_shift_c

  !===================================================================================

  function get_c_string_length(s) result(nchars)
    character(kind=C_CHAR, len=1), intent(in) :: s(*)
    integer :: nchars

    nchars = 0
    do while( s(nchars+1).ne.C_NULL_CHAR )
      nchars = nchars + 1
    end do
  end function get_c_string_length

  function get_fortran_string(s,nchars) result(f)
    character(kind=C_CHAR, len=1), intent(in) :: s(*)
    integer, intent(in) :: nchars
    character(len=nchars) :: f

    if( storage_size(f).eq.storage_size(s)*nchars) then
      f = transfer(s(1:nchars), f)
    else
      stop "can't transfer C_CHAR array to fortran character, do explicit copy!"
    end if
  end function get_fortran_string

  subroutine c_to_f_string(s,f)
    character(kind=C_CHAR, len=1), intent(in) :: s(*)
    character(len=:), allocatable, intent(out) :: f
    integer :: nchars

    nchars = 0
    do while( s(nchars+1).ne.C_NULL_CHAR )
       nchars = nchars + 1
    end do

    allocate(character(len=nchars) :: f)
    if( storage_size(f).eq.storage_size(s)*nchars) then
       f = transfer(s(1:nchars), f)
    else
       stop "can't transfer C_CHAR array to fortran character, do explicit copy!"
    end if
  end subroutine c_to_f_string

end module modgvec_gvec_to_gene_c_bind
