#ifndef VTK_WRITER_HPP
#define VTK_WRITER_HPP

#include <string>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <TinyVector.hpp>

class VTKWriter
{
 private:
  const std::string m_base_filename;
  unsigned int m_file_number;
  double m_last_time;
  const double m_time_period;

 public:
  template <typename MeshType>
  void write(const MeshType& mesh,
             const double& time,
             const bool& forced_output = false)
  {
    if (time == m_last_time) return; // output already performed
    if ((time - m_last_time >= m_time_period) or forced_output) {
      m_last_time = time;
    } else {
      return;
    }
    std::ostringstream sout;
    sout << m_base_filename << '.' << std::setfill('0') << std::setw(4) << m_file_number << ".vtu" << std::ends;
    std::ofstream fout(sout.str());
    fout << "<?xml version=\"1.0\"?>\n";
    fout << "<VTKFile type=\"UnstructuredGrid\">\n";
    fout << "<UnstructuredGrid>\n";
    fout << "<Piece NumberOfPoints=\""<< mesh.numberOfNodes()
         << "\" NumberOfCells=\"" << mesh.numberOfCells() << "\">\n";

    fout << "<Points>\n";
    fout << "<DataArray Name=\"Positions\" NumberOfComponents=\"3\" type=\"Float64\" format=\"ascii\">\n";
    if constexpr(MeshType::dimension ==1) {
      const Kokkos::View<const TinyVector<1>*> xr = mesh.xr();
      for (unsigned int r=0; r<mesh.numberOfNodes(); ++r) {
        for (unsigned short i=0; i<1; ++i) {
          fout << xr[r][i] << ' ';
        }
        fout << "0 0 "; // VTK requires 3 components
      }
    } else if constexpr (MeshType::dimension ==2) {
      const Kokkos::View<const TinyVector<2>*> xr = mesh.xr();
      for (unsigned int r=0; r<mesh.numberOfNodes(); ++r) {
        for (unsigned short i=0; i<2; ++i) {
          fout << xr[r][i] << ' ';
        }
        fout << "0 "; // VTK requires 3 components
      }
    } else {
      const Kokkos::View<const TinyVector<3>*> xr = mesh.xr();
      for (unsigned int r=0; r<mesh.numberOfNodes(); ++r) {
        for (unsigned short i=0; i<3; ++i) {
          fout << xr[r][i] << ' ';
        }
      }
    }
    fout << '\n';
    fout << "</DataArray>\n";
    fout << "</Points>\n";

    fout << "<Cells>\n";
    const Kokkos::View<const unsigned int**> cell_nodes = mesh.connectivity().cellNodes();
    const Kokkos::View<const unsigned short*> cell_nb_nodes = mesh.connectivity().cellNbNodes();

    fout << "<DataArray type=\"Int32\" Name=\"connectivity\" NumberOfComponents=\"1\" format=\"ascii\">\n";
    for (unsigned int j=0; j<mesh.numberOfCells(); ++j) {
      for (unsigned short r=0; r<cell_nb_nodes[j]; ++r) {
        fout << cell_nodes(j,r) << ' ';
      }
    }
    fout << '\n';
    fout << "</DataArray>\n";

    fout << "<DataArray type=\"UInt32\" Name=\"offsets\" NumberOfComponents=\"1\" format=\"ascii\">\n";
    {
      unsigned int offset=0;
      for (unsigned int j=0; j<mesh.numberOfCells(); ++j) {
        offset += cell_nb_nodes[j];
        fout << offset << ' ';
      }
    }
    fout << '\n';
    fout << "</DataArray>\n";

    fout << "<DataArray type=\"Int8\" Name=\"types\" NumberOfComponents=\"1\" format=\"ascii\">\n";
    for (unsigned int j=0; j<mesh.numberOfCells(); ++j) {
      switch (cell_nb_nodes[j]) {
        case 2: {
          fout << "3 ";
          break;
        }
        case 3: {
          fout << "5 ";
          break;
        }
        case 4: {
          if (mesh.meshDimension() == 3) {
            fout << "10 ";
          } else {
            fout << "9 ";
          }
          break;
        }
        case 8: {
          fout << "12 ";
          break;
        }
        default: {
          fout << "7 ";
          break;
        }
      }
    }
    fout << '\n';
    fout << "</DataArray>\n";

    fout << "</Cells>\n";
    fout << "</Piece>\n";
    fout << "</UnstructuredGrid>\n";
    fout << "</VTKFile>\n";

    m_file_number++;
  }
  VTKWriter(const std::string& base_filename,
            const double time_period)
      : m_base_filename(base_filename),
        m_file_number  (0),
        m_last_time    (-std::numeric_limits<double>::max()),
        m_time_period  (time_period)
  {}

  ~VTKWriter() = default;
};

#endif // VTK_WRITER_HPP