#ifdef WITH_HDF5
#ifndef TBPLAS_BASE_IO_HDF5_H
#define TBPLAS_BASE_IO_HDF5_H

#include <complex>
#include <cstdint>

#include <H5Cpp.h>
#include <eigen3/Eigen/Dense>

#include "io_base.h"

namespace tbplas::base {

/**
 * @brief Interface for HDF5-based file.
 *
 */
class HDFFile final : public BaseFile {
public:
    HDFFile() = delete;

    /**
     * @brief Construct a new HDFFile object
     *
     * @param[in] filePath path to the file to open without suffix, may contain directories,
     *   current directory by default
     * @param[in] mode opening mode of the file
     */
    explicit HDFFile(
        const std::string& filePath,
        OptBinMode mode = OptBinMode::READONLY);
    ~HDFFile() final;

    /**
     * @brief Open a HDF5 file.
     *
     * @param[in] filePath path to the file to open without suffix, may contain directories,
     *   current directory by default
     * @param[in] mode opening mode of the file
     * @return true if the file is opened successfully
     * @return false if failed to open the file
     */
    [[nodiscard]] bool open(
        const std::string& filePath,
        OptBinMode mode = OptBinMode::READONLY);

    /**
     * @brief Save cache to file and close.
     *
     */
    void close();

    OptStatus setMatrixXi(const std::string& key, const Eigen::MatrixXi& value);
    OptStatus setMatrixXd(const std::string& key, const Eigen::MatrixXd& value);
    OptStatus setMatrixXcd(const std::string& key, const Eigen::MatrixXcd& value);
    OptStatus getMatrixXi(const std::string& key, Eigen::MatrixXi& value) const;
    OptStatus getMatrixXd(const std::string& key, Eigen::MatrixXd& value) const;
    OptStatus getMatrixXcd(const std::string& key, Eigen::MatrixXcd& value) const;

    OptStatus getMatrixXdShape(const std::string& key, uint64_t& num_rows, uint64_t& num_cols) const;
    OptStatus getMatrixXcdShape(const std::string& key, uint64_t& num_rows, uint64_t& num_cols) const;
    OptStatus getMatrixXd(const std::string& key, double* data) const;
    OptStatus getMatrixXcd(const std::string& key, std::complex<double>* data) const;

    /// @brief Wrapper functions with status check
    void save_matrix_integer(const std::string& key, const Eigen::MatrixXi& value);
    void save_matrix_real(const std::string& key, const Eigen::MatrixXd& value);
    void save_matrix_complex(const std::string& key, const Eigen::MatrixXcd& value);
    void read_matrix_integer(const std::string& key, Eigen::MatrixXi& value) const;
    void read_matrix_real(const std::string& key, Eigen::MatrixXd& value) const;
    void read_matrix_complex(const std::string& key, Eigen::MatrixXcd& value) const;

    void read_matrix_real_shape(const std::string& key, uint64_t& num_rows, uint64_t& num_cols) const;
    void read_matrix_complex_shape(const std::string& key, uint64_t& num_rows, uint64_t& num_cols) const;
    void read_matrix_real(const std::string& key, double* data) const;
    void read_matrix_complex(const std::string& key, std::complex<double>* data) const;

private:
    /**
     * @brief Find given group in HDF5 file.
     *
     * @param[in] groupName group name, must be in format of XX.XX.XX
     * @param[in] target target group to hold the result, kept as it is if
     *   given group not found
     * @return true if group is found in HDF5 file
     * @return false if group not found
     */
    [[nodiscard]] bool findH5Group(
        const std::string& groupName,
        H5::Group& target) const;

    /**
     * @brief Create given group in HDF5 file.
     *
     * @param[in] key key of the node, must in format of XX.XX.XX
     * @return created group, do nothing if already exists
     */
    [[nodiscard]] H5::Group buildH5Group(const std::string& groupName);

    /**
     * @brief Check if the file is writable
     *
     * @return true if NOT opened in READONLY mode
     * @return false if opened in READONLY mode
     */
    [[nodiscard]] bool isWritable() const final;

    /// @brief HDF5 instance
    H5::H5File m_H5File;

    /// @brief Opening mode of the file
    OptBinMode m_FileOptMode = OptBinMode::READONLY;
};

/**
 * @brief Check the status of I/O operations.
 *
 * @param[in] task identifier of operation
 * @param[in] key key of dataset
 * @param[in] status status of operation
 */
void check_opt_status(
    const std::string& task,
    const std::string& key,
    OptStatus status);

} // namespace tbplas::base
#endif // TBPLAS_BASE_IO_HDF5_H
#endif // WITH_HDF5