/*============================================================================
  MetaIO
  Copyright 2000-2010 Insight Software Consortium

  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.

  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
#include "metaTypes.h"

#ifndef ITKMetaIO_METAMESH_H
#  define ITKMetaIO_METAMESH_H

#  include "metaUtils.h"
#  include "metaObject.h"

#  ifdef _MSC_VER
#    pragma warning(disable : 4251)
#  endif

#  include <list>


/*!    MetaMesh (.h and .cxx)
 *
 * Description:
 *    Reads and Writes MetaMeshFiles.
 *
 * \author Julien Jomier
 *
 * \date June, 2004
 *
 * Depends on:
 *    MetaUtils.h
 */

#  if (METAIO_USE_NAMESPACE)
namespace METAIO_NAMESPACE
{
#  endif

/** Typedef for the type of cells */
#  define MET_NUM_CELL_TYPES 9

enum MET_CellGeometry
{
  MET_VERTEX_CELL = 0,
  MET_LINE_CELL,
  MET_TRIANGLE_CELL,
  MET_QUADRILATERAL_CELL,
  MET_POLYGON_CELL,
  MET_TETRAHEDRON_CELL,
  MET_HEXAHEDRON_CELL,
  MET_QUADRATIC_EDGE_CELL,
  MET_QUADRATIC_TRIANGLE_CELL
};

const unsigned char MET_CellSize[MET_NUM_VALUE_TYPES] = { 1, 2, 3, 4, 5, 4, 8, 3, 6 };

const char MET_CellTypeName[MET_NUM_VALUE_TYPES][4] = { { 'V', 'R', 'T', '\0' }, { 'L', 'N', 'E', '\0' },
                                                        { 'T', 'R', 'I', '\0' }, { 'Q', 'A', 'D', '\0' },
                                                        { 'P', 'L', 'Y', '\0' }, { 'T', 'E', 'T', '\0' },
                                                        { 'H', 'E', 'X', '\0' }, { 'Q', 'E', 'D', '\0' },
                                                        { 'Q', 'T', 'R', '\0' } };


/** Define a mesh point */
class METAIO_EXPORT MeshPoint
{
public:
  explicit MeshPoint(int dim);
  ~MeshPoint();

  unsigned int m_Dim;
  float *      m_X;
  int          m_Id{};
};


/** Define a mesh cell
 *  a cell contains a list of Ids corresponding to the list
 *  of points */
class METAIO_EXPORT MeshCell
{
public:
  explicit MeshCell(int dim);
  ~MeshCell();

  int          m_Id;
  unsigned int m_Dim;
  int *        m_PointsId;
};

/** Define a mesh cell links
 *  a celllink contains a list of Ids corresponding to the list
 *  of links cells */
class METAIO_EXPORT MeshCellLink
{
public:
  MeshCellLink() { m_Id = 0; }
  ~MeshCellLink() = default;

  int            m_Id; // id of the cell link
  std::list<int> m_Links;
};

/** Define a mesh point data */
class METAIO_EXPORT MeshDataBase
{
public:
  MeshDataBase() { m_Id = -1; }
  virtual ~MeshDataBase() = default;

  virtual void
  Write(METAIO_STREAM::ofstream * stream) = 0;
  virtual unsigned int
  GetSize() = 0;
  virtual MET_ValueEnumType
      GetMetaType() = 0;
  int m_Id;

protected:
  METAIO_STREAM::ifstream * m_ReadStream{};
  METAIO_STREAM::ofstream * m_WriteStream{};
};

/** Mesh point data class for basic types (i.e int, float ... ) */
template <typename TElementType>
class METAIO_EXPORT MeshData : public MeshDataBase
{
public:
  MeshData() { m_Id = -1; }
  ~MeshData() override = default;

  MET_ValueEnumType
  GetMetaType() override
  {
    return MET_GetPixelType(typeid(TElementType));
  }

  void
  Write(METAIO_STREAM::ofstream * stream) override
  {
    // char* id = new char[sizeof(int)];
    // The file is written as LSB by default
    int mid = m_Id;
    MET_SwapByteIfSystemMSB(&mid, MET_INT);
    // MET_DoubleToValue((double)m_Id,MET_INT,id,0);
    stream->write((char *)&mid, sizeof(int));
    // delete [] id;
    // char* data = new char[sizeof(m_Data)];
    // double mdata = m_Data;
    // MET_DoubleToValue((double)m_Data,GetMetaType(),data,0);
    TElementType data = m_Data;
    MET_SwapByteIfSystemMSB(&data, GetMetaType());
    stream->write((char *)&data, sizeof(data));
  }

  unsigned int
  GetSize() override
  {
    unsigned int size = sizeof(int);
    size += sizeof(m_Data);
    return size;
  }

  TElementType m_Data;
};


class METAIO_EXPORT MetaMesh : public MetaObject
{

  // PUBLIC
public:
  typedef std::list<MeshPoint *>    PointListType;
  typedef std::list<MeshCell *>     CellListType;
  typedef std::list<MeshCellLink *> CellLinkListType;
  typedef std::list<MeshDataBase *> PointDataListType;
  typedef std::list<MeshDataBase *> CellDataListType;

  // Constructors & Destructor
  MetaMesh();

  explicit MetaMesh(const char * _headerName);

  explicit MetaMesh(const MetaMesh * _mesh);

  explicit MetaMesh(unsigned int dim);

  ~MetaMesh() override;

  void
  PrintInfo() const override;

  void
  CopyInfo(const MetaObject * _object) override;

  //    NPoints(...)
  //       Required Field
  //       Number of points which compose the mesh
  int
  NPoints() const;

  //    NCells(...)
  //       Required Field
  //       Number of cells which compose the mesh
  int
  NCells() const;

  //    NCellLinks(...)
  //       Required Field
  //       Number of cellLinks which compose the mesh
  int
  NCellLinks() const;

#if 0 //These are not yet implemented
  //    NCellTypes(...)
  //       Required Field
  //       Number of cells which compose the mesh
  void
  NCellTypes(int ncelltypes);
  int
  NCellTypes(void) const;
#endif

  /** Clear the metaMesh */
  void
  Clear() override;

  PointListType &
  GetPoints()
  {
    return m_PointList;
  }
  const PointListType &
  GetPoints() const
  {
    return m_PointList;
  }

  CellListType &
  GetCells(MET_CellGeometry geom)
  {
    return *(m_CellListArray[geom]);
  }
  const CellListType &
  GetCells(MET_CellGeometry geom) const
  {
    return *(m_CellListArray[geom]);
  }

  CellLinkListType &
  GetCellLinks()
  {
    return m_CellLinks;
  }
  const CellLinkListType &
  GetCellLinks() const
  {
    return m_CellLinks;
  }

  PointDataListType &
  GetPointData()
  {
    return m_PointData;
  }
  const PointDataListType &
  GetPointData() const
  {
    return m_PointData;
  }

  CellDataListType &
  GetCellData()
  {
    return m_CellData;
  }
  const CellDataListType &
  GetCellData() const
  {
    return m_CellData;
  }

  MET_ValueEnumType
  PointDataType() const
  {
    return m_PointDataType;
  }
  void
  PointDataType(MET_ValueEnumType _elementType)
  {
    m_PointDataType = _elementType;
  }

  MET_ValueEnumType
  CellDataType() const
  {
    return m_CellDataType;
  }
  void
  CellDataType(MET_ValueEnumType _elementType)
  {
    m_CellDataType = _elementType;
  }

  // PROTECTED
protected:
  bool m_ElementByteOrderMSB{};

  void
  M_SetupReadFields() override;

  void
  M_SetupWriteFields() override;

  bool
  M_Read() override;

  bool
  M_Write() override;

  int  m_NPoints;
  int  m_NCells{};
  int  m_NCellLinks{};
  int  m_NPointData{};
  int  m_NCellData{};
  char m_PointDim[255]{}; // "PointDim = "       "x y z r"

  PointListType m_PointList;

  // We store the Cell lists in a vector
  CellListType *    m_CellListArray[MET_NUM_CELL_TYPES]{};
  CellLinkListType  m_CellLinks;
  PointDataListType m_PointData;
  CellDataListType  m_CellData;

  MET_ValueEnumType m_PointType;
  MET_ValueEnumType m_PointDataType;
  MET_ValueEnumType m_CellDataType;
};

#  if (METAIO_USE_NAMESPACE)
};
#  endif


#endif
