/*******************************************************************************
 * This file is part of SWIFT.
 * Copyright (c) 2017 Matthieu Schaller (schaller@strw.leidenuniv.nl).
 *                    Peter W. Draper   (p.w.draper@durham.ac.uk)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/

/* Config parameters. */
#include <config.h>

/* Some standard headers. */
#include <libgen.h>
#include <stdio.h>
#include <string.h>

/* This object's header. */
#include "xmf.h"

/* Local headers. */
#include "common_io.h"
#include "error.h"

/**
 * @brief Return the basename of an HDF5 path.
 *
 * Need basename as XML paths are relative to the container, and XMF file is
 * written with the same baseName as the HDF5 snapshots.
 *
 * Note since the XMF file is written in the subdir (if it exists) we don't
 * want that as part of the name. But for distributed snapshots we need that
 * that directory.
 *
 * @param hdfFileName The full path to the HDF5 file
 * @param distributed Are we playing with a distributed snapshot?
 * @return the basename part of hdfFileName.
 */
static const char* xmf_basename(const char* hdfFileName,
                                const int distributed) {

  int pos_last_slash = strlen(hdfFileName) - 1;

  /* Find last slash */
  for (/* */; pos_last_slash >= 0; --pos_last_slash)
    if (hdfFileName[pos_last_slash] == '/') break;

  if (distributed) {
    /* Find one-before-last slash */
    for (pos_last_slash--; pos_last_slash >= 0; --pos_last_slash)
      if (hdfFileName[pos_last_slash] == '/') break;
  }

  static char buffer[FILENAME_BUFFER_SIZE];
  strcpy(buffer, &hdfFileName[pos_last_slash + 1]);

  return buffer;
}

/**
 * @brief Prepare the XMF file corresponding to a snapshot.
 *
 * @param fileName The name of the file.
 */
FILE* xmf_prepare_file(const char* fileName) {
  char buffer[1024];

  char tempFileName[FILENAME_BUFFER_SIZE];
  snprintf(tempFileName, FILENAME_BUFFER_SIZE, "%s.temp", fileName);
  FILE* xmfFile = fopen(fileName, "r");
  FILE* tempFile = fopen(tempFileName, "w");

  if (xmfFile == NULL) error("Unable to open current XMF file.");

  if (tempFile == NULL) error("Unable to open temporary file.");

  /* First we make a temporary copy of the XMF file and count the lines */
  int counter = 0;
  while (fgets(buffer, 1024, xmfFile) != NULL) {
    counter++;
    fprintf(tempFile, "%s", buffer);
  }
  fclose(tempFile);
  fclose(xmfFile);

  /* We then copy the XMF file back up to the closing lines */
  xmfFile = fopen(fileName, "w");
  tempFile = fopen(tempFileName, "r");

  if (xmfFile == NULL) error("Unable to open current XMF file.");

  if (tempFile == NULL) error("Unable to open temporary file.");

  int i = 0;
  while (fgets(buffer, 1024, tempFile) != NULL && i < counter - 3) {
    i++;
    fprintf(xmfFile, "%s", buffer);
  }
  fprintf(xmfFile, "\n");
  fclose(tempFile);
  remove(tempFileName);

  return xmfFile;
}

/**
 * @brief Writes the begin of the XMF file
 *
 * @todo Exploit the XML nature of the XMF format to write a proper XML writer
 * and simplify all the XMF-related stuff.
 *
 * @param fileName The name of the file.
 */
void xmf_create_file(const char* fileName) {

  FILE* xmfFile = fopen(fileName, "w");
  if (xmfFile == NULL) error("Unable to create XMF file.");

  fprintf(xmfFile, "<?xml version=\"1.0\" ?> \n");
  fprintf(xmfFile, "<!DOCTYPE Xdmf SYSTEM \"Xdmf.dtd\" []> \n");
  fprintf(
      xmfFile,
      "<Xdmf xmlns:xi=\"http://www.w3.org/2003/XInclude\" Version=\"2.1\">\n");
  fprintf(xmfFile, "<Domain>\n");
  fprintf(xmfFile,
          "<Grid Name=\"TimeSeries\" GridType=\"Collection\" "
          "CollectionType=\"Temporal\">\n\n");

  fprintf(xmfFile, "</Grid>\n");
  fprintf(xmfFile, "</Domain>\n");
  fprintf(xmfFile, "</Xdmf>\n");

  fclose(xmfFile);
}

/**
 * @brief Writes the part of the XMF entry presenting the geometry of the
 * snapshot
 *
 * @param xmfFile The file to write in.
 * @param hdfFileName The name of the HDF5 file corresponding to this output.
 * @param time The current simulation time.
 */
void xmf_write_outputheader(FILE* xmfFile, const char* hdfFileName,
                            float time) {
  /* Write end of file */

  fprintf(xmfFile, "<!-- XMF description for file: %s -->\n", hdfFileName);
  fprintf(xmfFile,
          "<Grid GridType=\"Collection\" CollectionType=\"Spatial\">\n");
  fprintf(xmfFile, "<Time Type=\"Single\" Value=\"%f\"/>\n", time);
}

/**
 * @brief Writes the end of the XMF file (closes all open markups)
 *
 * @param xmfFile The file to write in.
 * @param output The number of this output.
 * @param time The current simulation time.
 */
void xmf_write_outputfooter(FILE* xmfFile, int output, float time) {
  /* Write end of the section of this time step */

  fprintf(xmfFile,
          "\n</Grid> <!-- End of meta-data for output=%04i, time=%f -->\n",
          output, time);
  fprintf(xmfFile, "\n</Grid> <!-- timeSeries -->\n");
  fprintf(xmfFile, "</Domain>\n");
  fprintf(xmfFile, "</Xdmf>\n");

  fclose(xmfFile);
}

/**
 * @brief Writes the header of an XMF group for a given particle type.
 *
 * @param xmfFile The file to write to.
 * @param hdfFileName The name of the corresponding HDF5 file.
 * @param N The number of particles to write.
 * @param ptype The particle type we are writing.
 */
void xmf_write_groupheader(FILE* xmfFile, const char* hdfFileName,
                           const int distributed, size_t N,
                           enum part_type ptype) {

  fprintf(xmfFile, "\n<Grid Name=\"%s\" GridType=\"Uniform\">\n",
          part_type_names[ptype]);
  fprintf(xmfFile,
          "<Topology TopologyType=\"Polyvertex\" Dimensions=\"%zu\"/>\n", N);
  fprintf(xmfFile, "<Geometry GeometryType=\"XYZ\">\n");
  fprintf(xmfFile,
          "<DataItem Dimensions=\"%zu 3\" NumberType=\"Double\" "
          "Precision=\"8\" "
          "Format=\"HDF\">%s:/PartType%d/Coordinates</DataItem>\n",
          N, xmf_basename(hdfFileName, distributed), (int)ptype);
  fprintf(xmfFile,
          "</Geometry>\n <!-- Done geometry for %s, start of particle fields "
          "list -->\n",
          part_type_names[ptype]);
}

/**
 * @brief Writes the footer of an XMF group for a given particle type.
 *
 * @param xmfFile The file to write to.
 * @param ptype The particle type we are writing.
 */
void xmf_write_groupfooter(FILE* xmfFile, enum part_type ptype) {
  fprintf(xmfFile, "</Grid> <!-- End of meta-data for parttype=%s -->\n",
          part_type_names[ptype]);
}

/**
 * @brief Returns the precision of a given dataset type
 */
int xmf_precision(enum IO_DATA_TYPE type) {
  switch (type) {
    case INT:
    case FLOAT:
      return 4;
      break;
    case DOUBLE:
      return 8;
      break;
    case ULONGLONG:
    case LONGLONG:
      return 8;
      break;
    case UINT8:
    case CHAR:
      return 1;
      break;
    default:
      error("Unsupported type");
  }
  return 0;
}

/**
 * @brief Returns the Xdmf type name of a given dataset type
 */
const char* xmf_type(enum IO_DATA_TYPE type) {
  switch (type) {
    case FLOAT:
    case DOUBLE:
      return "Float";
      break;
    case INT:
    case ULONGLONG:
    case LONGLONG:
      return "Int";
      break;
    case UINT8:
    case CHAR:
      return "Char";
      break;
    default:
      error("Unsupported type");
  }
  return "";
}

/**
 * @brief Writes the lines corresponding to an array of the HDF5 output
 *
 * @param xmfFile The file in which to write
 * @param fileName The name of the HDF5 file associated to this XMF descriptor.
 * @param partTypeGroupName The name of the group containing the particles in
 * the HDF5 file.
 * @param name The name of the array in the HDF5 file.
 * @param N The number of particles.
 * @param dim The dimension of the quantity (1 for scalars, 3 for vectors).
 * @param type The type of the data to write.
 *
 * @todo Treat the types in a better way.
 */
void xmf_write_line(FILE* xmfFile, const char* fileName, const int distributed,
                    const char* partTypeGroupName, const char* name, size_t N,
                    int dim, enum IO_DATA_TYPE type) {
  fprintf(xmfFile,
          "<Attribute Name=\"%s\" AttributeType=\"%s\" Center=\"Node\">\n",
          name, dim == 1 ? "Scalar" : "Vector");
  if (dim == 1)
    fprintf(xmfFile,
            "<DataItem Dimensions=\"%zu\" NumberType=\"%s\" "
            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
            N, xmf_type(type), xmf_precision(type),
            xmf_basename(fileName, distributed), partTypeGroupName, name);
  else
    fprintf(xmfFile,
            "<DataItem Dimensions=\"%zu %d\" NumberType=\"%s\" "
            "Precision=\"%d\" Format=\"HDF\">%s:%s/%s</DataItem>\n",
            N, dim, xmf_type(type), xmf_precision(type),
            xmf_basename(fileName, distributed), partTypeGroupName, name);
  fprintf(xmfFile, "</Attribute>\n");
}
