!
!  Copyright 2019 Yuta Hirokawa (University of Tsukuba)
!
!  Licensed under the Apache License, Version 2.0 (the "License");
!  you may not use this file except in compliance with the License.
!  You may obtain a copy of the License at
!
!      http://www.apache.org/licenses/LICENSE-2.0
!
!  Unless required by applicable law or agreed to in writing, software
!  distributed under the License is distributed on an "AS IS" BASIS,
!  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
!  See the License for the specific language governing permissions and
!  limitations under the License.
!
module cbslf
  use, intrinsic :: iso_c_binding
  implicit none

  type cbslf_context
    integer(c_intptr_t) :: ctx
  end type
  public :: cbslf_context

  public :: cbslf_store_mode
  public :: cbslf_load_mode
  public :: cbslf_unknown_mode

  public :: cbslf_success
  public :: cbslf_error

  public :: cbslf_open
  public :: cbslf_close
  public :: cbslf_flush

  public :: cbslf_write
  public :: cbslf_read
  public :: cbslf_record
  public :: cbslf_record_heap

  public :: cbslf_get_mode
  public :: cbslf_set_compression_level
  public :: cbslf_get_compression_level

#define PASTE(a,b) a##b
#define CONCAT(a,b) PASTE(a,b)

#define SET_ERRCODE(CODE) \
        if (present(errcode)) errcode = CODE

#define SET_ERRCODE_COND(COND) \
        if (present(errcode)) then \
__NL__    if (COND) then \
__NL__      errcode = cbslf_success \
__NL__    else \
__NL__      errcode = cbslf_error \
__NL__    end if \
__NL__  end if

#define FORTRAN_ARRAY_1       :
#define FORTRAN_ARRAY_2       :,:
#define FORTRAN_ARRAY_3       :,:,:
#define FORTRAN_ARRAY_4       :,:,:,:
#define FORTRAN_ARRAY_5       :,:,:,:,:
#define FORTRAN_ARRAY_6       :,:,:,:,:,:
#define FORTRAN_ARRAY_7       :,:,:,:,:,:,:

#define FORTRAN_ARRAY_VALUE_1 1
#define FORTRAN_ARRAY_VALUE_2 1,1
#define FORTRAN_ARRAY_VALUE_3 1,1,1
#define FORTRAN_ARRAY_VALUE_4 1,1,1,1
#define FORTRAN_ARRAY_VALUE_5 1,1,1,1,1
#define FORTRAN_ARRAY_VALUE_6 1,1,1,1,1,1
#define FORTRAN_ARRAY_VALUE_7 1,1,1,1,1,1,1

#define FORTRAN_ARRAY_SHAPE_1 lb(1):ub(1)
#define FORTRAN_ARRAY_SHAPE_2 lb(1):ub(1),lb(2):ub(2)
#define FORTRAN_ARRAY_SHAPE_3 lb(1):ub(1),lb(2):ub(2),lb(3):ub(3)
#define FORTRAN_ARRAY_SHAPE_4 lb(1):ub(1),lb(2):ub(2),lb(3):ub(3),lb(4):ub(4)
#define FORTRAN_ARRAY_SHAPE_5 lb(1):ub(1),lb(2):ub(2),lb(3):ub(3),lb(4):ub(4),lb(5):ub(5)
#define FORTRAN_ARRAY_SHAPE_6 lb(1):ub(1),lb(2):ub(2),lb(3):ub(3),lb(4):ub(4),lb(5):ub(5),lb(6):ub(6)
#define FORTRAN_ARRAY_SHAPE_7 lb(1):ub(1),lb(2):ub(2),lb(3):ub(3),lb(4):ub(4),lb(5):ub(5),lb(6):ub(6),lb(7):ub(7)

#define DECLARE_SCALAR_INTERFACE(SAVE_LOAD,ROUTINE_NAME)  \
          module procedure CONCAT(CONCAT(SAVE_LOAD,_scalar_),ROUTINE_NAME)

#define DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,DIMS)  \
          module procedure CONCAT(CONCAT(SAVE_LOAD,CONCAT(_array_,DIMS)),CONCAT(_,ROUTINE_NAME))

#define DECLARE_ARRAY_INTERFACE(SAVE_LOAD,ROUTINE_NAME) \
          DECLARE_SCALAR_INTERFACE(SAVE_LOAD,ROUTINE_NAME) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,1) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,2) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,3) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,4) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,5) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,6) \
__NL__    DECLARE_ARRAY_INTERFACE_N(SAVE_LOAD,ROUTINE_NAME,7) \

#define DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,DIMS)  \
          module procedure CONCAT(CONCAT(SAVE_LOAD,CONCAT(_array_heap_,DIMS)),CONCAT(_,ROUTINE_NAME))

#define DECLARE_ARRAY_INTERFACE_ALLOCATABLE(SAVE_LOAD,ROUTINE_NAME) \
          DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,1) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,2) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,3) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,4) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,5) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,6) \
__NL__    DECLARE_ARRAY_INTERFACE_ALLOCATABLE_N(SAVE_LOAD,ROUTINE_NAME,7)

#define IMPLEMENT_SCALAR_INTERFACE(ROUTINE_NAME,DATA_TYPE)  \
          subroutine CONCAT(write_scalar_,ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, target, intent(in) :: val \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(c_int64_t) :: psize \
__NL__      integer(4) :: ret \
__NL__      psize = c_sizeof(val) \
__NL__      ret = cbsl_write(ctx%ctx, loc(val), psize) \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine \
__NL__    subroutine CONCAT(read_scalar_,ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, target, intent(out) :: val \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(c_int64_t) :: psize \
__NL__      integer(4) :: ret \
__NL__      psize = c_sizeof(val) \
__NL__      ret = cbsl_read(ctx%ctx, loc(val), psize) \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine \
__NL__    subroutine CONCAT(record_scalar_,ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, target, intent(inout) :: val \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(c_int64_t) :: psize \
__NL__      integer(4) :: ret \
__NL__      psize = c_sizeof(val) \
__NL__      ret = cbsl_record(ctx%ctx, loc(val), psize) \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine

#define IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,DIMS)  \
__NL__    subroutine CONCAT(CONCAT(write_array_,CONCAT(DIMS,_)),ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof, c_int64_t \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, intent(in) :: val(CONCAT(FORTRAN_ARRAY_,DIMS)) \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(4) :: lb(DIMS),ub(DIMS),nsize,i \
__NL__      integer(c_int64_t) :: bsize \
__NL__      integer(4) :: ret \
__NL__      nsize   = size(val, kind=kind(nsize)) \
__NL__      do i=1,size(lb) \
__NL__        lb(i) = lbound(val, i, kind(lb(i))) \
__NL__        ub(i) = ubound(val, i, kind(ub(i))) \
__NL__      end do \
__NL__      call cbslf_write(ctx, nsize, ret) \
__NL__      bsize = size(lb) * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__      ret   = iand(cbsl_write(ctx%ctx, loc(lb), bsize), ret) \
__NL__      ret   = iand(cbsl_write(ctx%ctx, loc(ub), bsize), ret) \
__NL__      bsize = nsize * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__      ret   = iand(cbsl_write(ctx%ctx, loc(val), bsize), ret) \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine \
__NL__    subroutine CONCAT(CONCAT(read_array_,CONCAT(DIMS,_)),ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof, c_int64_t \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, intent(out) :: val(CONCAT(FORTRAN_ARRAY_,DIMS)) \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(4) :: lb(DIMS),ub(DIMS),nsize \
__NL__      integer(c_int64_t) :: bsize \
__NL__      integer(4) :: ret \
__NL__      ret = cbslf_error \
__NL__      if (size(val) > 0) then \
__NL__        call cbslf_read(ctx, nsize, ret) \
__NL__        if (ret == cbslf_success .and. nsize > 0) then \
__NL__          bsize = size(lb) * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__          ret   = iand(cbsl_read(ctx%ctx, loc(lb), bsize), ret) \
__NL__          ret   = iand(cbsl_read(ctx%ctx, loc(ub), bsize), ret) \
__NL__          bsize = nsize * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__          ret   = iand(cbsl_read(ctx%ctx, loc(val), bsize), ret) \
__NL__        else \
__NL__          ret = cbslf_error \
__NL__        end if \
__NL__      end if \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine \
__NL__    subroutine CONCAT(CONCAT(record_array_,CONCAT(DIMS,_)),ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof, c_int64_t \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, allocatable, intent(inout) :: val(CONCAT(FORTRAN_ARRAY_,DIMS)) \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(4) :: mode,ret \
__NL__      mode = cbslf_get_mode(ctx) \
__NL__      select case(mode) \
__NL__        case(cbslf_store_mode) \
__NL__          call cbslf_write(ctx, val, ret) \
__NL__        case(cbslf_load_mode) \
__NL__          call cbslf_read(ctx, val, ret) \
__NL__        case default \
__NL__          ret = cbslf_error \
__NL__      end select \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine \
__NL__    subroutine CONCAT(CONCAT(record_array_heap_,CONCAT(DIMS,_)),ROUTINE_NAME)(ctx, val, errcode) \
__NL__      use, intrinsic :: iso_c_binding, only: c_sizeof, c_int64_t \
__NL__      implicit none \
__NL__      type(cbslf_context), intent(in) :: ctx \
__NL__      DATA_TYPE, allocatable, intent(inout) :: val(CONCAT(FORTRAN_ARRAY_,DIMS)) \
__NL__      integer(4), intent(out), optional :: errcode \
__NL__      integer(4) :: lb(DIMS),ub(DIMS),nsize,mode \
__NL__      integer(4) :: ret \
__NL__      integer(c_int64_t) :: bsize \
__NL__      mode = cbslf_get_mode(ctx) \
__NL__      select case(mode) \
__NL__        case(cbslf_store_mode) \
__NL__          call cbslf_write(ctx, val, ret) \
__NL__        case(cbslf_load_mode) \
__NL__          if (allocated(val)) then \
__NL__            call cbslf_read(ctx, val, ret) \
__NL__          else \
__NL__            call cbslf_read(ctx, nsize, ret) \
__NL__            if (ret == cbslf_success .and. nsize > 0) then \
__NL__              bsize = size(lb) * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__              ret   = iand(cbsl_read(ctx%ctx, loc(lb), bsize), ret) \
__NL__              ret   = iand(cbsl_read(ctx%ctx, loc(ub), bsize), ret) \
__NL__              allocate(val(CONCAT(FORTRAN_ARRAY_SHAPE_,DIMS))) \
__NL__              bsize = nsize * c_sizeof(val(CONCAT(FORTRAN_ARRAY_VALUE_,DIMS))) \
__NL__              ret   = iand(cbsl_read(ctx%ctx, loc(val), bsize), ret) \
__NL__            else \
__NL__              ret = cbslf_error \
__NL__            end if \
__NL__          end if \
__NL__        case default \
__NL__          ret = cbslf_error \
__NL__      end select \
__NL__      SET_ERRCODE(ret) \
__NL__    end subroutine

#define IMPLEMENT_ARRAY_INTERFACE(ROUTINE_NAME,DATA_TYPE)  \
          IMPLEMENT_SCALAR_INTERFACE(ROUTINE_NAME,DATA_TYPE) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,1) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,2) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,3) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,4) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,5) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,6) \
          IMPLEMENT_ARRAY_INTERFACE_N(ROUTINE_NAME,DATA_TYPE,7)

!======
private

  integer(8),   parameter :: CBSL_VERSION         = @cbsl_VERSION_MAJOR@@cbsl_VERSION_MINOR@@cbsl_VERSION_PATCH@
  character(*), parameter :: CBSL_VERSTION_STRING = '@cbsl_VERSION@'
  integer(4),   parameter :: CBSL_MAJOR_VERSION   = @cbsl_VERSION_MAJOR@
  integer(4),   parameter :: CBSL_MINOR_VERSION   = @cbsl_VERSION_MINOR@
  integer(4),   parameter :: CBSL_PATCH_VERSION   = @cbsl_VERSION_PATCH@

  integer(4), parameter :: cbslf_load_mode    = 1
  integer(4), parameter :: cbslf_store_mode   = 2
  integer(4), parameter :: cbslf_unknown_mode = -1

  integer(4), parameter :: cbslf_success = 0
  integer(4), parameter :: cbslf_error   = -1

  ! C function interfaces
  interface
    function cbsl_open(open_mode, path) result(ctx) &
        bind(C,name='cbsl_open')
      use, intrinsic :: iso_c_binding
      integer(c_int),value,  intent(in) :: open_mode
      character(kind=c_char),intent(in) :: path(*)
      integer(c_intptr_t) :: ctx
    end function

    function cbsl_close(ctx) result(retcode) &
        bind(C,name='cbsl_close')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_int) :: retcode
    end function

    function cbsl_flush(ctx) result(retcode) &
        bind(C,name='cbsl_flush')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_int) :: retcode
    end function

    function cbsl_get_mode(ctx) result(retcode) &
        bind(C,name='cbsl_get_mode')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_int) :: retcode
    end function

    function cbsl_write(ctx, pdata, psize) result(retcode) &
        bind(C,name='cbsl_write')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_intptr_t),value,intent(in) :: pdata
      integer(c_int64_t),value,intent(in)  :: psize
      integer(c_int) :: retcode
    end function

    function cbsl_read(ctx, pdata, psize) result(retcode) &
        bind(C,name='cbsl_read')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_intptr_t),value,intent(in) :: pdata
      integer(c_int64_t),value,intent(in)  :: psize
      integer(c_int) :: retcode
    end function

    function cbsl_record(ctx, pdata, psize) result(retcode) &
        bind(C,name='cbsl_record')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_intptr_t),value,intent(in) :: pdata
      integer(c_int64_t),value,intent(in)  :: psize
      integer(c_int) :: retcode
    end function

    ! cbsl_record_heap() not binds because the function uses malloc()

    function cbsl_set_compression_level(ctx,clevel) result(retcode) &
        bind(C,name='cbsl_set_compression_level')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_int),value,intent(in) :: clevel
      integer(c_int) :: retcode
    end function

    function cbsl_get_compression_level(ctx) result(clevel) &
        bind(C,name='cbsl_get_compression_level')
      use, intrinsic :: iso_c_binding
      integer(c_intptr_t),value,intent(in) :: ctx
      integer(c_int) :: clevel
    end function
  end interface

  ! Generic routines
  interface cbslf_write
    DECLARE_SCALAR_INTERFACE(write,character)
    DECLARE_SCALAR_INTERFACE(write,logical)

    DECLARE_ARRAY_INTERFACE(write,integer4)
    DECLARE_ARRAY_INTERFACE(write,integer8)
    DECLARE_ARRAY_INTERFACE(write,sreal)
    DECLARE_ARRAY_INTERFACE(write,dreal)
    DECLARE_ARRAY_INTERFACE(write,ccomplex)
    DECLARE_ARRAY_INTERFACE(write,zcomplex)
  end interface

  interface cbslf_read
    DECLARE_SCALAR_INTERFACE(read,character)
    DECLARE_SCALAR_INTERFACE(read,logical)

    DECLARE_ARRAY_INTERFACE(read,integer4)
    DECLARE_ARRAY_INTERFACE(read,integer8)
    DECLARE_ARRAY_INTERFACE(read,sreal)
    DECLARE_ARRAY_INTERFACE(read,dreal)
    DECLARE_ARRAY_INTERFACE(read,ccomplex)
    DECLARE_ARRAY_INTERFACE(read,zcomplex)
  end interface

  interface cbslf_record
    DECLARE_SCALAR_INTERFACE(record,character)
    DECLARE_SCALAR_INTERFACE(record,logical)

    DECLARE_ARRAY_INTERFACE(record,integer4)
    DECLARE_ARRAY_INTERFACE(record,integer8)
    DECLARE_ARRAY_INTERFACE(record,sreal)
    DECLARE_ARRAY_INTERFACE(record,dreal)
    DECLARE_ARRAY_INTERFACE(record,ccomplex)
    DECLARE_ARRAY_INTERFACE(record,zcomplex)
  end interface

  interface cbslf_record_heap
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,integer4)
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,integer8)
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,sreal)
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,dreal)
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,ccomplex)
    DECLARE_ARRAY_INTERFACE_ALLOCATABLE(record,zcomplex)
  end interface

contains
  IMPLEMENT_SCALAR_INTERFACE(character,character(*,kind=c_char))
  IMPLEMENT_SCALAR_INTERFACE(logical,logical(c_bool))

  IMPLEMENT_ARRAY_INTERFACE(integer4,integer(c_int32_t))
  IMPLEMENT_ARRAY_INTERFACE(integer8,integer(c_int64_t))
  IMPLEMENT_ARRAY_INTERFACE(sreal,real(c_float))
  IMPLEMENT_ARRAY_INTERFACE(dreal,real(c_double))
  IMPLEMENT_ARRAY_INTERFACE(ccomplex,complex(c_float_complex))
  IMPLEMENT_ARRAY_INTERFACE(zcomplex,complex(c_double_complex))

  function cbslf_open(open_mode, path, errcode) result(ctx)
    implicit none
    integer(4),   intent(in)            :: open_mode
    character(*), intent(in)            :: path
    integer(4),   intent(out), optional :: errcode
    type(cbslf_context) :: ctx
    character(1,c_char)  :: cpath(len_trim(path)+1)
    integer(4) :: n,i
    n = len_trim(path, kind=4)
    do i=1,n
      cpath(i) = path(i:i)
    end do
    cpath(n+1) = c_null_char
    ctx%ctx = cbsl_open(open_mode, cpath)
    SET_ERRCODE_COND(ctx%ctx /= 0) ! is not null pointer
  end function

  subroutine cbslf_close(ctx, errcode)
    implicit none
    type(cbslf_context), intent(in)            :: ctx
    integer(4),          intent(out), optional :: errcode
    integer(4) :: ret
    ret = cbsl_close(ctx%ctx)
    SET_ERRCODE(ret)
  end subroutine

  subroutine cbslf_flush(ctx, errcode)
    implicit none
    type(cbslf_context), intent(in)            :: ctx
    integer(4),          intent(out), optional :: errcode
    integer(4) :: ret
    ret = cbsl_flush(ctx%ctx)
    SET_ERRCODE(ret)
  end subroutine

  function cbslf_get_mode(ctx, errcode) result(open_mode)
    implicit none
    type(cbslf_context), intent(in)            :: ctx
    integer(4),          intent(out), optional :: errcode
    integer(4) :: open_mode
    open_mode = cbsl_get_mode(ctx%ctx)
    SET_ERRCODE_COND(open_mode == cbslf_unknown_mode)
  end function

  subroutine cbslf_set_compression_level(ctx, clevel, errcode)
    implicit none
    type(cbslf_context), intent(in)            :: ctx
    integer(4),          intent(in)            :: clevel
    integer(4),          intent(out), optional :: errcode
    integer(4) :: ret
    ret = cbsl_set_compression_level(ctx%ctx, clevel)
    SET_ERRCODE(ret)
  end subroutine

  function cbslf_get_compression_level(ctx, errcode) result(clevel)
    implicit none
    type(cbslf_context), intent(in)            :: ctx
    integer(4),          intent(out), optional :: errcode
    integer(4) :: clevel
    clevel = cbsl_get_compression_level(ctx%ctx)
    SET_ERRCODE_COND(clevel > 0)
  end function
end module
