/** \file

\internal

\page nc_dispatch Internal Dispatch Table Architecture

This document describes the architecture and details of the netCDF
internal dispatch mechanism. The idea is that when a user opens or
creates a netcdf file, a specific dispatch table is
chosen. Subsequent netcdf API calls are then channeled through that
dispatch table to the appropriate function for implementing that API
call.

At least the following dispatch tables are supported.
- netcdf classic files (netcdf-3)
- netcdf enhanced files (netcdf-4)
- OPeNDAP to netcdf-3
- OPeNDAP to netcdf-4

Internal Dispatch Tables
- \subpage adding_dispatch
- \subpage dispatch_notes
- \subpage put_vara_dispatch
- \subpage put_attr_dispatch


The dispatch table represents a distillation of the netcdf API down to
a minimal set of internal operations. The format of the dispatch table
is defined in the file libdispatch/ncdispatch.h. Every new dispatch
table must define this minimal set of operations.

\page adding_dispatch Adding a New Dispatch Table

\tableofcontents

In order to make this process concrete, let us assume we plan to add
an in-memory implementation of netcdf-3.

\section dispatch_step1 Step 1.

Define a –enable flag and an AM_CONFIGURE flag in configure.ac. We
will use the flags –enable-netcdfm and USE_NETCDFM respectively.

\section dispatch_step2 Step 2.

Choose some prefix of characters to identify the new dispatch
system. In effect we are defining a name-space. For our in-memory
system, we will choose "NCM" and "ncm". NCM is used for non-static
procedures to be entered into the dispatch table and ncm for all other
non-static procedures.

\section dispatch_step3 Step 3.

Modify file libdispatch/ncdispatch.h as follows.

    Add a index for this implementation:

\code
              #define NC_DISPATCH_NCM  5
\endcode

    Define an external reference to the in-memory dispatch table.

\code
              #ifdef USE_NETCDFM
              extern NC_Dispatch* NCM_dispatch_table;
              #endif
\endcode

\section dispatch_step4 Step 4.

Modify file libdispatch/netcdf.c as follows.

    Add a ptr to the in-memory dispatch table.

\code
              #ifdef USE_NETCDFM
              NC_Dispatch* NCM_dispatch_table = NULL;
              #endif
\endcode

    Add includes for any necessary header files as needed.

\section dispatch_step5 Step 5.

Define the functions necessary to fill in the dispatch table. As a
rule, we assume that a new directory is defined, libsrcm, say. Within
this directory, we need to define Makefile.am, the source files
containing the dispatch table and the functions to be placed in the
dispatch table – call them ncmdispatch.c and ncmdispatch.h. Look at
libsrc/nc3dispatch.[ch] for an example.

As part of the ncmdispatch.c file, you must define the following.

\code
     NC_Dispatch NCM_dispatcher = {
     NC_DISPATCH_NCM,
     NCM_create,
     NCM_open,
     ...
     };

     int
     NCM_initialize(void)
     {
         NCM_dispatch_table = &NCM_dispatcher;
         return NC_NOERR;
     }
\endcode

Assuming that the in-memory library does not require any external
libraries, then the Makefile.am will look something like this.

\code
     NCM_SOURCES = ncmdispatch.c ncmdispatch.h ...
     AM_CPPFLAGS +=  -I$(top_srcdir)/libsrc -I$(top_srcdir)/libdispatch
     libnetcdfm_la_SOURCES = $(NCM_SOURCES)
     noinst_LTLIBRARIES = libnetcdfm.la
\endcode

\section dispatch_step6 Step 6.

Provide for the inclusion of this library in the final libnetcdf
library. This is accomplished by modifying liblib/Makefile.am by
adding something like the following.

\code
     if USE_NETCDFM
        libnetcdf_la_LIBADD += $(top_builddir)/libsrcm/libnetcdfm.la
     endif
\endcode

\section dispatch_step7 Step 7.

Modify the NC_initialize function in liblib/stub.c by adding
appropriate references to the NCM dispatch function.

\code
     #ifdef USE_NETCDFM
     extern int NCM_initialize(void);
     #endif
     ...
     int NC_initialize(void)
     {
     ...
     #ifdef USE_DAP
         if((stat = NCM_initialize())) return stat;
     #endif
     ...
     }
\endcode

\section dispatch_step8 Step 8.

Add a directory of tests; ncm_test, say. The file ncm_test/Makefile.am
will look something like this.

\code
     # These files are created by the tests.
     CLEANFILES = ...
     # These are the tests which are always run.
     TESTPROGRAMS = test1 test2 ...
     test1_SOURCES = test1.c ...
     ...
     # Set up the tests.
     check_PROGRAMS = $(TESTPROGRAMS)
     TESTS = $(TESTPROGRAMS)
     # Any extra files required by the tests
     EXTRA_DIST = ...
\endcode

\section dispatch_step9 Step 9.

Provide for libnetcdfm to be constructed by adding the following to
the top-level Makefile.am.

\code
     if USE_NETCDFM
     NCM=libsrcm
     NCMTESTDIR=ncm_test
     endif
     ...
     SUBDIRS = ... $(DISPATCHDIR)  $(NCM) ... $(NCMTESTDIR)
\endcode

\section choosing_dispatch_table Choosing a Dispatch Table

The dispatch table is chosen in the NC_create and the NC_open
procedures in libdispatch/netcdf.c. The decision is currently based on
the following pieces of information.

The file path – this can be used to detect, for example, a DAP url
versus a normal file system file.

The mode argument – this can be used to detect, for example, what kind
of file to create: netcdf-3, netcdf-4, 64-bit netcdf-3, etc.  For
nc_open and when the file path references a real file, the contents of
the file can also be used to determine the dispatch table.  Although
currently not used, this code could be modified to also use other
pieces of information such as environment variables.

In addition to the above, there is one additional mechanism to force
the use of a specific dispatch table. The procedure
"NC_set_dispatch_override()" can be invoked to specify a dispatch
table.

When adding a new dispatcher, it is necessary to modify NC_create and
NC_open in libdispatch/netcdf.c to detect when it is appropriate to
use the NCM dispatcher. Some possibilities are as follows.
- Add a new mode flag: say NC_NETCDFM.
- Use an environment variable.
- Define a special file path format that indicates the need to use a
  special dispatch table.

\section special_dispatch Special Dispatch Table Signatures.

Several of the entries in the dispatch table are significantly
different than those of the external API.

\section create_open_dispatch Create/Open

The create table entry and the open table entry have the following
signatures respectively.

\code
     int (*create)(const char *path, int cmode,
                size_t initialsz, int basepe, size_t *chunksizehintp,
                int useparallel, MPI_Comm comm, MPI_Info info,
                struct NC_Dispatch*, struct NC** ncp);
\endcode

\code
     int (*open)(const char *path, int mode,
              int basepe, size_t *chunksizehintp,
              int use_parallel, MPI_Comm comm, MPI_Info info,
              NC_Dispatch*, NC** ncp);
\endcode

The key difference is that these are the union of all the possible
create/open signatures from the netcdf.h API. Note especially the last
two parameters. The dispatch table is included in case the create
function (e.g. NCM_create) needs to invoke other dispatch
functions. The very last parameter is a pointer to a pointer to an NC
instance. It is expected that the create function will allocate and
fill in an instance of an "NC" object and return a pointer to it in
the ncp parameter.

\page dispatch_notes Dispatch Programming Notes

As with the existing code, and when MPI is not being used, the comm
and info parameters should be passed in as 0. This is taken care of in
the nc_open() and nc_create() API procedures in libdispatch/netcdf.c.

In fact, the object returned in the ncp parameter does not actually
have to be an instance of struct NC. It only needs to "look like it
for the first few fields. This is, in effect, a fake version of
subclassing. Let us suppose that the NCM_create function uses a struct
NCM object. The initial part of the definition of NCM must match the
fields at the beginning of struct NC between the comments BEGIN_COMMON
and END_COMMON. So, we would have the following.

\code
              typedef struct NCM {
              /*BEGIN COMMON*/
                      int ext_ncid; /* uid Â«Â« 16 */
                      int int_ncid; /* unspecified other id */
                      struct NC_Dispatch* dispatch;
              #ifdef USE_DAP
                      struct NCDRNO* drno;
              #endif
              /*END COMMON*/
              ...
              } NCM;
\endcode

This allows the pointer to the NCM object to be cast as an instance of
NC* and its pointer returned in the ncp file. Eventually, this will be
replaced with a separate structure containing the common fields.

\page put_vara_dispatch Accessing Data with put_vara() and get_vara()

\code
     int (*put_vara)(int ncid, int varid, const size_t *start, const size_t *count,
                          const void *value, nc_type memtype);
\endcode

\code
     int (*get_vara)(int ncid, int varid, const size_t *start, const size_t *count,
                     void *value, nc_type memtype);
\endcode

Most of the parameters are similar to the netcdf API parameters. The
last parameter, however, is the type of the data in
memory. Additionally, instead of using an "int islong" parameter, the
memtype will be either ::NC_INT or ::NC_INT64, depending on the value
of sizeof(long). This means that even netcdf-3 code must be prepared
to encounter the ::NC_INT64 type.

\page put_attr_dispatch Accessing Attributes with put_attr() and get_attr()

\code
     int (*get_att)(int ncid, int varid, const char *name,
                         void *value, nc_type memtype);
\endcode

\code
     int (*put_att)(int ncid, int varid, const char *name, nc_type datatype, size_t len,
                    const void *value, nc_type memtype);
\endcode

Again, the key difference is the memtype parameter. As with
put/get_vara, it used ::NC_INT64 to encode the long case.

*/
