/*****************************************************************************
 *
 *  This file is part of the Data Logging Service (DLS).
 *
 *  DLS is free software: you can redistribute it and/or modify it under the
 *  terms of the GNU General Public License as published by the Free Software
 *  Foundation, either version 3 of the License, or (at your option) any later
 *  version.
 *
 *  DLS is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with DLS. If not, see <http://www.gnu.org/licenses/>.
 *
 ****************************************************************************/

#include <iostream>
#include <sstream>
#include <limits>
#include <iomanip>
#include <algorithm>
#include <memory>
using namespace std;

#include "LibDLS/Export.h"
#include "LibDLS/Job.h"
#include "LibDLS/JobPreset.h"
#include "LibDLS/Time.h"
#include "LibDLS/Dir.h"

#include "Export_p.h"
#include "File.h"

using namespace LibDLS;

//#define DO_HDF5_DEBUG

#include "hdf5.h"
#include <H5Cpp.h>

/*****************************************************************************
 * Export::ImplHDF5
 ****************************************************************************/

class ExportHDF5::ImplHDF5 : public Export::Impl {
public:
    ImplHDF5();
    template<class Node>
    static void CreateAttrString(
			  Node& node,
			  const string &attr,
			  const string &value
			  );
    template<class Node>
    static void CreateAttrInteger(
			   Node& node,
			   const string &attr,
			   const int &value
			   );
    template<class Node>
    static void CreateAttrDouble(
			  Node& node,
			  const string &attr,
			  const double &value
			  );
    template<class Node>
    static void CreateAttrBool(
			  Node& node,
			  const string &attr,
			  const bool &value
			  );
    void dimout(const string label, const int rank, hsize_t *dim);
    H5::DataType mapType(const ChannelType &type);
    std::unique_ptr<H5::H5File> h5file;
    H5::DataSet active_dataset;
    H5::DataSet message_dataset;
    ChannelType type_dataset;
    H5::DataSet active_time;
    hsize_t offset[1];
    hsize_t message_offset[1];
    stringstream filepath;
    static constexpr int rank = 1;

    struct DatasourceTree : H5::Group
    {
        static constexpr const char *datasource_prefix = "/Datasources";

        H5::Group getGroup(
            const Directory &dls_dir,
            int job_id,
            const std::string& path
            );

        explicit DatasourceTree(H5::Group&& group) : Group(group) {}
        DatasourceTree() {}
    } datasource_tree;

    /* Basic message composed data structure */
    struct message_hdf5 {
        char type;
        const char *typetext;
        double time;
        const char *text;
    };
    H5::CompType compMessageType;

    H5::DataSet createDataSet(const std::string& name, H5::DataType type);
};

/****************************************************************************/

ExportHDF5::ImplHDF5::ImplHDF5():
    Impl(),
    active_dataset{-1},
    message_dataset{-1},
    type_dataset{TUNKNOWN},
    active_time{-1},
    offset{0},
    message_offset{0},
    filepath{""},
    compMessageType(sizeof(message_hdf5))
{
    using H5::PredType;

    /* Prepare message handling */
    /* Prepare variable length string */
    H5::StrType textType{PredType::C_S1};
    textType.setSize(H5T_VARIABLE);
    textType.setCset(H5T_CSET_UTF8);
    textType.setStrpad(H5T_STR_NULLTERM);

    /* Insert members */
    compMessageType.insertMember(
	      "Type",
	      HOFFSET(ExportHDF5::ImplHDF5::message_hdf5, type),
	      PredType::NATIVE_CHAR
	      );
    compMessageType.insertMember(
	      "TypeText",
	      HOFFSET(ExportHDF5::ImplHDF5::message_hdf5, typetext),
	      textType);
    compMessageType.insertMember(
	      "Time",
	      HOFFSET(ExportHDF5::ImplHDF5::message_hdf5, time),
	      PredType::NATIVE_DOUBLE
	      );
    compMessageType.insertMember(
	      "Text",
	      HOFFSET(ExportHDF5::ImplHDF5::message_hdf5, text),
	      textType);
}

/****************************************************************************/

void ExportHDF5::ImplHDF5::dimout(const string label, const int rank,
        hsize_t *dim)
{
    cout << label << " Rank " << rank << " ";
    for(int i = 0; i < rank; i++){
        cout << dim[i] << " ";
    }
    cout << endl;
}

/****************************************************************************/

H5::DataType ExportHDF5::ImplHDF5::mapType(const ChannelType &type)
{
    using H5::PredType;

    switch (type){
        case TUNKNOWN:
        default:
            {
                stringstream err;
                err << "Unknown datatype " << type;
                throw ExportException(err.str());
            }
        case TCHAR:
            return PredType::NATIVE_CHAR;
        case TUCHAR:
            return PredType::NATIVE_UCHAR;
        case TSHORT:
            return PredType::NATIVE_SHORT;
        case TUSHORT:
            return PredType::NATIVE_USHORT;
        case TINT:
            return PredType::NATIVE_INT;
        case TUINT:
            return PredType::NATIVE_UINT;
        case TLINT:
            return PredType::NATIVE_LONG;
        case TULINT:
            return PredType::NATIVE_ULONG;
        case TFLT:
            return PredType::NATIVE_FLOAT;
        case TDBL:
            return PredType::NATIVE_DOUBLE;
    }
}

/****************************************************************************/

H5::DataSet ExportHDF5::ImplHDF5::createDataSet(const std::string &name,
        H5::DataType type)
{
    hsize_t maxdims[rank] = {H5S_UNLIMITED}; // Extendable
    hsize_t chunk_dims[rank] = {100}; // chunk size 100

    /* Create a new dataset within the file using chunk
       creation properties. */
    /* Values */
    H5::DataSpace dataspace{rank, offset, maxdims};
    /* Modify dataset creation properties, i. e. enable chunking  */
    H5::DSetCreatPropList prop_list;
    prop_list.setChunk(rank, chunk_dims);
    /* Enable compression */
    prop_list.setDeflate(9);

    return h5file->createDataSet(name, type, dataspace, prop_list);
}

/****************************************************************************/

template<class Node>
void ExportHDF5::ImplHDF5::CreateAttrString(
					    Node& node,
					    const string &attr,
					    const string &value
					    )
{
#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Create String attribute " << attr << " value " << value
        << endl;
#endif

    H5::StrType textType{H5::PredType::C_S1};
    textType.setSize(value.length() + 1);
    textType.setCset(H5T_CSET_UTF8);
    textType.setStrpad(H5T_STR_NULLTERM);
    H5::Attribute h5attr =
        node.createAttribute(attr.c_str(), textType, {H5S_SCALAR}, {});
    h5attr.write(textType, value.data());
}

/****************************************************************************/

template<class Node>
void ExportHDF5::ImplHDF5::CreateAttrDouble(
					    Node& node,
					    const string &attr,
					    const double &value
					    )
{

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Create Double attribute " << attr << " value " << value
        << endl;
#endif

    H5::Attribute h5attr = node.createAttribute(attr.c_str(),
            H5::PredType::NATIVE_DOUBLE, {H5S_SCALAR}, {});
    h5attr.write(H5::PredType::NATIVE_DOUBLE, &value);
}

/****************************************************************************/

template<class Node>
void ExportHDF5::ImplHDF5::CreateAttrInteger(
					     Node& node,
					     const string &attr,
					     const int &value
					     )
{
#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Create Integer attribute " << attr << " value " << value
        << endl;
#endif

    H5::Attribute h5attr = node.createAttribute(attr.c_str(),
            H5::PredType::NATIVE_INT, {H5S_SCALAR}, {});
    h5attr.write(H5::PredType::NATIVE_INT, &value);
}

/****************************************************************************/

template<class Node>
void ExportHDF5::ImplHDF5::CreateAttrBool(
					    Node& node,
					    const string &attr,
					    const bool &value
					    )
{

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Create Bool attribute " << attr << " value " << value
        << endl;
#endif

    H5::Attribute h5attr = node.createAttribute(attr.c_str(),
            H5::PredType::NATIVE_HBOOL, {H5S_SCALAR}, {});
    h5attr.write(H5::PredType::NATIVE_HBOOL, &value);
}

/*****************************************************************************
 * Export HDF5
 ****************************************************************************/

ExportHDF5::ExportHDF5():
    Export(new ImplHDF5())
{
}

/****************************************************************************/

ExportHDF5::~ExportHDF5() = default;

/****************************************************************************/

void ExportHDF5::open(
        const string &path,
        const string &filename,
        const Time &start,
        const Time &end
        )
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Open " << path << " / " << filename << endl;
#endif

    impl.filepath << path << "/" << filename << ".h5";

    try {
        impl.h5file.reset(new H5::H5File(impl.filepath.str().c_str(),
                    H5F_ACC_TRUNC));
    } catch (const std::exception& ex) {
        std::stringstream err;
        err << "Failed to open file \"" << impl.filepath.str() << "\": "
            << ex.what() << "!";
        throw ExportException(err.str());
    } catch (const H5::Exception& ex) {
        std::stringstream err;
        err << "Failed to open file \"" << impl.filepath.str() << "\": "
            << ex.getDetailMsg() << "!";
        throw ExportException(err.str());
    } catch (...) {
        std::stringstream err;
        err << "Failed to open file \"" << impl.filepath.str()
            << "\" for unknown reason!";
        throw ExportException(err.str());
    }


    Time now;
    now.set_now();
    /* FIXME use HDF5 internal time datatype */
    impl.CreateAttrString(*impl.h5file, "Data_Start_Time",
            start.to_real_time());
    impl.CreateAttrString(*impl.h5file, "Data_End_Time", end.to_real_time());
    impl.CreateAttrString(*impl.h5file, "Export_Time", now.to_rfc811_time());
    impl.CreateAttrBool(*impl.h5file, "Relative_Times",
            _impl->referenceTime != 0.0);
    impl.CreateAttrDouble(*impl.h5file, "Reference_Time",
            _impl->referenceTime.to_dbl_time());
    impl.CreateAttrBool(*impl.h5file, "Data_Trimmed", _impl->trim);

    /* Values */
    hsize_t maxdims[impl.rank] = {H5S_UNLIMITED}; // Extendable
    H5::DataSpace dataspace{impl.rank, impl.message_offset, maxdims};
    H5::DSetCreatPropList prop_list;
    hsize_t chunk_dims[impl.rank] = {1}; // Chunk size 1
    prop_list.setChunk(impl.rank, chunk_dims);
    prop_list.setDeflate(9); // Enable compression

    impl.message_dataset = impl.h5file->createDataSet(
            "/Messages", impl.compMessageType, dataspace, prop_list);

    impl.datasource_tree = ImplHDF5::DatasourceTree {
        impl.h5file->createGroup(ImplHDF5::DatasourceTree::datasource_prefix)
    };
}

/****************************************************************************/

void ExportHDF5::close()
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Close " << endl;
#endif
    if (!impl.h5file) {
        return;
    }

    try {
        impl.h5file->close();
    } catch (const H5::Exception& ex) {
        std::stringstream err;
        err << "Failed to close file \"" << impl.filepath.str() << "\":"
            << ex.getDetailMsg() <<  "!";
        throw ExportException(err.str());
    }
    impl.h5file.reset();
}

/****************************************************************************/

void ExportHDF5::begin(
        const Directory &dls_dir,
        const Channel &channel,
        const string &path,
        const string &filename
        )
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);

    if (!impl.h5file) {
        throw ExportException("HDF5 file has not been openend!");
    }

    Job *job = channel.getJob();
    const JobPreset jpreset = job->preset();
    impl.type_dataset = channel.type();

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Job id " << job->id() << endl;
    cout << "HDF5 Job id description " << jpreset.id_desc() << endl;
    cout << "HDF5 Job description " << jpreset.description() << endl;
    cout << "HDF5 Job owner " << jpreset.owner() << endl;
    cout << "HDF5 Job src " << jpreset.source() << endl;
    cout << "HDF5 Job trigger " << jpreset.trigger() << endl;
#endif

    impl.offset[0] = 0;

    stringstream dataset_name;
    stringstream time_name;
    stringstream dlsurl;
    stringstream msrurl;

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Begin " << channel.name() << endl;
#endif

    /* Create group for values and times */
    auto grp = impl.datasource_tree.getGroup(
            dls_dir, job->id(), channel.name());

    if (!filename.empty()) {
        /* Create a softlink */
        grp.link(grp.getObjName(), "/" + filename);
    }

    /* Build up data names */
    dataset_name << grp.getObjName() << "/" << "Value";
    time_name << grp.getObjName() << "/" << "Time";
    dlsurl << dls_dir.uri() << channel.name();
    msrurl << "msr://"<< jpreset.source() << ":" << jpreset.port()
        << channel.name();

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 dataset name " << dataset_name.str() << endl;
    cout << "HDF5 time name " << time_name.str() << endl;
    cout << "HDF5 create initial dataset vector" << endl;
#endif

    impl.active_dataset = impl.createDataSet(dataset_name.str(),
            impl.mapType(impl.type_dataset)
            );

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 create initial time vector" << endl;
#endif

    impl.active_time = impl.createDataSet(time_name.str(),
            H5::PredType::NATIVE_DOUBLE
            );

    /* Add some dataset properties */

    impl.CreateAttrString(grp, "MSR_URL", msrurl.str());
    impl.CreateAttrString(grp, "MSR_Host", jpreset.source());
    impl.CreateAttrInteger(grp, "MSR_Port", jpreset.port());
    impl.CreateAttrString(grp, "MSR_Path", channel.name());

    impl.CreateAttrString(grp, "DLS_URL", dlsurl.str());
    impl.CreateAttrInteger(grp, "DLS_Job_ID", job->id());
    impl.CreateAttrString(grp, "DLS_Job_Description", jpreset.description());
    impl.CreateAttrInteger(grp, "DLS_Channel_Index", channel.dir_index());
    if (not channel.path().empty()) {
        impl.CreateAttrString(grp, "DLS_Channel_Directory", channel.path());
    }
    if (not channel.alias().empty()) {
        impl.CreateAttrString(grp, "DLS_Channel_Alias", channel.alias());
    }

    impl.CreateAttrString(impl.active_time, "Unit", "s");
    if (not channel.unit().empty()) {
        impl.CreateAttrString(impl.active_dataset, "Unit", channel.unit());
    }
}

/****************************************************************************/

void ExportHDF5::data(const Data *data)
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);
    unsigned int i;
    stringstream err;
    unsigned int trim_start_idx = 0;
    unsigned int data_size = 0;

#ifdef DO_HDF5_DEBUG
    impl.dimout("Old size", impl.rank, impl.offset);
#endif

    /* Prepare data space for this data block */
    /* Store old dimensions, used for offset */
    hsize_t olddim[impl.rank];
    olddim[0] = impl.offset[0];

    /* Check if we need to trim the data */
    if (!_impl->trim) {
       trim_start_idx = 0;
       data_size = data->size();
    }
    else {
        for (i = 0; i < data->size(); i++) {
            Time time(data->time(i));
            if (time >= _impl->trimStart ) {
                trim_start_idx = i;
                break;
            }
        }
        for (i = trim_start_idx; i < data->size(); i++) {
            Time time(data->time(i));
            if (time <= _impl->trimEnd) {
                data_size = i - trim_start_idx + 1;
            }
        }
    }

#ifdef DO_HDF5_DEBUG
    impl.dimout("Offset", impl.rank, olddim);
#endif

    /* dimension of new data */
    hsize_t newdim[impl.rank];
    newdim[0] = data_size;

#ifdef DO_HDF5_DEBUG
    impl.dimout("Add size", impl.rank, newdim);
#endif

    /* new complete space */
    impl.offset[0] += data_size;

#ifdef DO_HDF5_DEBUG
    impl.dimout("New size", impl.rank, impl.offset);
    cout << "HDF5 Data size " << impl.offset[0] << endl;
    cout << "HDF5 Data extend space " << endl;
#endif

    /* Extend the datasets to the new size */
    impl.active_dataset.extend(impl.offset);
    impl.active_time.extend(impl.offset);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Data get space " << endl;
#endif

    /* Select a hyperslab in extended portion of dataset */
    auto fs_dataset = impl.active_dataset.getSpace();
    auto fs_time = impl.active_time.getSpace();

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Data select hyperslab " << endl;
#endif
    /*
      olddim is the offset
      newdim is the selected range
     */
    fs_dataset.selectHyperslab(H5S_SELECT_SET, newdim, olddim);
    fs_time.selectHyperslab(H5S_SELECT_SET, newdim, olddim);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Data create mem space " << endl;
#endif
    /* Define memory space */
    H5::DataSpace space_dataset(impl.rank, newdim, NULL);
    H5::DataSpace space_time(impl.rank, newdim, NULL);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Data write values " << endl;
#endif

    /* Data access type, type specific */
    /* Prepare data buffer to write */
    switch (impl.type_dataset){
        case TCHAR:
            {
                char buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (char) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TUCHAR:
            {
                unsigned char buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] =
                        (unsigned char) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TSHORT:
            {
                short buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (short) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TUSHORT:
            {
                unsigned short buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] =
                        (unsigned short) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TINT:
            {
                int buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (int) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TUINT:
            {
                unsigned int buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] =
                        (unsigned int) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);

            }
            break;
        case TLINT:
            {
                long buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (long) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);

            }
            break;
        case TULINT:
            {
                unsigned long buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (int) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TFLT:
            {
                float buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = (float) data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        case TDBL:
            {
                double buf_val[data_size];
                for (i = 0; i < data_size; i++) {
                    buf_val[i] = data->value(trim_start_idx + i);
                }
                impl.active_dataset.write(buf_val,
                        impl.mapType(impl.type_dataset),
                        space_dataset,
                        fs_dataset);
            }
            break;
        default:
            err << "Unknown datatype during data handling!";
            throw ExportException(err.str());
    }

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 Data write time " << endl;
#endif
    double buf_time[data_size];

    for (i = 0; i < data_size; i++) {
        auto time(data->time(trim_start_idx + i) - _impl->referenceTime);
        buf_time[i] = time.to_dbl_time();
    }

    impl.active_time.write(buf_time, H5::PredType::NATIVE_DOUBLE, space_time,
            fs_time);
}

/****************************************************************************/

void ExportHDF5::addMessage(const Job::Message &m)
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);

    hsize_t olddim[impl.rank];
    olddim[0] = impl.message_offset[0];

    /* dimension of new data */
    hsize_t newdim[impl.rank];
    newdim[0] = 1;

    /* new complete space */
    impl.message_offset[0]+=1;

    /* Extend the datasets to the new size */
    impl.message_dataset.extend(impl.message_offset);

    /* Select a hyperslab in extended portion of dataset */
    auto fs = impl.message_dataset.getSpace();

    /*
      olddim is the offset
      newdim is the selected range
     */
    fs.selectHyperslab(H5S_SELECT_SET, newdim, olddim);

    /* Define memory space */
    H5::DataSpace space(impl.rank, newdim, NULL);

    /* Prepare data to write */
    ExportHDF5::ImplHDF5::message_hdf5 data;
    data.type = (int) m.type;
    data.typetext = m.type_str().c_str();
    data.time = (m.time - impl.referenceTime).to_dbl_time();
    data.text = m.text.c_str();

    /* Write the data */
    impl.message_dataset.write(&data, impl.compMessageType, space, fs);
}

/****************************************************************************/

void ExportHDF5::end()
{
    auto& impl = static_cast<ImplHDF5&>(*_impl);
#ifdef DO_HDF5_DEBUG
    cout << "HDF5 End " << endl;
#endif
    impl.active_dataset.close();
    impl.active_time.close();
}

/****************************************************************************/

static H5::Group ensureSubGroup(H5::Group& group, const std::string &name)
{
    if (group.nameExists(name)) {
#ifdef DO_HDF5_DEBUG
        cout << "HDF5 ensureSubGroup Group exists "<< name << endl;
#endif
        return group.openGroup(name);
    }
    else {
#ifdef DO_HDF5_DEBUG
        cout << "HDF5 ensureSubGroup Group create "<< name << endl;
#endif
        return group.createGroup(name);
    }
}

/****************************************************************************/

H5::Group ExportHDF5::ImplHDF5::DatasourceTree::getGroup(
    const Directory &dls_dir,
    int _job_id,
    const std::string &path)
{
    std::string locator;

    if (dls_dir.access() == Directory::Network){
        locator = dls_dir.host() + ":" + dls_dir.port();
    }
    else {
        locator = dls_dir.uri();
        for (size_t i = 0; i < locator.size(); i++) {
            if (locator[i] == '/') {
                locator[i] = '_';
            }
        }
    }
    std::string prefix = std::string(datasource_prefix) + "/" + locator;

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 getGroup start prefix " << prefix << endl;
#endif

    // find/create group for locator
    H5::Group destination_group = ensureSubGroup(*this, prefix);

    prefix = prefix + "/job" + std::to_string(_job_id);

#ifdef DO_HDF5_DEBUG
    cout << "HDF5 getGroup final prefix " << prefix << endl;
#endif

    // find/create group for job id
    destination_group = ensureSubGroup(*this, prefix);
    // descend tree, create groups from top to bottom
    auto begin = path.begin();
    while (begin != path.end()) {
        // skip trailing /
        ++begin;
        const auto next_slash = std::find(begin, path.end(), '/');
        // create group using the full path
        const std::string path_part{path.begin(), next_slash};
        if (!path_part.empty()) {
            destination_group =
                ensureSubGroup(destination_group, prefix + path_part);
        }
        begin = next_slash;
    }

    return destination_group;
}

/****************************************************************************/
