/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA 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.
 *
 * This program 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 MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mia/core/filter.hh>

#include <addons/hdf5/hdf5a_mia.hh>
#include <addons/hdf5/hdf5_3dimage.hh>

NS_BEGIN(hdf5_3dimage)
using namespace mia;

using std::string;
using std::vector;
using std::stringstream;




CHDF53DImageIOPlugin::CHDF53DImageIOPlugin():
       C3DImageIOPlugin("hdf5")
{
       add_supported_type(it_bit);
       add_supported_type(it_sbyte);
       add_supported_type(it_ubyte);
       add_supported_type(it_sshort);
       add_supported_type(it_ushort);
       add_supported_type(it_sint);
       add_supported_type(it_uint);
       add_supported_type(it_slong);
       add_supported_type(it_ulong);
       add_supported_type(it_float);
       add_supported_type(it_double);
       add_suffix(".h5");
       add_suffix(".H5");
       add_property(io_plugin_property_multi_record);
       add_property(io_plugin_property_has_attributes);
       // enable some translators for post-conversion
       CVoxelAttributeTranslator::register_for("voxel");
       C3DIntAttributeTranslator::register_for("ca");
       C3DIntAttributeTranslator::register_for("cp");
}

struct HDF5ReadCallbackdata {
       CHDF53DImageIOPlugin::Data& result;
       string path;
       H5Base& id;
};

herr_t hdf5_walk (hid_t loc_id, const char *name, const H5L_info_t *MIA_PARAM_UNUSED(info),
                  void *operator_data)
{
       H5O_info_t      infobuf;
       HDF5ReadCallbackdata *cbd = reinterpret_cast<HDF5ReadCallbackdata *>(operator_data);
       herr_t status = H5Oget_info_by_name (loc_id, name, &infobuf, H5P_DEFAULT);

       if (status < 0) {
              cvdebug() << "hdf5_walk: H5Oget_info_by_name returned " << status
                        << " for '" << name << "' with current path '"  << cbd->path << "'\n";
       }

       string path = cbd->path + string("/") + string(name);

       switch (infobuf.type) {
       case H5O_TYPE_GROUP: { // recursion
              cvdebug() << "Open group '" << name << "'\n";
              string sname(name);
              H5Base group =  H5Group::open(cbd->id, sname);
              HDF5ReadCallbackdata rec_cbd = {cbd->result, path, group};
              status = H5Literate (group, H5_INDEX_NAME, H5_ITER_NATIVE, NULL, hdf5_walk, &rec_cbd);
              cvdebug() << "hdf5_walk:H5Literate returned with status " << status << "\n";
       }
       break;

       case H5O_TYPE_DATASET:
       case H5O_TYPE_NAMED_DATATYPE: {
              cvdebug() << "Found data set\n";
              auto dataset = H5Dataset::open(cbd->id, name);
              auto size = dataset.get_size();

              if (size.size() != 3) {
                     cvdebug() << "HDF5 (3dimage): found " << size.size() << " data set, ignoring\n";
                     break;
              }

              C3DBounds bsize(size[2], size[1], size[0]);
              H5Type file_type(H5Dget_type(dataset));
              H5Type mem_type = file_type.get_native_type();
              int type_id = mem_type.get_mia_type_id();
              P3DImage new_image;

              switch (type_id) {
              case EAttributeType::attr_bool:
                     new_image = read_image<C3DBitImage, bool>(bsize, dataset);
                     break;

              case EAttributeType::attr_uchar:
                     new_image = read_image<C3DUBImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_schar:
                     new_image = read_image<C3DSBImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_ushort:
                     new_image = read_image<C3DUSImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_sshort:
                     new_image = read_image<C3DSSImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_uint:
                     new_image = read_image<C3DUIImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_sint:
                     new_image = read_image<C3DSIImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_ulong:
                     new_image = read_image<C3DULImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_slong:
                     new_image = read_image<C3DSLImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_float:
                     new_image = read_image<C3DFImage>(bsize, dataset);
                     break;

              case EAttributeType::attr_double:
                     new_image = read_image<C3DDImage>(bsize, dataset);
                     break;

              default:
                     cverr() << "HDF5 (3dimage): Found unsupported image pixel type " << type_id << ", skipping.\n";
                     return 0;
              }

              new_image->set_attribute("path", path);
              cbd->result.push_back(new_image);
       }
       break;

       default:
              cvdebug() << "HDF5 read: ignoring unknown '" << name << "'\n";
       };

       return 0;
}


CHDF53DImageIOPlugin::PData CHDF53DImageIOPlugin::do_load(const string&  filename) const
{
       PData result(new Data);
       H5File file = H5File::open(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);

       if (file < 0) {
              // either the file doesn't exist or it is not a HDF5 file
              return PData();
       }

       HDF5ReadCallbackdata cbd = {*result, "", file};
       auto status = H5Literate (file, H5_INDEX_NAME, H5_ITER_NATIVE, NULL, hdf5_walk, &cbd);
       cvdebug() << "CHDF53DImageIOPlugin::do_load: H5Literate returned with status "
                 << status << "\n";
       return result;
}

class FHDF5Saver: public TFilter<void>
{
public:
       FHDF5Saver(H5File& file): m_file(file), m_id(0) {}

       template <typename T>
       void operator ()( const T3DImage<T>& image);
private:
       H5File& m_file;
       int m_id;

};

template <typename T>
void FHDF5Saver::operator ()( const T3DImage<T>& image)
{
       vector<hsize_t> dims = {image.get_size().z, image.get_size().y, image.get_size().x};
       auto space = H5Space::create(dims);
       auto file_type = Mia_to_h5_types<T>::file_datatype();
       stringstream path_str;
       path_str << "/mia/" << m_id++;
       auto path = path_str.str();

       if (image.has_attribute("hdf5-path")) {
              auto stored_path = image.get_attribute("hdf5-path");

              if (stored_path->type_id() == EAttributeType::attr_string) {
                     string s = stored_path->as_string();

                     if (s[0] == '/') {
                            path = s;
                     } else {
                            cvwarn() << "HDF5 save: image has path " << s << " without leading '/', ignoring";
                     }
              }
       }

       cvdebug() << "Add image to '" << path << "'\n";
       auto dataset = H5Dataset::create(m_file, path.c_str(), file_type, space);
       dataset.write_data(image, T());
       translate_to_hdf5_attributes(dataset, image);
}


const std::string CHDF53DImageIOPlugin::do_get_preferred_suffix() const
{
       return "h5";
}

bool CHDF53DImageIOPlugin::do_save(const string& fname, const Data& data) const
{
       H5File file = H5File::create(fname.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
       FHDF5Saver saver(file);

       for (auto i = data.begin(); i != data.end(); ++i)
              mia::accumulate(saver, **i);

       return true;
}

const std::string CHDF53DImageIOPlugin::do_get_descr() const
{
       return "HDF5 3D image IO";
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
       return new CHDF53DImageIOPlugin;
}


NS_END
