module cubeset_buffering
  use cubetools_parameters
  use cubetools_structure
  use cubetools_keywordlist_types
  use cubetools_setup_types
  use cubetools_datasize
  use cubeset_messaging
  !
  public :: bufferingcomm
  private
  !
  type :: buffering_comm_t
     type(option_t),      pointer :: comm
     type(option_t),      pointer :: input
     type(keywordlist_comm_t), pointer :: input_arg
     type(option_t),      pointer :: output
     type(keywordlist_comm_t), pointer :: output_arg
     type(option_t),      pointer :: block
     type(option_t),      pointer :: limit
     type(option_t),      pointer :: task
     type(option_t),      pointer :: defaults
   contains
     procedure, public  :: register => cubeset_buffering_register
     procedure, private :: parse    => cubeset_buffering_parse
     procedure, private :: print    => cubeset_buffering_print
  end type buffering_comm_t
  type(buffering_comm_t) :: bufferingcomm
  !
contains
  subroutine cubeset_buffering_command(line,error)
    !---------------------------------------------------------------------
    ! Support routine for command
    ! BUFFERING Mode
    !---------------------------------------------------------------------
    character(len=*), intent(in)    :: line
    logical,          intent(inout) :: error
    !
    character(len=*), parameter :: rname='BUFFERING>COMMAND'
    !
    call cubeset_message(setseve%trace,rname,'Welcome')
    if (cubetools_nopt().eq.0) then
       call bufferingcomm%print(error)
       return
    endif
    call bufferingcomm%parse(line,error)
    if (error) return
  end subroutine cubeset_buffering_command
  !
  !----------------------------------------------------------------------
  !
  subroutine cubeset_buffering_register(bufferingcomm,error)
    !---------------------------------------------------------------------
    ! 
    !---------------------------------------------------------------------
    class(buffering_comm_t), intent(inout) :: bufferingcomm
    logical,                 intent(inout) :: error
    !
    type(standard_arg_t) :: stdarg
    type(keywordlist_comm_t)  :: keyarg
    character(len=*), parameter :: rname='BUFFERING>REGISTER'
    !
    call cubeset_message(setseve%trace,rname,'Welcome')
    !
    call cubetools_register_command(&
         'BUFFERING','',&
         'Set how input and output files are buffered',&
         'Large input/output files may not fit in RAM memory.&
         & This command allows the user to tune the CUBE&
         & behavior in such a case. The MEMORY only mode will&
         & return an error, while the DISK mode will read/write&
         & only part of the data in RAM memory at a given time.&
         & The AUTOMATIC mode decides which method to use for&
         & each cube depending on its size. Use /DEFAULT to reset&
         & buffering settings. When /DEFAULT is combined with other&
         & keys on the same command line, reset is applied first.',&
         cubeset_buffering_command,&
         bufferingcomm%comm,error)
    if (error) return
    !
    call cubetools_register_option(&
         'INPUT','value',&
         'Define buffering mode for input files',&
         'Choose whether the input files should be stored entirely in&
         & memory (MEMORY mode), or processed by blocks (DISK mode).&
         & Another possibility is to automatically switch between memory&
         & and disk mode depending on the cube size (AUTOMATIC mode).',&
         bufferingcomm%input,error)
    if (error) return
    call keyarg%register(&
         'value',&
         'Input buffering mode',&
         strg_emp,&
         code_arg_mandatory,&
         buffering,&
         .not.flexible,&
         bufferingcomm%input_arg,&
         error)
    if (error) return
    !
    call cubetools_register_option(&
         'OUTPUT','value',&
         'Define buffering for output files',&
         'Choose whether the output files should be stored entirely in&
         & memory (MEMORY mode), or processed by blocks (DISK mode).&
         & Another possibility is to automatically switch between memory&
         & and disk mode depending on the cube size (AUTOMATIC mode).',&
         bufferingcomm%output,error)
    if (error) return
    call keyarg%register(&
         'value',&
         'Output buffering mode',&
         strg_emp,&
         code_arg_mandatory,&
         buffering,&
         .not.flexible,&
         bufferingcomm%output_arg,&
         error)
    if (error) return
    !
    call cubetools_register_option(&
         'BLOCK','size [unit]',&
         'Block size for disk access',&
         'In MEMORY mode, files are read from disk to memory all at&
         & once in a single buffer, but this process is done by blocks&
         & of the given size.'//strg_bl//&
         'In DISK mode files are accessed by contiguous blocks of&
         & limited size. This allows processing of files larger than&
         & the available memory. In serial mode, keeping this&
         & parameter relatively small (e.g. 1% of the file size)&
         & saves memory with marginal file access overheads (e.g. 1%&
         & means 100 accesses to the file). In parallel mode, however&
         &, one might want to increase this value so that a large&
         & slice of the file is loaded in memory and is processed by&
         & several parallelisation tasks: the ideal value is Nthread&
         & times the /TASK size parameter.',&
         bufferingcomm%block,error)
    if (error) return
    call stdarg%register(&
         'size',&
         'Block size for disk mode',&
         'Default is 0.1 GiB.',&
         code_arg_mandatory,&
         error)
    if (error) return
    call stdarg%register(&
         'unit',&
         'unit for block size',&
         'Default is GiB.',&
         code_arg_optional,&
         error)
    if (error) return
    !
    call cubetools_register_option(&
         'LIMIT','size [unit]',&
         'Define the threshold for DISK use in automatic mode',&
         'In AUTOMATIC mode, define the cube size threshold above&
         & which the file is processed by blocks on disk.',&
         bufferingcomm%limit,error)
    if (error) return
    call stdarg%register(&
         'size',&
         'Limit between DISK access and MEMORY access',&
         'Default is 1 GiB.',&
         code_arg_mandatory,&
         error)
    if (error) return
    call stdarg%register(&
         'unit',&
         'unit for limit',&
         'Default is GiB.',&
         code_arg_optional,&
         error)
    if (error) return
    !
    call cubetools_register_option(&
         'TASK','size [unit]',&
         'Define parallel task size',&
         'When parallel processing is available, the entries (either&
         &images, spectra, or subcubes) are grouped in different tasks,&
         &and the tasks are executed in parallel.'&
         //strg_bl//&
         'The larger the task, the smaller the task overheads. On the&
         &other hand, if tasks are too large, the currently processed&
         &data block (full file or file block) might not be divided in&
         &enough tasks to occupy all the parallel threads.',&
         bufferingcomm%task,error)
    if (error) return
    call stdarg%register(&
         'size',&
         'Size for parallelisation tasks',&
         'Default is 100 KiB.',&
         code_arg_mandatory,&
         error)
    if (error) return
    call stdarg%register(&
         'unit',&
         'unit for task size',&
         'Default is KiB.',&
         code_arg_optional,&
         error)
    if (error) return
    !
    call cubetools_register_option(&
         'DEFAULT','',&
         'Reset buffering settings',&
         'Reset BUFFERING settings before applying other keys from the&
         & same command line.',&
         bufferingcomm%defaults,error)
    if (error) return
  end subroutine cubeset_buffering_register
  !
  subroutine cubeset_buffering_parse(bufferingcomm,line,error)
    use cubeadm_setup
    use gkernel_interfaces
    !---------------------------------------------------------------------
    !
    !---------------------------------------------------------------------
    class(buffering_comm_t), intent(in)    :: bufferingcomm
    character(len=*),        intent(in)    :: line
    logical,                 intent(inout) :: error
    !
    type(cube_setup_buffer_t) :: defaults
    ! Topics
    logical :: dodefaults
    character(len=*), parameter :: rname='BUFFERING>PARSE'
    !
    call cubeset_message(setseve%trace,rname,'Welcome')
    !
    call bufferingcomm%defaults%present(line,dodefaults,error)
    if (error) return
    if (dodefaults) then  
       cubset%buff%input   = defaults%input
       cubset%buff%output  = defaults%output
       cubset%buff%limit   = defaults%limit
       cubset%buff%block   = defaults%block
       cubset%buff%task    = defaults%task
    endif
    !
    call cubeset_buffering_parse_mode(line,bufferingcomm%input,bufferingcomm%input_arg,cubset%buff%input,error)
    if (error) return
    call cubeset_buffering_parse_mode(line,bufferingcomm%output,bufferingcomm%output_arg,cubset%buff%output,error)
    if (error) return
    !
    call cubeset_buffering_parse_size(line,bufferingcomm%block,defaults%block,dataunit(iGiB),cubset%buff%block,error)
    if (error) return
    call cubeset_buffering_parse_size(line,bufferingcomm%limit,defaults%limit,dataunit(iGiB),cubset%buff%limit,error)
    if (error) return
    call cubeset_buffering_parse_size(line,bufferingcomm%task,defaults%task,dataunit(ikiB),cubset%buff%task,error)
    if (error) return
  end subroutine cubeset_buffering_parse
  !
  subroutine cubeset_buffering_parse_mode(line,opt,keyarg,mode,error)
    use cubetools_disambiguate
    !------------------------------------------------------------------------
    !
    !------------------------------------------------------------------------
    character(len=*),     intent(in)    :: line
    type(option_t),       intent(in)    :: opt
    type(keywordlist_comm_t),  intent(in)    :: keyarg
    integer(kind=code_k), intent(out)   :: mode
    logical,              intent(inout) :: error
    !
    logical :: do
    character(len=argu_l) :: argu, key
    character(len=*), parameter :: rname='BUFFERING>PARSE>MODE'
    !
    call cubeset_message(setseve%trace,rname,'Welcome')
    !
    call opt%present(line,do,error)
    if (error) return
    if (do) then
       call cubetools_getarg(line,opt,1,argu,mandatory,error)
       if (error) return
       call cubetools_keywordlist_user2prog(keyarg,argu,mode,key,error)
       if (error) return
    endif
  end subroutine cubeset_buffering_parse_mode
  !
  subroutine cubeset_buffering_parse_size(line,opt,defsize,defunit,size,error)
    !------------------------------------------------------------------------
    !
    !------------------------------------------------------------------------
    character(len=*),     intent(in)    :: line
    type(option_t),       intent(in)    :: opt
    real(kind=size_k),    intent(in)    :: defsize
    character(len=*),     intent(in)    :: defunit
    real(kind=size_k),    intent(inout) :: size
    logical,              intent(inout) :: error
    !
    logical :: do
    character(len=argu_l) :: usize, uunit
    character(len=*), parameter :: rname='BUFFERING>PARSE>MODE'
    !
    call cubeset_message(setseve%trace,rname,'Welcome')
    !
    call opt%present(line,do,error)
    if (error) return
    if (do) then
       call cubetools_getarg(line,opt,1,usize,mandatory,error)
       if (error) return
       uunit = defunit
       call cubetools_getarg(line,opt,2,uunit,.not.mandatory,error)
       if (error) return
       !
       call cubetools_datasize_user2prog(usize,uunit,defsize,size,error)
       if (error) return
    endif
  end subroutine cubeset_buffering_parse_size
  !
  subroutine cubeset_buffering_print(bufferingcomm,error)
    use cubetools_format
    use cubeadm_setup
    !---------------------------------------------------------------------
    !
    !---------------------------------------------------------------------
    class(buffering_comm_t), intent(in)    :: bufferingcomm
    logical,                 intent(inout) :: error
    !
    character(len=*), parameter :: rname='BUFFERING>PRINT'
    !
    character(len=message_length) :: mess
    !
    call cubeset_message(seve%r,rname,'  Buffering')
    write(mess,'(2A)')  '    INPUT:  ',buffering(cubset%buff%input)
    call cubeset_message(seve%r,rname,mess)
    write(mess,'(2A)')  '    OUTPUT: ',buffering(cubset%buff%output)
    call cubeset_message(seve%r,rname,mess)
    write(mess,'(2A)')  '    LIMIT:  ',cubetools_format_memsize(cubset%buff%limit)
    call cubeset_message(seve%r,rname,mess)
    write(mess,'(2A)')  '    BLOCK:  ',cubetools_format_memsize(cubset%buff%block)
    call cubeset_message(seve%r,rname,mess)
    write(mess,'(2A)')  '    TASK:   ',cubetools_format_memsize(cubset%buff%task)
    call cubeset_message(seve%r,rname,mess)
  end subroutine cubeset_buffering_print
end module cubeset_buffering
