/*! \file file_io.cpp
 *
 * \brief Implementation of file I/O operations.
 */
#include <exception>
#include <regex>
#include <string>
#include <hdf5.h>

#ifndef INCLUDE_ERRORS_H_
#include "../include/errors.h"
#endif

#ifndef INCLUDE_LIST_H_
#include "../include/List.h"
#endif

#ifndef INCLUDE_FILE_IO_H_
#include "../include/file_io.h"
#endif

using namespace std;

FileSchema::FileSchema(int num_rec, const std::string *rec_types, const std::string *rec_names) {
  num_records = num_rec;
  record_types = new string[num_rec];
  record_names = new string[num_rec];
  for (int i = 0; i < num_rec; i++) {
    record_types[i] = rec_types[i];
    if (rec_names != NULL) record_names[i] = rec_names[i];
    else record_names[i] = "/dset" + to_string(i);
  }
}

FileSchema::~FileSchema() {
  delete[] record_names;
  delete[] record_types;
}

string* FileSchema::get_record_names() {
  string *rec_names = new string[num_records];
  for (int i = 0; i < num_records; i++) rec_names[i] = record_names[i];
  return rec_names;
}

string* FileSchema::get_record_types() {
  string *rec_types = new string[num_records];
  for (int i = 0; i < num_records; i++) rec_types[i] = record_types[i];
  return rec_types;
}

HDFFile::HDFFile(const std::string& name, unsigned int flags, hid_t fcpl_id, hid_t fapl_id) {
  file_name = name;
  if (flags == H5F_ACC_EXCL || flags == H5F_ACC_TRUNC)
    file_id = H5Fcreate(name.c_str(), flags, fcpl_id, fapl_id);
  else if (flags == H5F_ACC_RDONLY || flags == H5F_ACC_RDWR)
    file_id = H5Fopen(name.c_str(), flags, fapl_id);
  id_list = new List<hid_t>(1);
  id_list->set(0, file_id);
  if (file_id != H5I_INVALID_HID) file_open_flag = true;
  status = (herr_t)0;
}

HDFFile::~HDFFile() {
  if (H5Iis_valid(file_id) > 0) status = H5Fclose(file_id);
  delete id_list;
}

herr_t HDFFile::close() {
  status = H5Fclose(file_id);
  if (status == 0) file_open_flag = false;
  return status;
}

HDFFile* HDFFile::from_schema(
			      FileSchema &schema, const std::string& name, unsigned int flags,
			      hid_t fcpl_id, hid_t fapl_id
) {
  HDFFile *hdf_file = new HDFFile(name, flags, fcpl_id, fapl_id);
  hid_t file_id = hdf_file->get_file_id();
  herr_t status;
  string *rec_types = schema.get_record_types();
  string *rec_names = schema.get_record_names();
  string known_types[] = {"INT32", "FLOAT64", "COMPLEX128"};
  int rec_num = schema.get_record_number();
  regex re;
  smatch m;
  for (int ri = 0; ri < rec_num; ri++) {
    int rank = 0;
    hsize_t *dims, *max_dims;
    hid_t data_type;
    string str_target = rec_types[ri];
    int type_index = 0;
    bool found_type = false;
    while (!found_type) {
      re = regex(known_types[type_index++]);
      if (regex_search(str_target, m, re)) {
	found_type = true;
	str_target = m.suffix().str();
	if (type_index == 1) data_type = H5Tcopy(H5T_NATIVE_INT);
	else if (type_index == 2) data_type = H5Tcopy(H5T_NATIVE_DOUBLE);
	else if (type_index == 3) data_type = H5Tcopy(H5T_NATIVE_DOUBLE);
      }
      if (type_index == 3) break;
    }
    if (found_type) {
      re = regex("[0-9]+");
      string old_target = str_target;
      while (regex_search(str_target, m, re)) {
	rank++;
	str_target = m.suffix().str();
      }
      dims = new hsize_t[rank]();
      max_dims = new hsize_t[rank]();
      str_target = old_target;
      for (int ti = 0; ti < rank; ti++) {
	regex_search(str_target, m, re);
	hsize_t dim = (hsize_t)stoi(m.str());
	dims[ti] = dim;
	max_dims[ti] = dim;
	str_target = m.suffix().str();
      }
      int multiplier = (type_index == 3) ? 2 : 1;
      dims[rank - 1] *= multiplier;
      max_dims[rank - 1] *= multiplier;
      hid_t dataspace_id = H5Screate_simple(rank, dims, max_dims);
      hid_t dataset_id = H5Dcreate(
				   file_id, rec_names[ri].c_str(), data_type, dataspace_id, H5P_DEFAULT,
				   H5P_DEFAULT, H5P_DEFAULT
				   );
      status = H5Sclose(dataspace_id);
      status = H5Dclose(dataset_id);
      delete[] dims;
      delete[] max_dims;
    } else {
      string message = "unrecognized type \"" + rec_types[ri] + "\"\n";
      throw UnrecognizedParameterException(message);
    }
  }

  delete[] rec_types;
  delete[] rec_names;
  return hdf_file;
}

herr_t HDFFile::read(
		     const std::string& dataset_name, const std::string& data_type, void *buffer,
		     hid_t mem_space_id, hid_t file_space_id, hid_t dapl_id,
		     hid_t dxpl_id
) {
  string known_types[] = {"INT32", "FLOAT64"};
  regex re;
  smatch m;
  bool found_type = false;
  int type_index = 0;
  while (!found_type) {
    re = regex(known_types[type_index++]);
    found_type = regex_search(data_type, m, re);
    if (type_index == 2) break;
  }
  if (found_type) {
    hid_t dataset_id = H5Dopen2(file_id, dataset_name.c_str(), dapl_id);
    hid_t mem_type_id;
    switch (type_index) {
    case 1:
      mem_type_id = H5T_NATIVE_INT; break;
    case 2:
      mem_type_id = H5T_NATIVE_DOUBLE; break;
    default:
      throw UnrecognizedParameterException("unrecognized data type \"" + data_type + "\"");
    }
    if (dataset_id != H5I_INVALID_HID) {
      status = H5Dread(dataset_id, mem_type_id, mem_space_id, file_space_id, dxpl_id, buffer);
      if (status == 0) status = H5Dclose(dataset_id);
      else status = (herr_t)-2;
    } else {
      status = (herr_t)-1;
    }
  } else {
    throw UnrecognizedParameterException("unrecognized data type \"" + data_type + "\"");
  }
  return status;
}

herr_t HDFFile::write(
		      const std::string& dataset_name, const std::string& data_type, const void *buffer,
		      hid_t mem_space_id, hid_t file_space_id, hid_t dapl_id,
		      hid_t dxpl_id
) {
  string known_types[] = {"INT32", "FLOAT64", "COMPLEX128"};
  regex re;
  smatch m;
  bool found_type = false;
  int type_index = 0;
  while (!found_type) {
    re = regex(known_types[type_index++]);
    found_type = regex_search(data_type, m, re);
    if (type_index == 3) break;
  }
  if (found_type) {
    hid_t dataset_id = H5Dopen2(file_id, dataset_name.c_str(), dapl_id);
    hid_t mem_type_id;
    switch (type_index) {
    case 1:
      mem_type_id = H5T_NATIVE_INT; break;
    case 2:
      mem_type_id = H5T_NATIVE_DOUBLE; break;
    case 3:
      mem_type_id = H5T_NATIVE_DOUBLE; break;
    default:
      throw UnrecognizedParameterException("unrecognized data type \"" + data_type + "\"");
    }
    if (dataset_id != H5I_INVALID_HID) {
      status = H5Dwrite(dataset_id, mem_type_id, mem_space_id, file_space_id, dxpl_id, buffer);
      if (status == 0) status = H5Dclose(dataset_id);
      else status = (herr_t)-2;
    } else {
      status = (herr_t)-1;
    }
  } else {
    throw UnrecognizedParameterException("unrecognized data type \"" + data_type + "\"");
  }
  return status;
}
