/**============================================================================
 *
 * \file   mesh_read.hpp
 * \brief  Declarations for the class MSHRead.
 *
 * \author Leblanc Christophe.
 * \date   30/01/2019
 * \email cleblancad@gmail.com
 *
 *===========================================================================*/

#ifndef MSH_READ_HPP
#define MSH_READ_HPP

#include <cstdlib>
#include <vector>
#include <stdexcept>
#include <fstream>
#include <string>
#include <array>
#include <map>

namespace marinov {

/// \brief read a msh file (version 2.2)
// TODO This has to be updated in order to read other more recent mesh versions.
// (and put into a separate file).
class MSHRead
{
public:
  typedef std::array<double, 3> Point;
  typedef std::array<std::size_t, 3> Face;
  typedef std::size_t Vertex_index;
  typedef std::size_t Face_index;

  MSHRead(std::ifstream &file): m_file(file) {}
  void read();

  const std::vector<Point>& points() const
  { return m_points; }

  const Point& point(const std::size_t i) const
  { return m_points[i]; }

  const std::vector<Face>& faces() const
  { return m_faces; }

  const Face& face(const std::size_t i) const
  { return m_faces[i]; }

  std::size_t number_of_points() const
  { return m_points.size(); }

  std::size_t number_of_faces() const
  { return m_faces.size(); }

  std::size_t v_begin() const
  { return 0; }

  std::size_t v_end() const
  { return m_points.size(); }

  std::size_t f_begin() const
  { return 0; }

  std::size_t f_end() const
  { return m_faces.size(); }

private:
  std::ifstream &m_file;
  std::vector<Point> m_points;
  std::vector<Face> m_faces;
};

void MSHRead::read()
{
  m_file.precision(50);
  char buffer[1024];
  std::size_t number_of_nodes, point_number;
  std::size_t number_of_elements, element_number, element_type;
  std::size_t number_of_tags, tag;

  // Skip first line.
  m_file >> buffer;

  // Control version.
  m_file >> buffer;
  if(strcmp(buffer, "2.2") != 0)
  {
    throw std::runtime_error("MSH file version is not 2.2. Stopping.");
  }

  // Skip lines.
  m_file >> buffer;
  m_file >> buffer;
  m_file >> buffer;
  m_file >> buffer;

  // Number of nodes.
  m_file >> number_of_nodes;

  if(number_of_nodes == 0)
    throw std::runtime_error("Mesh has zero nodes.");

  // Read points.
  m_points.resize(number_of_nodes);

  for(std::size_t i = 0; i < number_of_nodes; ++i)
  {
    if(m_file.eof())
      throw std::runtime_error("Unexpected end of file.");

    // Skip point number.
    m_file >> point_number;

    // Read coordinates.
    m_file >> m_points[i][0];
    m_file >> m_points[i][1];
    m_file >> m_points[i][2];
  }

  // Skip lines.
  m_file >> buffer;
  m_file >> buffer;

  // Number of elements.
  m_file >> number_of_elements;

  if(number_of_elements == 0)
    throw std::runtime_error("Mesh has zero elements.");

  // Read faces.
  m_faces.resize(number_of_elements);

  for(std::size_t i = 0; i < number_of_elements; ++i)
  {
    if(m_file.eof())
      throw std::runtime_error("Unexpected end of file.");

    m_file >> element_number;
    m_file >> element_type;

    if(element_type != 2)
    {
      throw std::runtime_error("Mesh is not triangular at element " + std::to_string(i)
                               + " which is of type " + std::to_string(element_type) + ".");
    }

    // Skip tags.
    m_file >> number_of_tags;
    for(std::size_t j = 0; j < number_of_tags; ++j)
      m_file >> tag;

    // Read current face.
    m_file >> m_faces[i][0];
    m_file >> m_faces[i][1];
    m_file >> m_faces[i][2];

    m_faces[i][0] -= 1;
    m_faces[i][1] -= 1;
    m_faces[i][2] -= 1;
  }
}

} // end namespace marinov

#endif  // MSH_READ_HPP
