! nc_diag_write - NetCDF Layer Diag Writing Module
! Copyright 2015 Albert Huang - SSAI/NASA for NASA GSFC GMAO (610.1).
! 
! 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.
! 
! Main module - nc_diag_write_mod
!

module nc_diag_write_mod
    ! Library that provides a high level interface for storing channel-
    ! based and observation-based data.
    ! 
    ! This library allows developers to easily store channel-based data
    ! (e.g. chaninfo) and observation-based data (metadata and data2d)
    ! to a NetCDF file via an easy to use API.
    ! 
    ! Internally, the process for storing this data looks like this:
    !   -> When the developer calls nc_diag_init, the NetCDF file is
    !      opened internally. The corresponding NCID is stored, and
    !      any memory allocation needed is done at this step.
    !      => If the file was opened in append mode, nc_diag_write will
    !         attempt to load any existing variable definitions for all
    !         types of variables - chaninfo, metadata, and data2d.
    !         Appropriate variable counters and data for each variable
    !         type will be set during init, and data writing will start
    !         at the end of the variable.
    !      
    !   -> Headers are essentially NetCDF global attributes, or
    !      attributes that describe a file. These can be added at any
    !      time during the writing session.
    !      
    !   -> varattr, or variable attributes, describe an associated
    !      variable. (This is a NetCDF4 variable attribute!) These can
    !      only be added after variable definitions have been locked.
    !      
    !   -> chaninfo variables:
    !      => nc_diag_chaninfo_dim_set must be called first to set
    !         the nchans dimension. If it isn't called, doing any
    !         chaninfo operation will result in an error.
    !      => chaninfo variables are 1D, with nchans number of elements.
    !      
    !   -> metadata and data2d variables:
    !      => metadata and data2d variables do not require any initial
    !         dimension setting - nc_diag_write will keep track of your
    !         number of observations for you!
    !      => metadata variables are 1D, with nobs number of elements.
    !         nobs can increase infinitely to fit the number of
    !         observations recorded.
    !      => data2d variables are 2D, with dimensions of nobs by
    !         another fixed dimension.
    !      
    !   -> Definition locking is sometimes necessary for certain
    !      operations, such as defining variable attributes. They are
    !      necessary due to needing information from NetCDF after
    !      variables are defined, or needing to assert that certain
    !      variable properties are constant. Locking uses the following
    !      steps:
    !      => nc_diag_*_write_def is called to send the variable
    !         definitions to NetCDF. This include defining any
    !         dimensions necessary, as well as defining the variables
    !         stored as well.
    !      => Once each nc_diag_*_write_def completes, their
    !         corresponding def_lock state will be set to TRUE, locking
    !         the definitions in place.
    !      => Attempts to make repeated calls will result in an error,
    !         due to the def_lock state being set to TRUE.
    !      
    !   -> Data calls will store the input data into memory. The
    !      implementation and design of the variable storage is
    !      dependent on the variable type being stored. chaninfo
    !      variables have a certain storage format, and metadata/data2d
    !      variables have another storage format. Note that metadata
    !      and data2d code have a few similarities in data storage
    !      since the variables themselves share common features, like
    !      the nobs dimension.
    !      
    !   -> Sometimes, there is a significant amount of data that needs
    !      to be processed and stored. Since nc_diag_write stores all
    !      of the data into memory (RAM) before it is written out,
    !      there may not be enough memory to store the entirety of the
    !      data. To alleviate that, nc_diag_flush_buffer can be called
    !      to flush the data from the memory and write them to disk.
    !      In reality, this doesn't actually free any memory... but the
    !      memory savings gained is still there. Calling the flushing
    !      subroutine performs the following steps:
    !      => It first checks to make sure that definitions are locked.
    !         The NetCDF variable IDs are needed in order to actually
    !         write (or "put") any data into the file.
    !      => It also checks to see if the data has already been locked.
    !         No more data can be written if the data has been locked.
    !      => It then calls all of the nc_diag_*_write_data subroutines
    !         with a special flag to indicate data flushing. When the
    !         data flushing flag is set, each of the variable
    !         subroutines will take measures to operate as a buffer
    !         flush, and not as a finalized data write.
    !      => When flushing within the variable subroutine, the
    !         subroutine first writes out any data using the variable-
    !         specific, memory-stored data.
    !      => It then resets any internal data counters that it may use
    !         to store and keep track of the data.
    !      => As mentioned before, it does not actually free any memory
    !         since deallocating and subsequently reallocating from
    !         scratch will take a long time, and is inefficient. With
    !         a counter reset, each variable type's internal data
    !         storage will start at the beginning of the data array,
    !         effectively avoiding any need to add any more memory, and
    !         thus achieving the goal of not using any more memory.
    !      => Finally, since the writing is in buffer flushing mode,
    !         the data_lock flag for each variable type is NOT set.
    !         This is so that more data can be written, either with
    !         the flushing method or with the regular write.
    !      
    !   -> Once data is done being queued ("stored"), nc_diag_write can
    !      be called. The variables will have their data re-read from
    !      memory and actually written to the file. This is also very
    !      much variable type independent, since every variable has its
    !      own way of storing variable data. Again, metadata and data2d
    !      have similar code, with the only difference being the
    !      dimensionality. Note that this is where NetCDF calls are
    !      made to define and "put" data. Once done, if we are NOT in
    !      append mode, we call nf90_enddef to end define mode.
    !      
    !   -> Once all the data has been queued and/or written out, it is 
    !      safe to call nc_diag_finish. We call this from nc_diag_write.
    !      => This will first write definitions and data, if applicable.
    !         The calls will have a special flag set to ensure that no
    !         errors are triggered for already having a lock set, since
    !         this subroutine will be closing the file anyways.
    !      => Once all of the data has been sent to NetCDF, this will
    !         tell NetCDF to close the file being written. Note that
    !         NetCDF also keeps a memory cache of the data being stored
    !         as well, so actual I/O writing may not be completely done
    !         until here. After the writing and closing on the NetCDF
    !         side completes, everything will be completely deallocated,
    !          and everything will be reset.
    !      
    !   -> Upon reset, nc_diag_write is again ready to write a new file
    !      via nc_diag_create!
    ! 
    ! Note that only ONE file is written as a time. This is due to the
    ! nature of the library focusing and storing data for a single
    ! file. Attempting to create another file without closing the
    ! previous one will result in an error.
    
    ! Load state variables! We need to know:
    !   init_done           - ...whether a file is currently loaded or
    !                         not.
    !   append_only         - ...whether we are in append mode or not.
    !   ncid                - ...the current NCID of our file.
    !   enable_trim         - ...whether we need to automatically trim
    !                         our strings for chaninfo string storage or
    !                         not.
    !   diag_chaninfo_store - ...chaninfo variable information.
    !                         Specifically, whether it's allocated or
    !                         not, and if it's allocated, whether the
    !                         definitions are locked or not. (def_lock)
    !   diag_metadata_store - ...metadata variable information.
    !                         Specifically, whether it's allocated or
    !                         not, and if it's allocated, whether the
    !                         definitions are locked or not. (def_lock)
    !   diag_data2d_store   - ...data2d variable information.
    !                         Specifically, whether it's allocated or
    !                         not, and if it's allocated, whether the
    !                         definitions are locked or not. (def_lock)
    use ncdw_state, only: init_done, append_only, ncid, &
        enable_trim, cur_nc_file, &
        diag_chaninfo_store, diag_metadata_store, diag_data2d_store, &
        diag_varattr_store
    
    ! Load needed NetCDF functions and constants
    use netcdf, only: nf90_inq_libvers, nf90_open, nf90_create, &
        nf90_enddef, nf90_close, nf90_sync, &
        NF90_WRITE, NF90_NETCDF4, NF90_CLOBBER
    
    !------------------------------------------------------------------
    ! API imports to expose API from this module
    ! (Plus general imports for this module as well!)
    !------------------------------------------------------------------
    
    ! Load necessary command line message subroutines and state
    ! variables
    use ncdw_climsg, only: &
#ifdef ENABLE_ACTION_MSGS
        nclayer_enable_action, nclayer_actionm, &
#endif
        nclayer_error, nclayer_warning, nclayer_info, nclayer_check, &
        nc_set_info_display, nc_set_action_display
    
    ! Load nc_diag_write specific types
    use ncdw_types, only: NLAYER_BYTE, NLAYER_SHORT, NLAYER_LONG, &
        NLAYER_FLOAT, NLAYER_DOUBLE, NLAYER_STRING
    
    ! Load header writing API
    use ncdw_lheader, only: nc_diag_header
    
    ! Load chaninfo writing API + auxillary functions for our use
    use ncdw_chaninfo, only: nc_diag_chaninfo_dim_set, &
        nc_diag_chaninfo, &
        nc_diag_chaninfo_load_def, nc_diag_chaninfo_write_def, &
        nc_diag_chaninfo_write_data, &
        nc_diag_chaninfo_set_strict, &
        nc_diag_chaninfo_allocmulti, nc_diag_chaninfo_prealloc_vars, &
        nc_diag_chaninfo_prealloc_vars_storage
    
    ! Load metadata writing API + auxillary functions for our use
    use ncdw_metadata, only: nc_diag_metadata, &
        nc_diag_metadata_load_def, nc_diag_metadata_write_def, &
        nc_diag_metadata_write_data, &
        nc_diag_metadata_set_strict, &
        nc_diag_metadata_allocmulti, &
        nc_diag_metadata_prealloc_vars, &
        nc_diag_metadata_prealloc_vars_storage, &
        nc_diag_metadata_prealloc_vars_storage_all
    
    ! Load data2d writing API + auxillary functions for our use
    use ncdw_data2d, only: nc_diag_data2d, &
        nc_diag_data2d_load_def, nc_diag_data2d_write_def, &
        nc_diag_data2d_write_data, &
        nc_diag_data2d_set_strict, &
        nc_diag_data2d_allocmulti, &
        nc_diag_data2d_prealloc_vars, &
        nc_diag_data2d_prealloc_vars_storage, &
        nc_diag_data2d_prealloc_vars_storage_all
    
    ! Load varattr (variable attribute) writing API
    use ncdw_varattr, only: nc_diag_varattr
    
    implicit none
    
    contains
        ! Creates or appends to a new NetCDF file for data writing.
        ! 
        ! Given the target NetCDF file name, attempt to create or open
        ! the file and set everything up for writing data to the file.
        ! This includes any internal memory allocation required for
        ! buffering any data sent to this file.
        ! 
        ! If the file is opened in non-append mode (default), this will
        ! attempt to create a new file and start data writing from
        ! scratch. If the file already exists, it will be OVERWRITTEN
        ! without any prompt.
        ! 
        ! If the file is opened in append mode, this will attempt to
        ! open the file specified, read the file's dimension and
        ! variable storage information, and set things up so that
        ! data writing starts at the end of the file's existing data.
        ! Note that append mode only works for nc_diag_write NetCDF
        ! files. Attempting to open a non-nc_diag_write file could
        ! result in errors!
        ! 
        ! In order for the file to be written to successfully,
        ! nc_diag_finish MUST be called for all of the data to be
        ! flushed, and the corresponding memory to be freed.
        ! 
        ! nc_diag_write may only operate on one file at a time. This is
        ! due to the nature of nc_diag_write focusing on a single file.
        ! 
        ! If a NetCDF file is already open, this will raise an error
        ! and the program will terminate.
        ! 
        ! Args:
        !     filename (character(len=*)): NetCDF file name to create or
        !         append to.
        !     append (logical, optional): whether to open the NetCDF
        !         file in append mode or not. By default, if this is
        !         not specified, the file will be opened regularly (not
        !         in append mode).
        ! 
        ! Raises:
        !     If a file is already open, an error occurs and the program
        !     will exit.
        !     
        !     If the file specified does not exist, or there are issues
        !     with NetCDF creating/opening/using the file, an error
        !     will occur with the corresponding NetCDF error.
        !     
        !     Issues with storage allocation are bugs, and will also
        !     result in an error with an indication that a bug has
        !     occurred.
        ! 
        subroutine nc_diag_init(filename, append)
            character(len=*),intent(in)    :: filename
            logical, intent(in), optional  :: append
            
            ! Buffer size variable for NetCDF optimization settings
            ! (Not sure if this helps much...)
            integer                        :: bsize = 16777216;
            
#ifdef ENABLE_ACTION_MSGS
            character(len=1000)                   :: action_str
            
            if (nclayer_enable_action) then
                if (present(append)) then
                    write(action_str, "(A, L, A)") "nc_diag_init(filename = " // trim(filename) // &
                        ", append = ", append, ")"
                else
                    write(action_str, "(A)") "nc_diag_init(filename = " // trim(filename) // &
                        ", append = (not specified))"
                end if
                call nclayer_actionm(trim(action_str))
            end if
#endif
            
            ! Inform user about NetCDF version
            call nclayer_info('Initializing netcdf layer library, version ' // trim(nf90_inq_libvers()) // '...')
            
            ! Make sure we haven't initialized yet. If we have, it
            ! means that another file is open that hasn't been closed
            ! yet!
            if (.NOT. init_done) then
                ! Special append mode - that means that we need to
                ! assume that all definitions are set and locked.
                if (present(append) .AND. (append .eqv. .TRUE.)) then
                    ! Open the file in append mode!
                    call nclayer_check( nf90_open(filename, NF90_WRITE, ncid, &
                        bsize, cache_nelems = 16777216) ) ! Optimization settings
                    
                    ! Set the append flag
                    append_only = .TRUE.
                else
                    ! Create the file from scratch!
                    
                    ! nf90_create creates the NetCDF file, and initializes
                    ! everything needed to write a NetCDF file.
                    ! 
                    ! NF90_CLOBBER forces overwriting the file, even if it already
                    ! exists.
                    ! 
                    ! ncid is a special ID that the NetCDF library uses to keep
                    ! track of what file you're working on. We're returning that
                    ! here.
                    call nclayer_check( nf90_create(filename, OR(NF90_NETCDF4, NF90_CLOBBER), ncid, &
                        0, bsize, cache_nelems = 16777216) ) ! Optimization settings
                end if
                
                ! Allocation sanity checks...
                ! These storage variables should NOT be allocated.
                ! If they are, it indicate that we have a serious problem.
                if (allocated(diag_chaninfo_store)) then
                    call nclayer_error("BUG! diag_chaninfo_store is allocated, but init_done is not set!")
                end if
                
                if (allocated(diag_metadata_store)) then
                    call nclayer_error("BUG! diag_metadata_store is allocated, but init_done is not set!")
                end if
                
                if (allocated(diag_data2d_store)) then
                    call nclayer_error("BUG! diag_data2d_store is allocated, but init_done is not set!")
                end if
                
                if (allocated(diag_varattr_store)) then
                    call nclayer_error("BUG! diag_data2d_store is allocated, but init_done is not set!")
                end if
                
                ! All good, allocate the storage variables!
                allocate(diag_chaninfo_store)
                allocate(diag_metadata_store)
                allocate(diag_data2d_store)
                allocate(diag_varattr_store)
                
                ! Set the current file being written to...
                cur_nc_file = filename
                
                ! Set the flag state to indicate that a file is open,
                ! and that initialization is done.
                init_done = .TRUE.
                
                ! "Lock and load" the definitions... or simply ask
                ! chaninfo/metadata/data2d to read the NetCDF files,
                ! build a cache, and set up anything necessary to be
                ! able to resume writing from before.
                if (present(append) .AND. (append .eqv. .TRUE.)) then
                    call nclayer_info("Loading chaninfo variables/dimensions from file:")
                    call nc_diag_chaninfo_load_def
                    
                    call nclayer_info("Loading metadata variables/dimensions from file:")
                    call nc_diag_metadata_load_def
                    
                    call nclayer_info("Loading data2d variables/dimensions from file:")
                    call nc_diag_data2d_load_def
                end if
            else
                ! Opening a new file while another file is still open is
                ! bad... let's yell at the user/developer!
                call nclayer_error("Attempted to initialize without closing previous nc_diag file!" &
                    // char(10) &
                    // "             (Previous file: " // trim(cur_nc_file) &
                    // char(10) &
                    // "              Attempted to open file: " // trim(filename) // ")")
            end if
        end subroutine nc_diag_init
        
        ! Lock and commit the variable definitions for the current
        ! NetCDF file.
        ! 
        ! Attempt to commit the currently stored variable definitions
        ! to the NetCDF file via NetCDF API calls. Once done, this will
        ! set the flag for locking the variable definitions, preventing
        ! any additional variables from being created or changed.
        ! 
        ! Locking the definitions here will enable functions that
        ! require variable definition locking. This include
        ! nc_diag_varattr and nc_diag_flush_buffer, both of which
        ! require the variable definitions to be committed and locked.
        ! 
        ! Definitions may not be locked more than once. In addition,
        ! creating new variables after definitions are locked will
        ! result in errors.
        ! 
        ! Args:
        !     None
        ! 
        ! Raises:
        !     The following errors will trigger indirectly from other
        !     subroutines called here:
        !     
        !     If definitions have already been locked, this will result
        !     in an error.
        !     
        !     If there is no file open, this will result in an error.
        !     
        !     Other errors may result from invalid data storage, NetCDF
        !     errors, or even a bug. See the called subroutines'
        !     documentation for details.
        ! 
        subroutine nc_diag_lock_def
#ifdef ENABLE_ACTION_MSGS
            if (nclayer_enable_action) then
                call nclayer_actionm("nc_diag_lock_def()")
            end if
#endif
            call nclayer_info("Locking all variable definitions!")
            
            ! Call all of the variable write_def
            call nclayer_info("Defining chaninfo:")
            call nc_diag_chaninfo_write_def
            
            call nclayer_info("Defining metadata:")
            call nc_diag_metadata_write_def
            
            call nclayer_info("Defining data2d:")
            call nc_diag_data2d_write_def
            
            call nclayer_info("All variable definitions locked!")
        end subroutine nc_diag_lock_def
        
        ! Write all of the variables to the NetCDF file, including the
        ! variable definitions and data, and close the file.
        ! 
        ! Attempt to write the currently stored variable definitions
        ! and data to the NetCDF file via NetCDF API calls.
        ! 
        ! Once done, this will lock both the definitions and the data,
        ! preventing any new variables or new data from being written
        ! after this call completes.
        ! 
        ! Once data has been written and locked, the file itself will be
        ! closed. NetCDF may internally cache/buffer variable data in
        ! memory, so actual writing may occur at this time to let NetCDF
        ! actually commit the data to disk.
        ! 
        ! Finally, nc_diag_write state cleanup and memory deallocation
        ! will occur via a call to nc_diag_finish.
        ! 
        ! Writing may not occur more than once. In addition, writing any
        ! new variables or adding any new data will result in an error.
        ! (Not that you can write any more data after this, since the
        ! file is closed and everything is reset...)
        ! 
        ! Args:
        !     None
        ! 
        ! Raises:
        !     The following errors will trigger indirectly from other
        !     subroutines called here:
        !     
        !     If the variable definitions have already been locked, this
        !     will NOT result in an error. This is due to the fact that
        !     we could've locked definitions earlier, and that we
        !     can assume that with locked definitions, we are able to
        !     write data.
        !     
        !     Data writing is the critical part. If the variable data
        !     writing has already been locked, this will result in an
        !     error.
        !     
        !     If there is no file open (or the file is already closed),
        !     this will result in an error.
        !     
        !     Other errors may result from invalid data storage, NetCDF
        !     errors, or even a bug. See the called subroutines'
        !     documentation for details.
        ! 
        subroutine nc_diag_write
#ifdef ENABLE_ACTION_MSGS
            if (nclayer_enable_action) then
                call nclayer_actionm("nc_diag_write()")
            end if
#endif
            
            ! Call all variable write_def, with an extra option to make
            ! sure that no errors occur during write, even when locked!
            ! (We could have previously locked, but here we're doing it
            ! on purpose!)
            call nclayer_info("Defining chaninfo:")
            call nc_diag_chaninfo_write_def(.TRUE.)
            
            call nclayer_info("Defining metadata:")
            call nc_diag_metadata_write_def(.TRUE.)
            
            call nclayer_info("Defining data2d:")
            call nc_diag_data2d_write_def(.TRUE.)
            
            ! Lock definition writing!
            if ((.NOT. append_only) .AND. ((.NOT. diag_chaninfo_store%def_lock) .OR. &
                (.NOT. diag_metadata_store%def_lock) .OR. &
                (.NOT. diag_data2d_store%def_lock))) &
                call nclayer_check(nf90_enddef(ncid))
            
            ! Call all variable write_data
            call nclayer_info("Writing chaninfo:")
            call nc_diag_chaninfo_write_data
            
            call nclayer_info("Writing metadata:")
            call nc_diag_metadata_write_data
            
            call nclayer_info("Writing data2d:")
            call nc_diag_data2d_write_data
            
            ! Call nf90_close to save everything to disk!
            call nclayer_info("All done queuing in data, letting NetCDF take over!")
            call nclayer_check(nf90_close(ncid))
            
            call nclayer_info("All done!")
            
            ! Call our cleanup subroutine
            call nc_diag_finish
        end subroutine nc_diag_write
        
        ! Reset nc_diag_write state, and deallocate all of the variable
        ! storage in preparation for another new NetCDF file write.
        ! 
        ! Attempt to reset nc_diag_write state and deallocate all of 
        ! the variable storage. This frees up memory, and allows for
        ! nc_diag_init to work again for a new file.
        ! 
        ! This can only be called once per open. (You can't call this
        ! without a nc_diag_init happening before it!) Calling this
        ! without any file opened (or data stored) will result in an
        ! error.
        ! 
        ! This is an internal subroutine, and is NOT meant to be called
        ! outside of nc_diag_write. Calling this subroutine in your
        ! program may result in unexpected behavior and/or data
        ! corruption!
        ! 
        ! Args:
        !     None
        ! 
        ! Raises:
        !     If there is no file open, or if no data/state needs to be
        !     cleaned up, this will result in an error.
        !     
        !     Issues with storage deallocation are bugs, and will also
        !     result in an error with an indication that a bug has
        !     occurred.
        ! 
        subroutine nc_diag_finish
#ifdef ENABLE_ACTION_MSGS
            if (nclayer_enable_action) then
                call nclayer_actionm("nc_diag_finish()")
            end if
#endif
            ! Make sure that we only deallocate if we have something
            ! open/initialized!
            if (init_done) then
                call nclayer_info("Cleaning up...")
                
                ! Do some quick sanity checks!
                if (.NOT. allocated(diag_chaninfo_store)) then
                    call nclayer_error("BUG! diag_chaninfo_store is not allocated, but init_done is set!")
                end if
                
                if (.NOT. allocated(diag_metadata_store)) then
                    call nclayer_error("BUG! diag_metadata_store is not allocated, but init_done is set!")
                end if
                
                if (.NOT. allocated(diag_data2d_store)) then
                    call nclayer_error("BUG! diag_data2d_store is not allocated, but init_done is set!")
                end if
                
                if (.NOT. allocated(diag_varattr_store)) then
                    call nclayer_error("BUG! diag_data2d_store is not allocated, but init_done is set!")
                end if
                
                ! Deallocate everything! Note that this deallocates
                ! everything within the derived type as well.
                ! (See? Fortran is better than C!)
                deallocate(diag_chaninfo_store)
                deallocate(diag_metadata_store)
                deallocate(diag_data2d_store)
                deallocate(diag_varattr_store)
                
                ! Clear initialization, append, and current file name
                ! state.
                init_done = .FALSE.
                append_only = .FALSE.
                cur_nc_file = ""
            else
                call nclayer_error("Attempted to deallocate without initializing!")
            end if
        end subroutine nc_diag_finish
        
        ! Flush all of the current variable data to NetCDF, and reset
        ! all of the variable storage to an initial state.
        ! 
        ! Attempt to write the currently stored variable definitions
        ! and data to the NetCDF file via NetCDF API calls.
        ! 
        ! Once done, this will effectively "flush" the data from the
        ! current variable buffers. Internally, this sets a starting
        ! counter and resets the buffer counter so that new data can
        ! be stored sequentially without requiring more memory, at least
        ! until memory runs out for the current buffer.
        ! 
        ! Definitions MUST be locked in order for flushing to work.
        ! Without definition locking, nc_diag_write is unable to make
        ! calls to NetCDF due to the lack of variable IDs.
        ! 
        ! If definitions are not locked, calling this will result in an
        ! error.
        ! 
        ! Data locking does NOT occur with flushing. As a result, this
        ! subroutine may be called multiple times, and a final
        ! nc_diag_write can be called once after this call.
        ! 
        ! (Note that calling nc_diag_write will lock the data and close
        ! the file, regardless of flushing the buffer here!)
        ! 
        ! Args:
        !     None
        ! 
        ! Raises:
        !     If definitions have not been locked, this will result in
        !     an error.
        !     
        !     The following errors will trigger indirectly from other
        !     subroutines called here:
        !     
        !     If the variable data writing has already been locked, this
        !     will result in an error.
        !     
        !     If there is no file open (or the file is already closed),
        !     this will result in an error.
        !     
        !     Other errors may result from invalid data storage, NetCDF
        !     errors, or even a bug. See the called subroutines'
        !     documentation for details.
        ! 
        subroutine nc_diag_flush_buffer
#ifdef ENABLE_ACTION_MSGS
            if (nclayer_enable_action) then
                call nclayer_actionm("nc_diag_flush_buffer()")
            end if
#endif
            if (.NOT. init_done) &
                call nclayer_error("Attempted to flush nc_diag_write buffers without initializing!")
            
            if ((.NOT. diag_chaninfo_store%def_lock) .OR. &
                (.NOT. diag_metadata_store%def_lock) .OR. &
                (.NOT. diag_data2d_store%def_lock)) &
                call nclayer_error("Definitions must be locked in order to flush the buffer!")
            
            ! Perform writes with the buffer flag set!
            call nclayer_info("Flushing chaninfo:")
            call nc_diag_chaninfo_write_data(.TRUE.)
            
            call nclayer_info("Flushing metadata:")
            call nc_diag_metadata_write_data(.TRUE.)
            
            call nclayer_info("Flushing data2d:")
            call nc_diag_data2d_write_data(.TRUE.)
            
            call nclayer_info("Flushing done!")
        end subroutine nc_diag_flush_buffer
        
        ! Force NetCDF to flush its buffers and write any data stored to
        ! disk.
        ! 
        ! Attempt to force the write of NetCDF's stored variable data to
        ! the NetCDF file via NetCDF API calls.
        ! 
        ! This does NOT flush nc_diag_write's buffers. It only attempts
        ! to flush NetCDF's internal buffers to disk.
        ! 
        ! If there is no file open, or the file is already closed, this
        ! will result in an error.
        ! 
        ! Args:
        !     None
        ! 
        ! Raises:
        !     If there is no file open (or the file is already closed),
        !     this will result in an error.
        !     
        !     Other errors may result from NetCDF errors. Any errors
        !     from NetCDF are likely to occur if there are problems
        !     writing to disk. Errors resulting from problems with
        !     manipulating NetCDF memory or a glitch are unlikely, but
        !     still possible.
        ! 
        subroutine nc_diag_flush_to_file
#ifdef ENABLE_ACTION_MSGS
            if (nclayer_enable_action) then
                call nclayer_actionm("nc_diag_flush_to_file()")
            end if
#endif
            ! Make sure we have something open + initialized
            if (.NOT. init_done) &
                call nclayer_error("Attempted to flush NetCDF buffers without initializing!")
            
            ! Call nf90_sync to try and commit the put'd data to disk
            call nclayer_check(nf90_sync(ncid))
        end subroutine nc_diag_flush_to_file
        
        ! Toggle whether nc_diag_write should be strict about dimensions
        ! and variable consistency.
        ! 
        ! Set the strictness of nc_diag_write for checking dimensions
        ! and stored variable consistency.
        ! 
        ! If set to TRUE, nc_diag_write will error when consistency
        ! checks fail.
        ! 
        ! If set to FALSE, nc_diag_write will only display a warning
        ! when these checks fail.
        ! 
        ! To see more details about what checks are made, see the
        ! corresponding called subroutine documentation for details.
        ! 
        ! Args:
        !     enable_strict (logical): whether to be strict with
        !         consistency checks or not.
        ! 
        ! Raises:
        !     If there is no file open (or the file is already closed),
        !     this will result in an error.
        !     
        !     Although unlikely, other errors may indirectly occur.
        !     They may be general storage errors, or even a bug.
        !     See the called subroutines' documentation for details.
        ! 
        subroutine nc_diag_set_strict(enable_strict)
            logical, intent(in) :: enable_strict
            
            ! Make sure we have something open + initialized
            if (init_done) then
                ! Call all of the variable set_strict subroutines
                call nc_diag_chaninfo_set_strict(enable_strict)
                call nc_diag_metadata_set_strict(enable_strict)
                call nc_diag_data2d_set_strict(enable_strict)
            else
                call nclayer_error("Can't set strictness level - NetCDF4 layer not initialized yet!")
            end if
        end subroutine nc_diag_set_strict
        
        ! Toggle whether nc_diag_write should trim strings or keep their
        ! original length.
        ! 
        ! Set the option to trim strings automatically with string
        ! variable data or not.
        ! 
        ! If set to TRUE, nc_diag_write will automatically trim strings
        ! to the minimum needed to hold the string. (Extra spaces at
        ! the end will be trimmed off the largest string in an array,
        ! and the result will be the bounds for that string array!)
        ! 
        ! If set to FALSE, nc_diag_write will NOT trim any strings. The
        ! given string length is assumed to be the bounds for holding
        ! the string. However, nc_diag_write will enforce strict
        ! checking of the input string length. If the length of the
        ! string changes during subsequent storage, nc_diag_write
        ! will error.
        ! 
        ! Note that this only applies to variable string storage.
        ! Attribute string storage is handled directly by NetCDF.
        ! From testing, it seems that NetCDF will trim your string when
        ! storing headers (global attributes).
        ! 
        ! Args:
        !     do_trim (logical): whether to automatically trim the
        !         stored strings or not.
        ! 
        ! Raises:
        !     Nothing... at least here. See above for potential errors
        !     outside of this subroutine.
        ! 
        subroutine nc_diag_set_trim(do_trim)
            logical, intent(in) :: do_trim
            
            enable_trim = do_trim
        end subroutine nc_diag_set_trim
end module nc_diag_write_mod