/// nc-complex: A lightweight, drop-in extension for complex number support in
/// netCDF
///
/// Copyright (C) 2023 Peter Hill
///
/// SPDX-License-Identifier: MIT

#ifndef PLASMA_FAIR_NC_COMPLEX
#define PLASMA_FAIR_NC_COMPLEX

// This header is required when building as a DLL on Windows and is
// automatically generated by CMake. If you're not using CMake (and
// not on Windows) for some reason, then define `NC_COMPLEX_NO_EXPORT`
// to skip this.
#ifndef NC_COMPLEX_NO_EXPORT
#include "nc_complex/nc_complex_export.h"
#else
#define NC_COMPLEX_EXPORT
#endif

#include <complex.h>
#include <netcdf.h>
#include <stdbool.h>
#include <stddef.h>

#ifdef __cplusplus
#include <complex>
#endif

//@{
/// Portable typedefs for complex numbers
///
/// These become aliases for `std::complex` with C++.
#ifdef _MSC_VER
typedef _Dcomplex double_complex;
typedef _Fcomplex float_complex;
#else
#if defined(__cplusplus) && defined(__clang__)
using double_complex = std::complex<double>;
using float_complex = std::complex<float>;
#else
typedef double _Complex double_complex;
typedef float _Complex float_complex;
#endif
#endif
//@}

#ifdef __cplusplus
/// @name Helper functions
///@{
/// Helper functions for converting between (pointers to) C++ and C complex types
NC_COMPLEX_EXPORT inline double_complex* cpp_to_c_complex(std::complex<double>* data) {
    return reinterpret_cast<double_complex*>(data);
}

NC_COMPLEX_EXPORT inline std::complex<double>* c_to_cpp_complex(double_complex* data) {
    return reinterpret_cast<std::complex<double>*>(data);
}

NC_COMPLEX_EXPORT inline float_complex* cpp_to_c_complex(std::complex<float>* data) {
    return reinterpret_cast<float_complex*>(data);
}

NC_COMPLEX_EXPORT inline std::complex<float>* c_to_cpp_complex(float_complex* data) {
    return reinterpret_cast<std::complex<float>*>(data);
}
///@}
extern "C" {
#endif

/// @name Complex datatype defines
/// Datatype for complex numbers, for use with \rstref{pfnc_def_var}
///  
/// @note
/// These *only* work when defining a variable with \rstref{pfnc_def_var}. To
/// check the type of an existing variable use \rstref{pfnc_var_is_complex}, and
/// to check if it is specifically using a compound datatype or a dimension use
/// \rstref{pfnc_var_is_complex_type} or \rstref{pfnc_var_has_complex_dimension}
/// respectively.
/// @endnote  
///@{  

/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
#define PFNC_FLOAT_COMPLEX (NC_FIRSTUSERTYPEID - 4)
/// Always use a complex dimension, regardless of file format
#define PFNC_FLOAT_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 3)
/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
#define PFNC_DOUBLE_COMPLEX (NC_FIRSTUSERTYPEID - 2)
/// Always use a complex dimension, regardless of file format
#define PFNC_DOUBLE_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 1)
///@}

/// Return true if variable is complex
NC_COMPLEX_EXPORT bool pfnc_var_is_complex(int ncid, int varid);
/// Return true if variable is complex and uses a compound datatype
NC_COMPLEX_EXPORT bool pfnc_var_is_complex_type(int ncid, int varid);
/// Return true if variable is complex and has a complex dimension
/// (assumed to be the last dimension)
NC_COMPLEX_EXPORT bool pfnc_var_has_complex_dimension(int ncid, int varid);

/// Return true if dimension is complex
NC_COMPLEX_EXPORT bool pfnc_is_complex_dim(int ncid, int dim_id);

/// Get the ID for the complex datatype with `double` elements, creating it if it doesn't already exist
NC_COMPLEX_EXPORT int pfnc_get_double_complex_typeid(int ncid, nc_type* nc_typeid);
/// Get the ID for the complex datatype with `float` elements, creating it if it doesn't already exist
NC_COMPLEX_EXPORT int pfnc_get_float_complex_typeid(int ncid, nc_type* nc_typeid);

/// Get complex dimension, creating one if it doesn't already exist
NC_COMPLEX_EXPORT int pfnc_get_complex_dim(int ncid, int* nc_dim);

/// Get the base numerical type of a complex type
///
/// Returns the type of the components for a compound type, or the
/// type of an element for a dimension type.
NC_COMPLEX_EXPORT int pfnc_complex_base_type(
    int ncid, nc_type nc_typeid, nc_type* base_type_id
);

/// Get the base numerical type of a complex variable
NC_COMPLEX_EXPORT int pfnc_inq_var_complex_base_type(
    int ncid, int varid, nc_type* nc_typeid
);

/// Return some information about the `nc-complex` library
NC_COMPLEX_EXPORT const char* pfnc_inq_libvers(void);

/// @name Wrappers
/// Wrappers for the equivalent `nc_*` functions that correctly handle
/// the start/count/stride arrays for complex dimensions.
///
/// When the variable is stored using a complex dimension, the file
/// representation has one more dimension than the user-visible
/// in-memory representation. For example, a 1D array:
///
/// ```c
/// double_complex data[5];
/// ```
///
/// would be represented in the file with two dimensions (when not
/// using a compound datatype!), and so if we use the standard netCDF
/// API we would need to use `{5, 2}` for the `countp` arguments, for
/// example, while using nc-complex, we only need `{5}`.
///  
/// NOTE: The `pfnc_put/get*` functions do *not* currently handle
/// conversion between `float/double` base types
///@{

/// Extension to `nc_def_var` that also accepts
/// \rstref{PFNC_FLOAT_COMPLEX}, \rstref{PFNC_FLOAT_COMPLEX_DIM},
/// \rstref{PFNC_DOUBLE_COMPLEX}, and \rstref{PFNC_DOUBLE_COMPLEX_DIM}
NC_COMPLEX_EXPORT int pfnc_def_var(
    int ncid,
    const char* name,
    nc_type xtype,
    int ndims,
    const int* dimidsp,
    int* varidp
);

NC_COMPLEX_EXPORT int pfnc_put_vara_double_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const double_complex* op
);

NC_COMPLEX_EXPORT int pfnc_get_vara_double_complex(
    int ncid, int varid, const size_t* startp, const size_t* countp, double_complex* ip
);

NC_COMPLEX_EXPORT int pfnc_put_vars_double_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    const double_complex* op
);

NC_COMPLEX_EXPORT int pfnc_get_vars_double_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    double_complex* ip
);

NC_COMPLEX_EXPORT int pfnc_put_var1_double_complex(
    int ncid, int varid, const size_t* indexp, const double_complex* data
);
NC_COMPLEX_EXPORT int pfnc_get_var1_double_complex(
    int ncid, int varid, const size_t* indexp, double_complex* data
);

NC_COMPLEX_EXPORT int pfnc_put_vara_float_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const float_complex* op
);

NC_COMPLEX_EXPORT int pfnc_get_vara_float_complex(
    int ncid, int varid, const size_t* startp, const size_t* countp, float_complex* ip
);

NC_COMPLEX_EXPORT int pfnc_put_vars_float_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    const float_complex* op
);

NC_COMPLEX_EXPORT int pfnc_get_vars_float_complex(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    float_complex* ip
);

NC_COMPLEX_EXPORT int pfnc_put_var1_float_complex(
    int ncid, int varid, const size_t* indexp, const float_complex* data
);
NC_COMPLEX_EXPORT int pfnc_get_var1_float_complex(
    int ncid, int varid, const size_t* indexp, float_complex* data
);

NC_COMPLEX_EXPORT int pfnc_inq_var(
    int ncid,
    int varid,
    char* name,
    nc_type* xtypep,
    int* ndimsp,
    int* dimidsp,
    int* nattsp
);

// NOLINTBEGIN(modernize-use-nullptr)
NC_COMPLEX_EXPORT inline int pfnc_inq_varndims(int ncid, int varid, int* ndimsp) {
    return pfnc_inq_var(ncid, varid, NULL, NULL, ndimsp, NULL, NULL);
}
NC_COMPLEX_EXPORT inline int pfnc_inq_vardimid(int ncid, int varid, int* dimidsp) {
    return pfnc_inq_var(ncid, varid, NULL, NULL, NULL, dimidsp, NULL);
}
// NOLINTEND(modernize-use-nullptr)

NC_COMPLEX_EXPORT int pfnc_def_var_chunking(
    int ncid, int varid, int storage, const size_t* chunksizesp
);
NC_COMPLEX_EXPORT int pfnc_inq_var_chunking(
    int ncid, int varid, int* storagep, size_t* chunksizesp
);

NC_COMPLEX_EXPORT int pfnc_get_vara(
    int ncid, int varid, const size_t* startp, const size_t* countp, void* ip
);
NC_COMPLEX_EXPORT int pfnc_get_vars(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    void* ip
);

NC_COMPLEX_EXPORT int pfnc_put_vara(
    int ncid, int varid, const size_t* startp, const size_t* countp, const void* op
);

NC_COMPLEX_EXPORT int pfnc_put_vars(
    int ncid,
    int varid,
    const size_t* startp,
    const size_t* countp,
    const ptrdiff_t* stridep,
    const void* op
);
///@}

#ifdef __cplusplus
}
#endif

#endif
