/****************************************************************************
* NanoPLY                                                                   *
* NanoPLY is a C++11 header-only library to read and write PLY file         *
*                                                                           *
* Copyright(C) 2014-2015                                                    *
* Visual Computing Lab                                                      *
* ISTI - Italian National Research Council                                  *
*                                                                           *
* This Source Code Form is subject to the terms of the Mozilla Public       *
* License, v. 2.0. If a copy of the MPL was not distributed with this       *
* file, You can obtain one at http://mozilla.org/MPL/2.0/.                  *
*                                                                           *
****************************************************************************/


#ifndef NANOPLY_HPP
#define NANOPLY_HPP

#include <vector>
#include <unordered_map>
#include <tuple>
#include <cassert>
#include <algorithm>
#include <stdexcept>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <limits>
#include <fstream>
#include <sstream>
#include <stdint.h>
#include <string>
#include <iomanip>


// Avoid conflicting declaration of min/max macros in windows headers
#if !defined(NOMINMAX) && (defined(_WIN32) || defined(_WIN32_)  || defined(WIN32) || defined(_WIN64))
# define NOMINMAX
# ifdef max
#  undef   max
#  undef   min
# endif
#endif

namespace nanoply
{

  /** Error Type.
  *	Error type returned by the open of a PLY file.
  */
  typedef enum NNP_ERROR
  {
    NNP_OK = 0x0000,  /**< No error. */
    NNP_UNABLE_TO_OPEN = 0x0001,  /**< The file cannot be opend. */
    NNP_MISSING_HEADER = 0x0002,  /**< The file does not contain a valid PLY header. */
    NNP_MISSING_FORMAT = 0x0004,  /**< The file has an invalid internal format. */
    NNP_INVALID_ELEMENT = 0x0008,  /**< The file has an invalid element. */
    NNP_INVALID_PROPERTY = 0x0010	  /**< The file has an invalid property. */
  } ErrorCode;


  /** PLY Element Entity.
  *	Element that can be saved in PLY file.
  */
  typedef enum NNP_ELEM
  {
    NNP_UNKNOWN_ELEM = 0x0, /**< Unknown element. */
    NNP_VERTEX_ELEM = 0x1, /**< Vertex element. */
    NNP_EDGE_ELEM = 0x2, /**< Edge element. */
    NNP_FACE_ELEM = 0x4  /**< Face element. */
  } PlyElemEntity;


  /** PLY Entity.
  *  Property that can be saved in a PLY file.
  */
  typedef enum NNP_ENTITY
  {
    NNP_UNKNOWN_ENTITY = 0x00000000,	/**< Unknown property. */
    NNP_PX = 0x00000001,	/**< Position x cordinate. */
    NNP_PY = 0x00000002,	/**< Position y cordinate. */
    NNP_PZ = 0x00000004,	/**< Position z cordinate. */
    NNP_PXYZ = 0x00000007,	/**< Position (x, y, z). */
    NNP_NX = 0x00000010,	/**< Normal x component. */
    NNP_NY = 0x00000020,	/**< Normal y component. */
    NNP_NZ = 0x00000040,	/**< Normal z component. */
    NNP_NXYZ = 0x00000070,	/**< Normal (x, y, z). */
    NNP_CR = 0x00000100,	/**< Color red component. */
    NNP_CG = 0x00000200,	/**< Color green component. */
    NNP_CB = 0x00000400,	/**< Color blue component. */
    NNP_CRGB = 0x00000700,	/**< Color RGB. */
    NNP_CA = 0x00000800,	/**< Color alpha component. */
    NNP_CRGBA = 0x00000F00,	/**< Color RGBA. */
    NNP_DENSITY = 0x00000008,	/**< Density or Radius property. */
    NNP_SCALE = 0x00000080,	/**< Scale property. */
    NNP_TEXTUREU = 0x00001000,	/**< Texture coordinate u. */
    NNP_TEXTUREV = 0x00002000,	/**< Texture coordinate v. */
    NNP_TEXTURE2D = 0x00003000,	/**< Texture coordinate 2D. */
    NNP_TEXTUREW = 0x00004000,	/**< Texture coordinate w. */
    NNP_TEXTURE3D = 0x00007000,	/**< Texture coordinate 3D. */
    NNP_TEXTUREINDEX = 0x00008000,	/**< Texture index. */
    NNP_QUALITY = 0x00010000,	/**< Quality property. */
    NNP_REFLECTANCE = 0x00020000,	/**< Reflectance property. */
    NNP_BITFLAG = 0x00040000,	/**< Bit flags. */
    NNP_K1 = 0x00080000,	/**< Main curvaure value k1. */
    NNP_K2 = 0x00100000,	/**< Main curvaure value k2. */
//    NNP_KG = 0x00200000,	/**< Gaussian curvature value. */
//    NNP_KH = 0x00400000, /**< Mean curvature value. */
    NNP_K1DIR = 0x00800000, /**< Curvature direction k1. */
    NNP_K2DIR = 0x01000000, /**< Curvature direction k2. */
    NNP_EDGE_V1 = 0x02000000,	/**< Index of the first vertex of the edge. */
    NNP_EDGE_V2 = 0x04000000,	/**< Index of the second vertex of the edge. */
    NNP_FACE_VERTEX_LIST = 0x08000000, /**< List of vertex indices for the face. */
    NNP_FACE_WEDGE_COLOR = 0x10000000, /**< List of colors for wedge. */
    NNP_FACE_WEDGE_NORMAL = 0x20000000, /**< List of normals for wedge. */
    NNP_FACE_WEDGE_TEX = 0x40000000  /**< List of texture coordinates for wedge. */
  } PlyEntity;


  /** PLY Type.
  *  Type of a PLY property.
  */
  typedef enum NNP_PLYTYPE
  {
    NNP_UNKNOWN_TYPE = 0x000000, /**< Unknown type. */
    NNP_FLOAT32 = 0x000001, /**< Float. */
    NNP_FLOAT64 = 0x000002, /**< Double. */
    NNP_INT8 = 0x000004, /**< Char. */
    NNP_INT16 = 0x000008, /**< Short. */
    NNP_INT32 = 0x000010, /**< Int. */
    NNP_UINT8 = 0x000020, /**< Unsigned Char. */
    NNP_UINT16 = 0x000040, /**< Unsigned Short. */
    NNP_UINT32 = 0x000080, /**< Unsigned Int. */
    NNP_LIST_UINT8_UINT32 = 0x000100, /**< List (size Unsigned Char) of Unsigned Int.  */
    NNP_LIST_INT8_UINT32 = 0x000200, /**< List (size Char) of Unsigned Int.  */
    NNP_LIST_UINT8_INT32 = 0x000400, /**< List (size Unsigned Char) of Int.  */
    NNP_LIST_INT8_INT32 = 0x000800, /**< List (size Char) of Int. */
    NNP_LIST_UINT8_FLOAT32 = 0x001000, /**< List (size Unsigned Char) of Float. */
    NNP_LIST_INT8_FLOAT32 = 0x002000, /**< List (size Char) of Float. */
    NNP_LIST_UINT8_FLOAT64 = 0x004000, /**< List (size Unsigned Char) of Double. */
    NNP_LIST_INT8_FLOAT64 = 0x008000, /**< List (size Char) of Double. */
    NNP_LIST_UINT8_UINT8 = 0x010000, /**< List (size Unsigned Char) of Unsigned Char.  */
    NNP_LIST_INT8_UINT8 = 0x020000, /**< List (size Char) of Unsigned Char.  */
    NNP_LIST_UINT8_INT8 = 0x040000, /**< List (size Unsigned Char) of Char.  */
    NNP_LIST_INT8_INT8 = 0x080000, /**< List (size Char) of Char. */
    NNP_LIST_UINT8_UINT16 = 0x100000, /**< List (size Unsigned Char) of Unsigned Short.  */
    NNP_LIST_INT8_UINT16 = 0x200000, /**< List (size Char) of Unsigned Short.  */
    NNP_LIST_UINT8_INT16 = 0x400000, /**< List (size Unsigned Char) of Short.  */
    NNP_LIST_INT8_INT16 = 0x800000  /**< List (size Char) of Short. */
  } PlyType;

  template <typename TEnum>
  inline std::size_t hashEnum(const TEnum & value)
  {
	  const std::hash<unsigned int> h{};
	  return h((unsigned int)value);
  }

} // end namespace nanoply

// Cast of an enum to an unsigned int (maybe only for Android)
namespace std
{
	template <> struct hash<nanoply::PlyElemEntity> { std::size_t operator () (const nanoply::PlyElemEntity & t) const { return nanoply::hashEnum(t); } };
	template <> struct hash<nanoply::PlyEntity    > { std::size_t operator () (const nanoply::PlyEntity     & t) const { return nanoply::hashEnum(t); } };
	template <> struct hash<nanoply::PlyType      > { std::size_t operator () (const nanoply::PlyType       & t) const { return nanoply::hashEnum(t); } };
} // end namespace std

namespace nanoply
{

  /**
  * @cond HIDDEN_SYMBOLS
  */
  template < size_t T> struct SizeT {};

  typedef std::vector<std::string> NameVector;
  typedef std::unordered_map<PlyType, NameVector> TypeMap;
  typedef std::unordered_map<PlyType, NameVector>::iterator TypeMapIterator;
  typedef std::unordered_map<PlyEntity, NameVector> EntityMap;
  typedef std::unordered_map<PlyEntity, NameVector>::iterator EntityMapIterator;
  typedef std::unordered_map<PlyElemEntity, NameVector> ElementMap;
  typedef std::unordered_map<PlyElemEntity, NameVector>::iterator ElementMapIterator;

  /* Names used for the PlyType */
  static TypeMap mapType({
      { PlyType::NNP_UNKNOWN_TYPE, NameVector({ "unknonw" }) },
      { PlyType::NNP_FLOAT32, NameVector({ "float", "float32" }) },
      { PlyType::NNP_FLOAT64, NameVector({ "double", "float64" }) },
      { PlyType::NNP_INT8, NameVector({ "char", "int8" }) },
      { PlyType::NNP_INT16, NameVector({ "short", "int16" }) },
      { PlyType::NNP_INT32, NameVector({ "int", "int32" }) },
      { PlyType::NNP_UINT8, NameVector({ "uchar", "uint8" }) },
      { PlyType::NNP_UINT16, NameVector({ "ushort", "uint16" }) },
      { PlyType::NNP_UINT32, NameVector({ "uint", "uint32" }) },
      { PlyType::NNP_LIST_UINT8_UINT32, NameVector({ "list uchar uint", "list uint8 uint32" }) },
      { PlyType::NNP_LIST_INT8_UINT32, NameVector({ "list char uint", "list int8 uint32" }) },
      { PlyType::NNP_LIST_UINT8_INT32, NameVector({ "list uchar int", "list uint8 int32" }) },
      { PlyType::NNP_LIST_INT8_INT32, NameVector({ "list char int", "list int8 int32" }) },
      { PlyType::NNP_LIST_UINT8_FLOAT32, NameVector({ "list uchar float", "list uint8 float32" }) },
      { PlyType::NNP_LIST_INT8_FLOAT32, NameVector({ "list char float", "list int8 float32" }) },
      { PlyType::NNP_LIST_UINT8_FLOAT64, NameVector({ "list uchar double", "list uint8 float64" }) },
      { PlyType::NNP_LIST_INT8_FLOAT64, NameVector({ "list char double", "list int8 float64" }) },
      { PlyType::NNP_LIST_UINT8_UINT8, NameVector({ "list uchar uchar", "list uint8 uint8" }) },
      { PlyType::NNP_LIST_INT8_UINT8, NameVector({ "list char uchar", "list int8 uint8" }) },
      { PlyType::NNP_LIST_UINT8_INT8, NameVector({ "list uchar char", "list uint8 int8" }) },
      { PlyType::NNP_LIST_INT8_INT8, NameVector({ "list char char", "list int8 int8" }) },
      { PlyType::NNP_LIST_UINT8_UINT16, NameVector({ "list uchar ushort", "list uint8 uint16" }) },
      { PlyType::NNP_LIST_INT8_UINT16, NameVector({ "list char ushort", "list int8 uint16" }) },
      { PlyType::NNP_LIST_UINT8_INT16, NameVector({ "list uchar short", "list uint8 int16" }) },
      { PlyType::NNP_LIST_INT8_INT16, NameVector({ "list char short", "list int8 int16" }) }
  });


  /* Names used for the PlyProperty */
  static EntityMap mapProp({
      { PlyEntity::NNP_UNKNOWN_ENTITY, NameVector({ "unknonw" }) },
      { PlyEntity::NNP_PX, NameVector({ "x", "px", "posx" }) },
      { PlyEntity::NNP_PY, NameVector({ "y", "py", "posy" }) },
      { PlyEntity::NNP_PZ, NameVector({ "z", "pz", "posz" }) },
      { PlyEntity::NNP_PXYZ, NameVector({ "x y z", "px py pz", "posx posy posz" }) },
      { PlyEntity::NNP_NX, NameVector({ "nx", "normalx" }) },
      { PlyEntity::NNP_NY, NameVector({ "ny", "normaly" }) },
      { PlyEntity::NNP_NZ, NameVector({ "nz", "normalz" }) },
      { PlyEntity::NNP_NXYZ, NameVector({ "nx ny nz", "normalx normaly normalz" }) },
      { PlyEntity::NNP_CR, NameVector({ "red", "diffuse_red", "r", "diffuse_r" }) },
      { PlyEntity::NNP_CG, NameVector({ "green", "diffuse_green", "g", "diffuse_g" }) },
      { PlyEntity::NNP_CB, NameVector({ "blue", "diffuse_blue", "b", "diffuse_b" }) },
      { PlyEntity::NNP_CA, NameVector({ "alpha", "diffuse_alpha", "a", "diffuse_a" }) },
      { PlyEntity::NNP_CRGB, NameVector({ "rgb", "diffuse_rgb" }) },
      { PlyEntity::NNP_CRGBA, NameVector({ "rgba", "diffuse_rgba" }) },
      { PlyEntity::NNP_DENSITY, NameVector({ "radius", "density" }) },
      { PlyEntity::NNP_SCALE, NameVector({ "scale", "value" }) },
      { PlyEntity::NNP_TEXTUREU, NameVector({ "texture_u", "u", "s", "tx" }) },
      { PlyEntity::NNP_TEXTUREV, NameVector({ "texture_v", "v", "t", "ty" }) },
      { PlyEntity::NNP_TEXTURE2D, NameVector({ "texture_uv", "uv" }) },
      { PlyEntity::NNP_TEXTUREW, NameVector({ "texture_w", "w" }) },
      { PlyEntity::NNP_TEXTURE3D, NameVector({ "texture_uvw", "uvw" }) },
      { PlyEntity::NNP_TEXTUREINDEX, NameVector({ "texnumber", "texid" }) },
      { PlyEntity::NNP_QUALITY, NameVector({ "quality", "confidence" }) },
      { PlyEntity::NNP_REFLECTANCE, NameVector({ "reflectance" }) },
      { PlyEntity::NNP_BITFLAG, NameVector({ "flags" }) },
      { PlyEntity::NNP_K1, NameVector({ "k1" }) },
      { PlyEntity::NNP_K2, NameVector({ "k2" }) },
//      { PlyEntity::NNP_KG, NameVector({ "k" }) },
//      { PlyEntity::NNP_KH, NameVector({ "h" }) },
      { PlyEntity::NNP_K1DIR, NameVector({ "k1dir" }) },
      { PlyEntity::NNP_K2DIR, NameVector({ "k2dir" }) },
      { PlyEntity::NNP_EDGE_V1, NameVector({ "vertex1", "v1" }) },
      { PlyEntity::NNP_EDGE_V2, NameVector({ "vertex2", "v2" }) },
      { PlyEntity::NNP_FACE_VERTEX_LIST, NameVector({ "vertex_indices", "vertex_index" }) },
      { PlyEntity::NNP_FACE_WEDGE_COLOR, NameVector({ "color" }) },
      { PlyEntity::NNP_FACE_WEDGE_NORMAL, NameVector({ "normal" }) },
      { PlyEntity::NNP_FACE_WEDGE_TEX, NameVector({ "texcoord" }) }
  });


  /* Names used for the PlyElement */
  static ElementMap mapElem({
      { PlyElemEntity::NNP_UNKNOWN_ELEM, NameVector({ "unknown" }) },
      { PlyElemEntity::NNP_VERTEX_ELEM, NameVector({ "vertex" }) },
      { PlyElemEntity::NNP_EDGE_ELEM, NameVector({ "edge" }) },
      { PlyElemEntity::NNP_FACE_ELEM, NameVector({ "face" }) },
  });


  /* Returns the vector of possible name for the input PlyEntity */
  static inline const NameVector& PlyPropertyName(PlyEntity ent)
  {
    static NameVector emptyVec;
    if (mapProp.find(ent) != mapProp.end())
      return 	mapProp[ent];
    return  emptyVec;
  };


  /* Returns the vector of possible name for the input PlyType */
  static inline const NameVector& PlyTypeName(PlyType ent)
  {
    static NameVector emptyVec;
    if (mapType.find(ent) != mapType.end())
      return 	mapType[ent];
    return  emptyVec;
  };


  /* Returns the vector of possible name for the input PlyElement */
  static inline const NameVector& PlyElementName(PlyElemEntity ent)
  {
    static NameVector emptyVec;
    if (mapElem.find(ent) != mapElem.end())
      return 	mapElem[ent];
    return  emptyVec;
  };


  /* Return 1 for little endian, 0 for big endian*/
  inline int checkEndianness()
  {
    unsigned int x = 1;
    char *c = (char*)&x;
    return (int)*c;
  };

  /* Adjust the endianess of the input buffer*/
  inline void adjustEndianess(unsigned char* buffer, int typeSize, int count)
  {
    for (int i = 0; i < count; i++)
    {
      int offset = i*typeSize;
      for (int j = 0; j < typeSize / 2; j++)
      {
        unsigned char temp = buffer[offset + j];
        buffer[offset + j] = buffer[offset + typeSize - 1 - j];
        buffer[offset + typeSize - 1 - j] = temp;
      }
    }
  }
  /**
  * @endcond
  */



  /** PLY File.
  *  Class to manage the read and write of a PLY file using a memory buffer.
  */
  class PlyFile
  {
  private:
    std::fstream fileStream;	/**< Stream. */
    char mode;					/**< Mode of the stream (0 = read, 1 = write). */

    int64_t bufferSize;			/**< Size of the buffer. */
    int64_t bufferOffset;		/**< Offset for the next operation. */
    int64_t maxSize;			/**< Maximum size of the stream. */
    char* buffer;				/**< Buffer. */

  public:

    PlyFile();

    ~PlyFile();

    /**
    * Open the file in read mode.
    *
    * @param filename	name of the file.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool OpenFileToRead(const std::string &filename);

    /**
    * Open the file in write mode.
    *
    * @param filename	name of the file.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool OpenFileToWrite(const std::string &filename);

    /**
    * Read the next header line.
    *
    * @param line	read line.
    * @param last	true if the line is the last of header.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool NextHeaderLine(std::string &line, bool &last);

    /**
    * Write the line in the header.
    *
    * @param line	line to write.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteHeaderLine(const std::string &line);

    /**
    * Read binary data from the file.
    *
    * @param dest	pointer where to copy the data.
    * @param nByte	number of byte to read.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool ReadBinaryData(char * & dest, int nByte);

    /**
    * Read ASCII data from the file.
    *
    * @param dest	reference to the container of the data.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    template <typename T>
    inline bool ReadAsciiData(T &dest);

    /**
    * Write binary data in the file.
    *
    * @param src	pointer to the data to write.
    * @param nByte	number of byte to write.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteBinaryData(void *src, int nByte);

    /**
    * Write ASCII data in the file.
    *
    * @param src	reference to the container of the data.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    template <typename T>
    inline bool WriteAsciiData(const T &src);

    /**
    * Set the maximum size of the buffer.
    *
    * @return size	size of the buffer.
    */
    inline void SetBufferSize(int64_t size);

    /**
    * Force the write of the buffer in the file.
    */
    inline void Flush();
  };


  inline PlyFile::PlyFile()
  {
    buffer = NULL;
    maxSize = 10 * (1 << 20);
    mode = -1;
  }


  inline PlyFile::~PlyFile()
  {
    Flush();
    if (buffer != NULL)
      delete[] buffer;
    if (fileStream.is_open())
      fileStream.close();
  }


  inline bool PlyFile::OpenFileToRead(const std::string& filename)
  {
    if (fileStream.is_open())
      fileStream.close();
    mode = 0;
    fileStream.open(filename, std::fstream::in | std::fstream::binary);
    if (fileStream.fail())
      return false;
    bufferOffset = 0;
    return true;
  }


  inline bool PlyFile::OpenFileToWrite(const std::string& filename)
  {
    if (fileStream.is_open())
      fileStream.close();
    mode = 1;
    fileStream.open(filename, std::fstream::out | std::fstream::binary);
    if (fileStream.fail())
      return false;
    bufferOffset = 0;
    fileStream.setf(std::ios::fixed, std::ios::floatfield);
    //fileStream.precision(7);
    return true;
  }


  inline bool PlyFile::NextHeaderLine(std::string& line, bool& last)
  {
    if (mode != 0)
      return false;
    std::getline(fileStream, line);
    std::transform(line.begin(), line.end(), line.begin(), ::tolower);
    last = false;
    if (line.find("end_header") != std::string::npos)
      last = true;
    return true;
  }


  inline bool PlyFile::WriteHeaderLine(const std::string& line)
  {
    if (mode != 1)
      return false;
    fileStream << line;
    return true;
  }


  inline bool PlyFile::ReadBinaryData(char * & dest, int nByte)
  {
    if (mode != 0)
      return false;
    if (buffer == NULL)
    {
      buffer = new char[maxSize];
      fileStream.read(buffer, maxSize);
      bufferSize = fileStream.gcount();
      bufferOffset = 0;
    }
    else if (bufferOffset + nByte > bufferSize)
    {
      const size_t lastByte = bufferSize - bufferOffset;
      memcpy(buffer, &buffer[bufferOffset], lastByte);
      fileStream.read(&buffer[lastByte], maxSize - lastByte);
      bufferSize = fileStream.gcount() + lastByte;
      bufferOffset = 0;
    }
    //memcpy(dest, &buffer[bufferOffset], nByte);
		dest = buffer + bufferOffset;
    bufferOffset += nByte;
    return true;
  }


  template <typename T>
  inline bool PlyFile::ReadAsciiData(T& dest)
  {
    if (mode != 0)
      return false;
    fileStream >> dest;
    return true;
  }

  inline bool PlyFile::WriteBinaryData(void* src, int nByte)
  {
    if (mode != 1)
      return false;
    if (buffer == NULL)
    {
      buffer = new char[maxSize];
      bufferSize = maxSize;
      bufferOffset = 0;
    }
    else if (bufferOffset + nByte > bufferSize)
    {
      fileStream.write(buffer, bufferOffset);
      bufferOffset = 0;
    }
    memcpy(&buffer[bufferOffset], src, nByte);
    bufferOffset += nByte;
    return true;
  }


  template <typename T>
  inline bool PlyFile::WriteAsciiData(const T& src)
  {
    if (mode != 1)
      return false;
    fileStream << src;
    return true;
  }

	template <>
	inline bool PlyFile::WriteAsciiData<double>(const double & src)
	{
		if (mode != 1)
			return false;
		const auto precision = fileStream.precision();
		fileStream << std::setprecision(std::numeric_limits<double>::max_digits10)
		           << src
		           << std::setprecision(precision);
		return true;
	}

	template <>
	inline bool PlyFile::WriteAsciiData<float>(const float & src)
	{
		if (mode != 1)
			return false;
		const auto precision = fileStream.precision();
		fileStream << std::setprecision(std::numeric_limits<float>::max_digits10)
		           << src
		           << std::setprecision(precision);
		return true;
  }

  inline void PlyFile::SetBufferSize(int64_t size)
  {
    maxSize = size;
  }


  inline void PlyFile::Flush()
  {
    if (mode == 1)
      fileStream.write(buffer, bufferOffset);
  }




  /** PLY Property.
  *  Define a PLY property (entity and type).
  */
  class PlyProperty
  {
  public:

    std::string name;	/**< Property name. */
    PlyType type;		/**< Property type. */
    PlyEntity elem;		/**< Property entity. */
    bool validToWrite;  /**< Property validity (necessary to write the header). */


    inline PlyProperty() {}


    /**
    * Constructor that sets the type and the entity of a standard PLY property.
    *
    * @param _t	Property type.
    * @param _e	Property entity.
    */
    inline PlyProperty(PlyType _t, PlyEntity _e) : name(PlyPropertyName(_e)[0]), type(_t), elem(_e), validToWrite(false){}

    /**
    * Constructor that sets the type, the entity and the name of a standard PLY property.
    *
    * @param _t		Property type.
    * @param _e		Property entity.
    * @param _n		Property name.
    */
    inline PlyProperty(PlyType _t, PlyEntity _e, std::string _n) : name(_n), type(_t), elem(_e), validToWrite(false){}

    /**
    * Constructor that sets the type and the name of a custom PLY property.
    *
    * @param _t		Property type.
    * @param _n		Property name.
    */
    inline PlyProperty(PlyType _t, std::string _n) :name(_n), type(_t), elem(PlyEntity::NNP_UNKNOWN_ENTITY), validToWrite(false){}

    /**
    * Get the description string of the property entity.
    *
    * @return	Description string of the property entity.
    */
    inline const char* EntityStr();

    /**
    * Get the name of the property entity.
    *
    * @return	Name of the property entity.
    */
    inline const char* EntityName();

    /**
    * Get the description string of the property type.
    *
    * @return	Description string of the property type.
    */
    inline const char* TypeStr();

    /**
    * Get the size in byte of the property type.
    *
    * @return	Size in byte of the property type.
    */
    inline int TypeSize();

    /**
    * Get the number of component of the property entity.
    *
    * @return	Number of component.
    */
    inline int CountValue();

    /**
    * Check if the property type is signed or unsigned.
    *
    * @return	true = signed, false = unsigned.
    */
    inline bool IsSigned();

    /**
    * Skip the property in an Ascii file.
    *
    * @param file	Opened file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool SkipAsciiPropertyInFile(PlyFile &file);

    /**
    * Skip the property in a binary file.
    *
    * @param file	Opened file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool SkipBinaryPropertyInFile(PlyFile &file);

    /**
    * Write the property string in the header of the PLY file.
    *
    * @param file	Opened file.
    * @return		  If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteHeader(PlyFile &file);
  };


  inline const char* PlyProperty::EntityStr()
  {
    switch (this->elem)
    {
    case NNP_UNKNOWN_ENTITY:    return "NNP_UNKNOWN_ENTITY   ";
    case NNP_PX:    return "NNP_PX               ";
    case NNP_PY:    return "NNP_PY               ";
    case NNP_PZ:    return "NNP_PZ               ";
    case NNP_PXYZ:    return "NNP_PXYZ             ";
    case NNP_NX:    return "NNP_NX               ";
    case NNP_NY:    return "NNP_NY               ";
    case NNP_NZ:    return "NNP_NZ               ";
    case NNP_NXYZ:    return "NNP_NXYZ             ";
    case NNP_CR:    return "NNP_CR               ";
    case NNP_CG:    return "NNP_CG               ";
    case NNP_CB:    return "NNP_CB               ";
    case NNP_CRGB:    return "NNP_CRGB             ";
    case NNP_CA:    return "NNP_CA               ";
    case NNP_CRGBA:    return "NNP_CRGBA            ";
    case NNP_DENSITY:    return "NNP_DENSITY          ";
    case NNP_SCALE:    return "NNP_SCALE            ";
    case NNP_QUALITY:    return "NNP_QUALITY          ";
    case NNP_REFLECTANCE:    return "NNP_REFLECTANCE      ";
    case NNP_TEXTUREU:    return "NNP_TEXTUREU         ";
    case NNP_TEXTUREV:    return "NNP_TEXTUREV         ";
    case NNP_TEXTURE2D:    return "NNP_TEXTURE2D        ";
    case NNP_TEXTUREW:    return "NNP_TEXTUREW         ";
    case NNP_TEXTURE3D:    return "NNP_TEXTURE3D        ";
    case NNP_TEXTUREINDEX:    return "NNP_TEXTUREINDEX     ";
    case NNP_BITFLAG:    return "NNP_BITFLAG          ";
    case NNP_K1:    return "NNP_K1               ";
    case NNP_K2:    return "NNP_K2               ";
//    case NNP_KG:    return "NNP_K                ";
//    case NNP_KH:    return "NNP_H                ";
    case NNP_K1DIR:    return "NNP_K1DIR            ";
    case NNP_K2DIR:    return "NNP_K2DIR            ";
    case NNP_EDGE_V1:    return "NNP_EDGE_V1          ";
    case NNP_EDGE_V2:    return "NNP_EDGE_V2          ";
    case NNP_FACE_VERTEX_LIST:    return "NNP_FACE_VERTEX_LIST ";
    case NNP_FACE_WEDGE_COLOR:    return "NNP_FACE_WEDGE_COLOR ";
    case NNP_FACE_WEDGE_NORMAL:    return "NNP_FACE_WEDGE_NORMAL";
    case NNP_FACE_WEDGE_TEX:    return "NNP_FACE_WEDGE_TEX   ";

    default: assert(0);
      break;
    }
    return 0;
  }


  inline const char* PlyProperty::EntityName()
  {

    if (this->elem == PlyEntity::NNP_UNKNOWN_ENTITY)
      return (*this).name.c_str();
#ifdef USE_NOSTANDARDPLY_OUTPUT
    if (this->elem == PlyEntity::NNP_SCALE)
      return PlyPropertyName(this->elem)[1].c_str();
    if (this->elem == PlyEntity::NNP_QUALITY)
      return PlyPropertyName(this->elem)[1].c_str();
    if (this->elem == PlyEntity::NNP_CR)
      return PlyPropertyName(this->elem)[1].c_str();
    if (this->elem == PlyEntity::NNP_CB)
      return PlyPropertyName(this->elem)[1].c_str();
    if (this->elem == PlyEntity::NNP_CG)
      return PlyPropertyName(this->elem)[1].c_str();
    if (this->elem == PlyEntity::NNP_CA)
      return PlyPropertyName(this->elem)[1].c_str();
#endif
    const std::vector<std::string>& vect = PlyPropertyName(this->elem);
    if (vect.size() > 0)
      return vect[0].c_str();
    assert(0);
    return 0;
  }


  inline const char* PlyProperty::TypeStr()
  {
    switch (this->type)
    {
    case NNP_UNKNOWN_TYPE:    return "NNP_UNKNOWN_TYPE       ";
    case NNP_FLOAT32:    return "NNP_FLOAT32            ";
    case NNP_FLOAT64:    return "NNP_FLOAT64            ";
    case NNP_INT8:    return "NNP_INT8               ";
    case NNP_INT16:    return "NNP_INT16              ";
    case NNP_INT32:    return "NNP_INT32              ";
    case NNP_UINT8:    return "NNP_UINT8              ";
    case NNP_UINT16:    return "NNP_UINT16             ";
    case NNP_UINT32:    return "NNP_UINT32             ";
    case NNP_LIST_UINT8_UINT32:    return "NNP_LIST_UINT8_UINT32  ";
    case NNP_LIST_INT8_UINT32:    return "NNP_LIST_INT8_UINT32   ";
    case NNP_LIST_UINT8_INT32:    return "NNP_LIST_UINT8_INT32   ";
    case NNP_LIST_INT8_INT32:    return "NNP_LIST_INT8_INT32    ";
    case NNP_LIST_UINT8_FLOAT32:    return "NNP_LIST_UINT8_FLOAT32 ";
    case NNP_LIST_INT8_FLOAT32:    return "NNP_LIST_INT8_FLOAT32  ";
    case NNP_LIST_UINT8_FLOAT64:    return "NNP_LIST_UINT8_FLOAT64 ";
    case NNP_LIST_INT8_FLOAT64:    return "NNP_LIST_INT8_FLOAT64  ";
    case NNP_LIST_UINT8_UINT8:    return "NNP_LIST_UINT8_UINT8  ";
    case NNP_LIST_INT8_UINT8:    return "NNP_LIST_INT8_UINT8   ";
    case NNP_LIST_UINT8_INT8:    return "NNP_LIST_UINT8_INT8   ";
    case NNP_LIST_INT8_INT8:    return "NNP_LIST_INT8_INT8    ";
    case NNP_LIST_UINT8_UINT16:    return "NNP_LIST_UINT8_UINT16  ";
    case NNP_LIST_INT8_UINT16:    return "NNP_LIST_INT8_UINT16   ";
    case NNP_LIST_UINT8_INT16:    return "NNP_LIST_UINT8_INT16   ";
    case NNP_LIST_INT8_INT16:    return "NNP_LIST_INT8_INT16    ";
    default: assert(0);
      break;
    }
    return 0;
  }


  inline int PlyProperty::TypeSize()
  {
    switch (this->type)
    {
    case NNP_UNKNOWN_TYPE:
      return 0;
    case NNP_INT8:
    case NNP_UINT8:
      return sizeof(char);
    case NNP_INT16:
    case NNP_UINT16:
      return sizeof(short);
    case NNP_FLOAT32:
    case NNP_INT32:
    case NNP_UINT32:
      return sizeof(int);
    case NNP_FLOAT64:
      return sizeof(double);
    case NNP_LIST_UINT8_UINT32:
    case NNP_LIST_INT8_UINT32:
    case NNP_LIST_UINT8_INT32:
    case NNP_LIST_INT8_INT32:
      return sizeof(int);
    case NNP_LIST_UINT8_UINT16:
    case NNP_LIST_INT8_UINT16:
    case NNP_LIST_UINT8_INT16:
    case NNP_LIST_INT8_INT16:
      return sizeof(short);
    case NNP_LIST_UINT8_UINT8:
    case NNP_LIST_INT8_UINT8:
    case NNP_LIST_UINT8_INT8:
    case NNP_LIST_INT8_INT8:
      return sizeof(char);
    case NNP_LIST_UINT8_FLOAT32:
    case NNP_LIST_INT8_FLOAT32:
      return sizeof(float);
    case NNP_LIST_UINT8_FLOAT64:
    case NNP_LIST_INT8_FLOAT64:
      return sizeof(double);
    default: assert(0);
      break;
    }
    return 0;
  }


  inline bool PlyProperty::IsSigned()
  {
    switch (this->type)
    {
    case NNP_INT8:
    case NNP_INT16:
    case NNP_INT32:
    case NNP_FLOAT32:
    case NNP_FLOAT64:
    case NNP_LIST_INT8_UINT32:
    case NNP_LIST_INT8_INT32:
    case NNP_LIST_INT8_UINT16:
    case NNP_LIST_INT8_INT16:
    case NNP_LIST_INT8_UINT8:
    case NNP_LIST_INT8_INT8:
    case NNP_LIST_INT8_FLOAT32:
    case NNP_LIST_INT8_FLOAT64:
      return true;
    case NNP_UINT8:
    case NNP_UINT16:
    case NNP_UINT32:
    case NNP_LIST_UINT8_UINT32:
    case NNP_LIST_UINT8_INT32:
    case NNP_LIST_UINT8_UINT16:
    case NNP_LIST_UINT8_INT16:
    case NNP_LIST_UINT8_UINT8:
    case NNP_LIST_UINT8_INT8:
    case NNP_LIST_UINT8_FLOAT32:
    case NNP_LIST_UINT8_FLOAT64:
      return false;
    default:
      return false;
    }
  }


  inline int PlyProperty::CountValue()
  {
    if (this->elem == NNP_CRGB || this->elem == NNP_NXYZ || this->elem == NNP_PXYZ || this->elem == NNP_TEXTURE3D)
      return 3;
    else if (this->elem == NNP_CRGBA)
      return 4;
    else if (this->elem == NNP_TEXTURE2D)
      return 2;
    return 1;
  }


  inline bool PlyProperty::SkipAsciiPropertyInFile(PlyFile &file)
  {
    int count = CountValue();
    if (this->type >= NNP_LIST_UINT8_UINT32)
      file.ReadAsciiData(count);
    switch (type)
    {
    case NNP_INT8:
    case NNP_INT16:
    case NNP_INT32:
    case NNP_LIST_UINT8_INT32:
    case NNP_LIST_INT8_INT32:
    case NNP_LIST_UINT8_INT16:
    case NNP_LIST_INT8_INT16:
    case NNP_LIST_UINT8_INT8:
    case NNP_LIST_INT8_INT8:
    {
      int* temp = new int[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);
      delete[] temp;
      break;
    }
    case NNP_UINT8:
    case NNP_UINT16:
    case NNP_UINT32:
    case NNP_LIST_UINT8_UINT32:
    case NNP_LIST_INT8_UINT32:
    case NNP_LIST_UINT8_UINT16:
    case NNP_LIST_INT8_UINT16:
    case NNP_LIST_UINT8_UINT8:
    case NNP_LIST_INT8_UINT8:
    {
      unsigned int* temp = new unsigned int[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);
      delete[] temp;
      break;
    }
    case NNP_FLOAT32:
    case NNP_LIST_UINT8_FLOAT32:
    case NNP_LIST_INT8_FLOAT32:
    {
      float* temp = new float[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);
      delete[] temp;
      break;
    }
    case NNP_FLOAT64:
    case NNP_LIST_UINT8_FLOAT64:
    case NNP_LIST_INT8_FLOAT64:
    {
      double* temp = new double[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);
      delete[] temp;
      break;
    }
    default: assert(0);
    }
    return true;
  }


  inline bool PlyProperty::SkipBinaryPropertyInFile(PlyFile& file)
  {
    char * temp = nullptr;
    int count = CountValue();
    if (this->type >= NNP_LIST_UINT8_UINT32)
    {
      int size;
      if (this->IsSigned())
      {
        file.ReadBinaryData(temp, sizeof(char));
        size = this->TypeSize() * int(*(reinterpret_cast<unsigned char *>(temp)));
      }
      else
      {
        file.ReadBinaryData(temp, sizeof(char));
        size = this->TypeSize() * int(*(reinterpret_cast<unsigned char *>(temp)));
      }
      file.ReadBinaryData(temp, size);
    }
    else
    {
      int size = this->TypeSize() * count;
      file.ReadBinaryData(temp, size);
    }
    return true;
  }


  inline bool PlyProperty::WriteHeader(PlyFile& file)
  {
    if (!validToWrite)
      return true;
    std::string name, type;
    type = PlyTypeName(this->type)[0];
    name = this->EntityName();
    std::vector<std::string> v;
    switch (this->elem)
    {
    case NNP_PXYZ:
    {
      v.push_back(PlyPropertyName(NNP_PX)[0]);
      v.push_back(PlyPropertyName(NNP_PY)[0]);
      v.push_back(PlyPropertyName(NNP_PZ)[0]);
      break;
    }
    case NNP_NXYZ:
    {
      v.push_back(PlyPropertyName(NNP_NX)[0]);
      v.push_back(PlyPropertyName(NNP_NY)[0]);
      v.push_back(PlyPropertyName(NNP_NZ)[0]);
      break;
    }
    case NNP_CRGB:
    {
      v.push_back(PlyPropertyName(NNP_CR)[0]);
      v.push_back(PlyPropertyName(NNP_CG)[0]);
      v.push_back(PlyPropertyName(NNP_CB)[0]);
      break;
    }
    case NNP_CRGBA:
    {
      v.push_back(PlyPropertyName(NNP_CR)[0]);
      v.push_back(PlyPropertyName(NNP_CG)[0]);
      v.push_back(PlyPropertyName(NNP_CB)[0]);
      v.push_back(PlyPropertyName(NNP_CA)[0]);
      break;
    }
    case NNP_TEXTURE2D:
    {
      v.push_back(PlyPropertyName(NNP_TEXTUREU)[0]);
      v.push_back(PlyPropertyName(NNP_TEXTUREV)[0]);
      break;
    }
    case NNP_TEXTURE3D:
    {
      v.push_back(PlyPropertyName(NNP_TEXTUREU)[0]);
      v.push_back(PlyPropertyName(NNP_TEXTUREV)[0]);
      v.push_back(PlyPropertyName(NNP_TEXTUREW)[0]);
      break;
    }
    default:
      v.push_back(name);

    }
    for (size_t i = 0; i < v.size(); i++)
    {
      std::stringstream s;
      s << "property " << type << " " << v[i] << "\n";
      if (!file.WriteHeaderLine(s.str()))
        return false;
    }
    return true;
  }




  /** PLY Element.
  *  Define a PLY Element as a collection of properties.
  */
  class PlyElement
  {
  public:

    std::string name;					/**< Name of the elment in the PLY header (for example "vertex", "face", ect.). */
    PlyElemEntity plyElem;				/**< Ply element entity. */
    size_t cnt;							/**< Number of instances of the elment in the PLY file. */
    std::vector<PlyProperty> propVec;   /**< Collection of properties that define the element. */
    bool validToWrite;					/**< Element validity (necessary to write the header). */

    /**
    * Default Constructor
    */
    inline PlyElement() : cnt(0), validToWrite(false){}

    /**
    * Constructor that sets the name, the properties and the number of instances of the element.
    *
    * @param _name		Name of the element.
    * @param prop		Vector of properties.
    * @param nElem		Number of instances.
    */
    inline PlyElement(const std::string& _name, std::vector<PlyProperty> &prop, size_t nElem) :name(_name), plyElem(PlyElemEntity::NNP_UNKNOWN_ELEM), cnt(nElem), propVec(prop), validToWrite(false){}

    /**
    * Constructor that sets the entity, the properties and the number of instances of the element.
    *
    * @param ent		Element entity.
    * @param prop		Vector of properties.
    * @param nElem		Number of instances.
    */
    inline PlyElement(PlyElemEntity ent, std::vector<PlyProperty> &prop, size_t nElem) :name(PlyElementName(ent)[0]), plyElem(ent), cnt(nElem), propVec(prop), validToWrite(false){}

    /**
    * Parse the input line and add the properties to the element.
    * It assumes that the passed line has the folowing structure: "property PLYTYPE PLYELEMENT"
    *
    * @param line		Input line.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool AddProperty(std::string &line);

    /**
    * Initialize an element from the header line.
    *
    * @param elemStr	String with the element definition.
    * @param propStr	Strings with the property definition.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool InitFromHeader(std::string &elemStr, std::vector<std::string> &propStr);

    /**
    * Write the element descriport in the file header.
    *
    * @param file	Ply file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteHeader(PlyFile &file);

    /**
    * Skip the element in an Ascii file.
    *
    * @param file	Ply file
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool SkipAsciiElementsInFile(PlyFile &file);

    /**
    * Skip the element in a binary file.
    *
    * @param file	Ply file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool SkipBinaryElementsInFile(PlyFile &file);

    /**
    * Check if the input entity is in the property of the element.
    *
    * @param entity		Input entity.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool Contains(NNP_ENTITY entity);
  };


  inline bool PlyElement::InitFromHeader(std::string &elemStr, std::vector<std::string> &propStr)
  {
    char* token;
    char* tempStr = &elemStr[0];
    token = strtok(tempStr, " \t");
    if (strstr(token, "element") == NULL)
      return false;
    token = strtok(0, " \t\r\n");
    name = std::string(token);
    plyElem = PlyElemEntity::NNP_UNKNOWN_ELEM;
    ElementMapIterator iter = mapElem.begin();
    bool found = false;
    while (iter != mapElem.end())
    {
      NameVector& v = (*iter).second;
      for (size_t i = 0; i < v.size(); i++)
      {
        if (v[i] == name)
        {
          found = true;
          break;
        }
      }
      if (found)
      {
        plyElem = (*iter).first;
        break;
      }
      iter++;
    }
    token = strtok(0, " \t\r\n");
    cnt = atoi(token);
    for (size_t i = 0; i < propStr.size(); i++)
      if (!AddProperty(propStr[i]))
        return false;
    unsigned int mask = 0;
    for (size_t i = 0; i < propVec.size(); i++)
      mask |= propVec[i].elem;
    std::vector<PlyProperty> compactPropVec;
    for (size_t i = 0; i < propVec.size(); i++)
    {
      switch (propVec[i].elem)
      {
      case NNP_NX:
      case NNP_NY:
        if ((mask & NNP_NXYZ) != NNP_NXYZ)
          compactPropVec.push_back(propVec[i]);
        break;
      case NNP_NZ:
        if ((mask & NNP_NXYZ) != NNP_NXYZ)
          compactPropVec.push_back(propVec[i]);
        else
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_NXYZ));
        break;
      case NNP_PX:
      case NNP_PY:
        if ((mask & NNP_PXYZ) != NNP_PXYZ)
          compactPropVec.push_back(propVec[i]);
        break;
      case NNP_PZ:
        if ((mask & NNP_PXYZ) != NNP_PXYZ)
          compactPropVec.push_back(propVec[i]);
        else
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_PXYZ));
        break;
      case NNP_CR:
      case NNP_CG:
        if (((mask & NNP_CRGB) != NNP_CRGB) && ((mask & NNP_CRGBA) != NNP_CRGBA))
          compactPropVec.push_back(propVec[i]);
        break;
      case NNP_CB:
        if (((mask & NNP_CRGB) != NNP_CRGB) & ((mask & NNP_CRGBA) != NNP_CRGBA))
          compactPropVec.push_back(propVec[i]);
        else if (((mask & NNP_CRGB) == NNP_CRGB) & ((mask & NNP_CRGBA) != NNP_CRGBA))
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_CRGB));
        break;
      case NNP_CA:
        if (((mask & NNP_CRGB) != NNP_CRGB) & ((mask & NNP_CRGBA) != NNP_CRGBA))
          compactPropVec.push_back(propVec[i]);
        else if ((mask & NNP_CRGBA) == NNP_CRGBA)
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_CRGBA));
        break;
      case NNP_TEXTUREU:
        if (((mask & NNP_TEXTURE2D) != NNP_TEXTURE2D) & ((mask & NNP_TEXTURE3D) != NNP_TEXTURE3D))
          compactPropVec.push_back(propVec[i]);
        break;
      case NNP_TEXTUREV:
        if (((mask & NNP_TEXTURE2D) != NNP_TEXTURE2D) & ((mask & NNP_TEXTURE3D) != NNP_TEXTURE3D))
          compactPropVec.push_back(propVec[i]);
        else if (((mask & NNP_TEXTURE2D) == NNP_TEXTURE2D) & ((mask & NNP_TEXTURE3D) != NNP_TEXTURE3D))
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_TEXTURE2D));
        break;
      case NNP_TEXTUREW:
        if (((mask & NNP_TEXTURE2D) != NNP_TEXTURE2D) & ((mask & NNP_TEXTURE3D) != NNP_TEXTURE3D))
          compactPropVec.push_back(propVec[i]);
        else if ((mask & NNP_TEXTURE3D) == NNP_TEXTURE3D)
          compactPropVec.push_back(PlyProperty(propVec[i].type, NNP_TEXTURE3D));
        break;
      default:
        compactPropVec.push_back(propVec[i]);
        break;
      }
    }
    propVec.clear();
    propVec = compactPropVec;
    return true;
  }


  inline bool PlyElement::WriteHeader(PlyFile &file)
  {
    if (!validToWrite || cnt == 0)
      return true;
    bool ok = true;
    std::stringstream temp;
    temp << "element " << name << " " << cnt << "\n";
    if (file.WriteHeaderLine(temp.str()))
    {
      for (size_t i = 0; i < propVec.size(); i++)
        ok = propVec[i].WriteHeader(file);
    }
    else
      return false;
    return ok;
  }


  inline bool PlyElement::SkipAsciiElementsInFile(PlyFile &file)
  {
    for (size_t i = 0; i < this->cnt; ++i)
      for (size_t j = 0; j < this->propVec.size(); ++j)
        this->propVec[j].SkipAsciiPropertyInFile(file);
    return true;
  }


  inline bool PlyElement::SkipBinaryElementsInFile(PlyFile &file)
  {
    for (size_t i = 0; i < this->cnt; ++i)
      for (size_t j = 0; j < this->propVec.size(); ++j)
        this->propVec[j].SkipBinaryPropertyInFile(file);
    return true;
  }


  inline bool PlyElement::AddProperty(std::string &line)
  {
    char* token;
    char* tempStr = &line[0];
    token = strtok(tempStr, " \t");
    if (strstr(token, "property") == NULL)
      return false;
    char* typeStr = strtok(0, " \t\n");
    char *ty1, *ty2;
    std::string type;
    type.append(typeStr);
    if (strcmp(typeStr, "list") == 0)
    {
      ty1 = strtok(0, " \t\n");
      ty2 = strtok(0, " \t\n");
      type.append(" ");
      type.append(ty1);
      type.append(" ");
      type.append(ty2);
    }
    PlyType plyType = PlyType::NNP_UNKNOWN_TYPE;
    TypeMapIterator iterType = mapType.begin();
    bool found = false;
    while (iterType != mapType.end())
    {
      NameVector& v = (*iterType).second;
      for (size_t i = 0; i < v.size(); i++)
      {
        if (v[i] == type)
        {
          found = true;
          break;
        }
      }
      if (found)
      {
        plyType = (*iterType).first;
        break;
      }
      iterType++;
    }
    if (plyType == PlyType::NNP_UNKNOWN_TYPE)
      return false;
    char* nameStr = strtok(0, " \t\n");
    PlyEntity plyEntity = PlyEntity::NNP_UNKNOWN_ENTITY;
    EntityMapIterator iterEnt = mapProp.begin();
    found = false;
    while (iterEnt != mapProp.end())
    {
      NameVector& v = (*iterEnt).second;
      for (size_t i = 0; i < v.size(); i++)
      {
        if (v[i] == nameStr)
        {
          found = true;
          break;
        }
      }
      if (found)
      {
        plyEntity = (*iterEnt).first;
        break;
      }
      iterEnt++;
    }
    if (plyEntity != PlyEntity::NNP_UNKNOWN_ENTITY)
      propVec.push_back(PlyProperty(plyType, plyEntity, nameStr));
    else
      propVec.push_back(PlyProperty(plyType, nameStr));
    return true;
  }


  inline bool PlyElement::Contains(PlyEntity entity)
  {
    for (size_t i = 0; i < propVec.size(); i++)
    {
      if (propVec[i].elem == entity)
        return true;
    }
    return false;
  }




  /** PLY header info.
  *  Define the data of the PLY header
  */
  class Info
  {

  public:
    ErrorCode errInfo;						/**< Error code returned by the reading of a PLY file. */
    bool binary;							/**< Boolean about the file format (Binary = true, ASCII = false). */
    std::vector<PlyElement> elemVec;		/**< Elements defined in the header. */
    bool bigEndian;							/**< Endianess of the binary file. */
    std::string filename;					/**< Filename. */
    std::vector<std::string> textureFile;	/**< Texture file names. */

    /**
    * Default Constructor
    */
    inline Info();

    /**
    * Constructor that reads the header info from a file.
    *
    * @param filename	Path of the file to read.
    */
    inline Info(const std::string& filename);

    /**
    * Load the ply info from the header of the input filename.
    *
    * @param filename	Path of the file to read.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool LoadHeader(const std::string& filename);

    /**
    * Write the ply info in the header of the input file.
    *
    * @param file	File to write.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteHeader(PlyFile& file);

    /**
    * Add the ply element to the header.
    *
    * @param pe	  Ply element to write in the header.
    */
    inline void AddPlyElement(PlyElement& pe);

    /**
    * Clear the error code.
    */
    inline void Clear() { errInfo = NNP_OK; }

    /**
    * Return the number of instances of the element with the input name
    *
    * @param name	Name of the element.
    * @return		The number of instances
    */
    inline size_t GetElementCount(const std::string& name);

    /**
    * Return the number of instances of the element with the input element type
    *
    * @param e	Element type.
    * @return	The number of instances
    */
    inline size_t GetElementCount(PlyElemEntity e);

    /**
    * Return the number of vertex instances
    *
    * @return The number of vertex instances
    */
    inline size_t GetVertexCount();

    /**
    * Return the number of face instances
    *
    * @return The number of face instances
    */
    inline size_t GetFaceCount();

    /**
    * Return the number of edge instances
    *
    * @return The number of edge instances
    */
    inline size_t GetEdgeCount();

    /**
    * Return a reference to the element with a specific name
    *
    * @param name	Name of the element.
    * @return		The reference to the element
    */
    inline PlyElement* GetElement(const std::string & name);

    /**
    * Return a reference to the element with a specific element type
    *
    * @param e	Element type.
    * @return	The reference to the element
    */
    inline PlyElement* GetElement(PlyElemEntity e);

    /**
    * Return a reference to the vertex element
    *
    * @return The reference to the vertex element
    */
    inline PlyElement* GetVertexElement();

    /**
    * Return a reference to the face element
    *
    * @return The reference to the face element
    */
    inline PlyElement* GetFaceElement();

    /**
    * Return a reference to the edge element
    *
    * @return The reference to the edge element
    */
    inline PlyElement* GetEdgeElement();

  };


  inline Info::Info()
  {
    this->binary = true;
    this->bigEndian = true;
    this->Clear();
  }


  inline Info::Info(const std::string& filename)
  {
    this->LoadHeader(filename);
  }


  inline bool Info::LoadHeader(const std::string& filename)
  {
    this->filename = filename;
    this->errInfo = NNP_OK;
    std::ifstream input(filename);
    if (!input.good())
    {
      this->errInfo = NNP_UNABLE_TO_OPEN;
      input.close();
      return false;
    }
    std::string buffer;
    std::getline(input, buffer);
    std::transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
    if (buffer != "ply")
    {
      this->errInfo = NNP_MISSING_HEADER;
      input.close();
      return false;
    }
    std::getline(input, buffer);
    std::transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
    std::size_t pos = buffer.find("format");
    if (pos == std::string::npos)
    {
      this->errInfo = NNP_MISSING_FORMAT;
      input.close();
      return false;
    }
    if (buffer.find("binary_lit") != std::string::npos)
    {
      this->binary = true;
      this->bigEndian = false;
    }
    else if (buffer.find("binary_big") != std::string::npos)
    {
      this->binary = true;
      this->bigEndian = true;
    }
    else if (buffer.find("ascii") != std::string::npos)
    {
      this->binary = false;
      this->bigEndian = false;
    }
    else
    {
      this->errInfo = NNP_MISSING_FORMAT;
      return false;
    }
    std::getline(input, buffer);
		std::string lowBuffer;
		lowBuffer.resize(buffer.size());		
		std::transform(buffer.begin(), buffer.end(), lowBuffer.begin(), ::tolower);
    while (lowBuffer != "end_header")
    {
      if (lowBuffer.find("element") != std::string::npos)
      {
        std::string elemStr = lowBuffer;
        std::vector<std::string> propStr;
        do
        {
          std::getline(input, buffer);
					lowBuffer.clear();
					lowBuffer.resize(buffer.size());
          std::transform(buffer.begin(), buffer.end(), lowBuffer.begin(), ::tolower);
          pos = lowBuffer.find("property");
          if (pos != std::string::npos)
            propStr.push_back(lowBuffer);
        } while (pos != std::string::npos);
        PlyElement pe;
        if (!pe.InitFromHeader(elemStr, propStr))
        {
          this->errInfo = NNP_INVALID_ELEMENT;
          input.close();
          return false;
        }
        elemVec.push_back(pe);
      }
      else
      {
        if (lowBuffer.find("comment texture") != std::string::npos)
          textureFile.push_back(buffer.substr(buffer.find(" ", 10) + 1));
        std::getline(input, buffer);
				lowBuffer.clear();
				lowBuffer.resize(buffer.size());
        std::transform(buffer.begin(), buffer.end(), lowBuffer.begin(), ::tolower);
      }
    }
    input.close();
    return true;
  }


  inline bool Info::WriteHeader(PlyFile& file)
  {
    bool ok = true;
    ok = file.WriteHeaderLine(std::string("ply\n"));
    if (this->binary)
      ok = file.WriteHeaderLine(std::string("format binary_little_endian 1.0\n"));
    else
      ok = file.WriteHeaderLine(std::string("format ascii 1.0\n"));
    ok = file.WriteHeaderLine(std::string("comment nanoply generated\n"));
    for (size_t i = 0; i < this->textureFile.size(); i++)
      ok = file.WriteHeaderLine(std::string("comment TextureFile ") + this->textureFile[i] + "\n");
    for (size_t i = 0; i < this->elemVec.size(); i++)
      ok = this->elemVec[i].WriteHeader(file);
    ok = file.WriteHeaderLine(std::string("end_header\n"));
    return ok;
  }


  inline void Info::AddPlyElement(PlyElement& pe)
  {
    elemVec.push_back(pe);
  }


  inline size_t Info::GetElementCount(const std::string & name)
  {
    PlyElement* pe = GetElement(name);
    if (pe != NULL)
      return pe->cnt;
    return -1;
  }


  inline size_t Info::GetElementCount(PlyElemEntity e)
  {
    PlyElement* pe = GetElement(e);
    if (pe != NULL)
      return pe->cnt;
    return 0;
  }


  inline size_t Info::GetVertexCount()
  {
    return GetElementCount(PlyElemEntity::NNP_VERTEX_ELEM);
  }


  inline size_t Info::GetFaceCount()
  {
    return GetElementCount(PlyElemEntity::NNP_FACE_ELEM);
  }


  inline size_t Info::GetEdgeCount()
  {
    return GetElementCount(PlyElemEntity::NNP_EDGE_ELEM);
  }


  inline PlyElement* Info::GetElement(const std::string& name)
  {
    for (size_t i = 0; i < elemVec.size(); i++)
    {
      if (elemVec[i].name == name)
        return &elemVec[i];
    }
    return NULL;
  }


  inline PlyElement* Info::GetElement(PlyElemEntity e)
  {
    for (size_t i = 0; i < elemVec.size(); i++)
    {
      if (elemVec[i].plyElem == e)
        return &elemVec[i];
    }
    return NULL;
  }


  inline PlyElement* Info::GetVertexElement()
  {
    return GetElement(PlyElemEntity::NNP_VERTEX_ELEM);
  }


  inline PlyElement* Info::GetFaceElement()
  {
    return GetElement(PlyElemEntity::NNP_FACE_ELEM);
  }


  inline PlyElement* Info::GetEdgeElement()
  {
    return GetElement(PlyElemEntity::NNP_EDGE_ELEM);
  }



  /** Abstract class for the descriptor of a Ply propertie.
  *	The class defines how a PlyProperty is saved in memory.
  */
  class DescriptorInterface
  {

  public:

    int64_t curPos;		/**< Position of the next property to read or to write. */

    void *base;			/**< Pointer to the memory location that contains the data of the property. */

    PlyEntity elem;		/**< Ply entity managed by the descriptor. */

    std::string name;	/**< Name of the PlyProperty*/

    /**
    * Constructor of the descriptor.
    *
    * @param _e	Ply entity managed by the descriptor.
    * @param _b	Pointer to the memory location that contains the data of the property.
    */
    inline DescriptorInterface(PlyEntity _e, void *_b) :curPos(0), base(_b), elem(_e), name(PlyPropertyName(_e)[0]){}

    /**
    * Constructor of the descriptor.
    *
    * @param _s		Name of the PlyProperty.
    * @param _b	Pointer to the memory location that contains the data of the property.
    */
    inline DescriptorInterface(const std::string& _s, void *_b) :curPos(0), base(_b), elem(PlyEntity::NNP_UNKNOWN_ENTITY), name(_s){}

	/**
    * Virtual destructor
    */
    virtual ~DescriptorInterface() {}

    /**
    * Restart the descriptor.
    */
    virtual void Restart() = 0;

    /**
    * Read the property data from the binary file.
    *
    * @param file		Input file.
    * @param prop		PLY property to read from the file.
    * @param fixEndian	If true the method adjust the endianess of the data.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    virtual bool ReadElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian) = 0;

    /**
    * Read the property data from the ascii file.
    *
    * @param file	Input file.
    * @param prop	PLY property to read from the file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    virtual bool ReadElemAscii(PlyFile &file, PlyProperty &prop) = 0;

    /**
    * Write the property data in the binary file.
    *
    * @param file		Input file.
    * @param prop		PLY property to write in the file.
    * @param fixEndian	If true the method adjust the endianess of the data.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    virtual bool WriteElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian) = 0;

    /**
    * Write the property data in the ascii file.
    *
    * @param file	Input file.
    * @param prop	PLY property to write in the file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    virtual bool WriteElemAscii(PlyFile &file, PlyProperty &prop) = 0;
  };



  /** Memory descriptor of a Ply element.
  *	The class defines how a PlyElement is saved in memory.
  */
  class ElementDescriptor
  {
  public:

    typedef std::vector<nanoply::DescriptorInterface*> PropertyDescriptor;

    std::string name;					/**< Name of the Ply element. */
    PlyElemEntity elem;					/**< PLY Element Entity. */
    PropertyDescriptor dataDescriptor;	/**< Vector of property descriptor. */

    /**
    * Constructor of the Ply element descriptor.
    *
    * @param _e		Ply Element entity managed by the descriptor.
    */
    inline ElementDescriptor(PlyElemEntity _e) : name(PlyElementName(_e)[0]), elem(_e){}

    /**
    * Constructor of the Ply element descriptor.
    *
    * @param _s		Name of the Ply element managed by the descriptor.
    */
    inline ElementDescriptor(const std::string &_s) : name(_s), elem(PlyElemEntity::NNP_UNKNOWN_ELEM){}

    /**
    * Read all the properties of the element from the binary file.
    *
    * @param file		Input file.
    * @param elem		PLY element to read from the file.
    * @param fixEndian	If true the method adjust the endianess of the data.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool ReadElemBinary(PlyFile &file, PlyElement &elem, bool fixEndian);

    /**
    * Read all the property data of the element from the ascii file.
    *
    * @param file	Input file.
    * @param elem	PLY element to read from the file.
    * @return		If successful returns true. Otherwise, it returns false.
    */
    inline bool ReadElemAscii(PlyFile &file, PlyElement &elem);

    /**
    * Write all the property data of the element in the binary file.
    *
    * @param file		Input file.
    * @param elem		PLY element to write from the file.
    * @param fixEndian	If true the method adjust the endianess of the data.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteElemBinary(PlyFile &file, PlyElement &elem, bool fixEndian);

    /**
    * Write all the property data of the element in the ascii file.
    *
    * @param file		Input file.
    * @param elem		PLY element to write from the file.
    * @return			If successful returns true. Otherwise, it returns false.
    */
    inline bool WriteElemAscii(PlyFile &file, PlyElement &elem);

    /**
    * Check if the properties defined in input element have a proper data descriport to write in the file.
    * It sets the variable "validToWrite" for all the Ply properties with a data descriport.
    *
    * @param elem		PLY element to write from the file.
    */
    inline void CheckDescriptor(PlyElement &elem);

  private:

    inline void ExtractDescriptor(PropertyDescriptor &descr, PlyElement &elem);

  };


  inline void ElementDescriptor::ExtractDescriptor(PropertyDescriptor& descr, PlyElement &elem)
  {
    for (size_t j = 0; j < elem.propVec.size(); j++)
    {
      PlyProperty& prop = elem.propVec[j];
      size_t i = 0;
      for (; i < dataDescriptor.size(); i++)
      {
        if (dataDescriptor[i]->elem == prop.elem)
        {
          if (prop.elem != PlyEntity::NNP_UNKNOWN_ENTITY)
          {
            descr.push_back(dataDescriptor[i]);
            break;
          }
          else //if (dataDescriptor[i]->name == prop.name)
          {
            std::string name1(dataDescriptor[i]->name);
            std::string name2(prop.name);
            std::transform(name1.begin(), name1.end(), name1.begin(), ::tolower);
            std::transform(name2.begin(), name2.end(), name2.begin(), ::tolower);
            if (name1 == name2)
            {
              descr.push_back(dataDescriptor[i]);
              break;
            }
          }
        }
        else if ((dataDescriptor[i]->elem == PlyEntity::NNP_CRGB && prop.elem == PlyEntity::NNP_CRGBA) ||
          (dataDescriptor[i]->elem == PlyEntity::NNP_CRGBA && prop.elem == PlyEntity::NNP_CRGB))
        {
          descr.push_back(dataDescriptor[i]);
          break;
        }
      }
      if (i == dataDescriptor.size())
        descr.push_back(NULL);
    }
  }


  inline bool ElementDescriptor::ReadElemBinary(PlyFile &file, PlyElement &elem, bool fixEndian)
  {
    PropertyDescriptor descr;
    ExtractDescriptor(descr, elem);
    for (size_t i = 0; i < elem.cnt; i++)
    {
      for (size_t j = 0; j < elem.propVec.size(); j++)
      {
        PlyProperty& prop = elem.propVec[j];
        if (descr[j] != NULL)
          (*descr[j]).ReadElemBinary(file, prop, fixEndian);
        else
          prop.SkipBinaryPropertyInFile(file);
      }
    }
    return true;
  }


  inline bool ElementDescriptor::ReadElemAscii(PlyFile &file, PlyElement &elem)
  {
    PropertyDescriptor descr;
    ExtractDescriptor(descr, elem);
    for (size_t i = 0; i < elem.cnt; i++)
    {
      for (size_t j = 0; j < elem.propVec.size(); j++)
      {
        PlyProperty& prop = elem.propVec[j];
        if (descr[j] != NULL)
          (*descr[j]).ReadElemAscii(file, prop);
        else
          prop.SkipAsciiPropertyInFile(file);
      }
    }
    return true;
  }

  inline bool ElementDescriptor::WriteElemBinary(PlyFile &file, PlyElement &elem, bool fixEndian)
  {
    PropertyDescriptor descr;
    ExtractDescriptor(descr, elem);
    for (size_t i = 0; i < elem.cnt; i++)
    {
      for (size_t j = 0; j < elem.propVec.size(); j++)
      {
        if (descr[j] != NULL)
          (*descr[j]).WriteElemBinary(file, elem.propVec[j], fixEndian);
      }
    }
    return true;
  }

  inline bool ElementDescriptor::WriteElemAscii(PlyFile &file, PlyElement &elem)
  {
    PropertyDescriptor descr;
    ExtractDescriptor(descr, elem);
    for (size_t i = 0; i < elem.cnt; i++)
    {
      bool first = true;
      for (size_t j = 0; j < elem.propVec.size(); j++)
      {
        if (descr[j] != NULL)
        {
          if (!first)
            file.WriteAsciiData(std::string(" "));
          else
            first = false;
          (*descr[j]).WriteElemAscii(file, elem.propVec[j]);
        }
      }
      file.WriteAsciiData(std::string("\n"));
    }
    return true;
  }


  inline void ElementDescriptor::CheckDescriptor(PlyElement &elem)
  {
    if (elem.propVec.size() == 0)
    {
      elem.validToWrite = false;
      return;
    }
    elem.validToWrite = true;
    PropertyDescriptor descr;
    ExtractDescriptor(descr, elem);
    for (size_t j = 0; j < elem.propVec.size(); j++)
    {
      if (descr[j] != NULL)
        elem.propVec[j].validToWrite = true;
    }
  }



  template<class ContainerType, int VectorSize, typename ScalarType>
  class DescriptorHelper
  {

  public:

    template<typename C>
    static void ReadBinary(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop, bool fixEndian)
    {
      char * buffer = nullptr;
      int size;
      int count = prop.CountValue();
      int typeSize = prop.TypeSize();
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        file.ReadBinaryData(buffer, sizeof(char));
        const int cntList = int(*(reinterpret_cast<unsigned char *>(buffer)));
        size = typeSize * cntList;
        count = cntList;
      }
      else
        size = typeSize * count;
      file.ReadBinaryData(buffer, size);

      if (typeSize > 1 && fixEndian)
        adjustEndianess(reinterpret_cast<unsigned char *>(buffer), typeSize, count);

      unsigned char* baseProp = (unsigned char*)descr.base + descr.curPos * sizeof(ContainerType);
      C* temp = (C*)buffer;
      if ((prop.elem == NNP_CRGB || prop.elem == NNP_CRGBA))
      {
        float norm = 1.0f;
        if (std::is_same<ScalarType, float>::value && std::is_same<C, unsigned char>::value)
          norm = 1.0f / 255.0f;
        else if (std::is_same<ScalarType, unsigned char>::value && std::is_same<C, float>::value)
          norm = 255.0f;
        for (int i = 0; i < std::min(VectorSize, count); i++)
          *((ScalarType *)(baseProp + i * sizeof(ScalarType))) = ScalarType(temp[i] * norm);
      }
      else
      {
        for (int i = 0; i < std::min(VectorSize, count); i++)
          *((ScalarType *)(baseProp + i * sizeof(ScalarType))) = ScalarType(temp[i]);
      }
      ++(descr.curPos);
    }

    template<typename C>
    static void ReadAscii(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop)
    {
      int count = prop.CountValue();
      if (prop.type >= NNP_LIST_UINT8_UINT32)
        file.ReadAsciiData(count);

      C* temp = new C[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);

      unsigned char* baseProp = (unsigned char*)descr.base + descr.curPos * sizeof(ContainerType);
      if ((prop.elem == NNP_CRGB || prop.elem == NNP_CRGBA))
      {
        float norm = 1.0f;
        if (std::is_same<ScalarType, float>::value && prop.type == NNP_UINT8)
          norm = 1.0f / 255.0f;
        else if (std::is_same<ScalarType, unsigned char>::value && prop.type == NNP_FLOAT32)
          norm = 255.0f;
        for (int i = 0; i < std::min(VectorSize, count); i++)
          *((ScalarType *)(baseProp + i * sizeof(ScalarType))) = ScalarType(temp[i] * norm);
      }
      else
      {
        for (int i = 0; i < std::min(VectorSize, count); i++)
          *((ScalarType *)(baseProp + i * sizeof(ScalarType))) = ScalarType(temp[i]);
      }
      delete[] temp;
      ++(descr.curPos);
    }

    template<typename C>
    static void WriteBinary(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop, bool fixEndian)
    {
      (void)fixEndian;
      int count = prop.CountValue();
      C data[VectorSize];
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        if (prop.IsSigned())
        {
          char listSize = (char)VectorSize;
          file.WriteBinaryData(&listSize, 1);
          count = VectorSize;
        }
        else
        {
          unsigned char listSize = (unsigned char)VectorSize;
          file.WriteBinaryData(&listSize, 1);
          count = VectorSize;
        }
      }

      C temp = 0;
      unsigned char* baseProp = (unsigned char*)descr.base + descr.curPos * sizeof(ContainerType);
      if ((prop.elem == NNP_CRGB || prop.elem == NNP_CRGBA))
      {
        float norm = 1.0f;
        if (std::is_same<ScalarType, float>::value && std::is_same<C, unsigned char>::value)
          norm = 255.0f;
        else if (std::is_same<ScalarType, unsigned char>::value && std::is_same<C, float>::value)
          norm = 1.0f / 255.0f;
        for (int i = 0; i < std::min(VectorSize, count); i++)
          data[i] = (C)((*(ScalarType*)(baseProp + i * sizeof(ScalarType))) * norm);
      }
      else
      {
        for (int i = 0; i < std::min(VectorSize, count); i++)
          data[i] = (C)((*(ScalarType*)(baseProp + i * sizeof(ScalarType))));
      }

      if (sizeof(C) > 1 && fixEndian)
        adjustEndianess((unsigned char*)data, sizeof(C), std::min(VectorSize, count));

      file.WriteBinaryData(data, sizeof(C)*std::min(VectorSize, count));
      for (int i = 0; i < (count - VectorSize); i++)
        file.WriteBinaryData(&temp, sizeof(C));
      ++(descr.curPos);
    }

    template<typename C>
    static void WriteAscii(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop)
    {
      int count = prop.CountValue();
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        if (prop.IsSigned())
        {
          int listSize = (int)VectorSize;
          file.WriteAsciiData(listSize);
          count = VectorSize;
        }
        else
        {
          unsigned int listSize = (unsigned int)VectorSize;
          file.WriteAsciiData(listSize);
          count = VectorSize;
        }
        file.WriteAsciiData(std::string(" "));
      }

      C data[VectorSize];
      unsigned char* baseProp = (unsigned char*)descr.base + descr.curPos * sizeof(ContainerType);
      if ((prop.elem == NNP_CRGB || prop.elem == NNP_CRGBA))
      {
        float norm = 1.0;
        if (std::is_same<ScalarType, float>::value && prop.type == NNP_UINT8)
          norm = 255.0f;
        else if (std::is_same<ScalarType, unsigned char>::value && prop.type == NNP_FLOAT32)
          norm = 1.0f / 255.0f;
        for (int i = 0; i < std::min(VectorSize, count); i++)
          data[i] = (C)((*(ScalarType*)(baseProp + i * sizeof(ScalarType))) * norm);
      }
      else
      {
        for (int i = 0; i < std::min(VectorSize, count); i++)
          data[i] = (C)((*(ScalarType*)(baseProp + i * sizeof(ScalarType))));
      }


      for (int i = 0; i < (count - VectorSize); i++)
        data[i] = 0;

      for (int i = 0; i < count; i++)
      {
        file.WriteAsciiData(data[i]);
        if (i < count - 1)
          file.WriteAsciiData(std::string(" "));
      }
      ++(descr.curPos);
    }
  };




  template<class ContainerType, typename ScalarType>
  class DescriptorHelper<ContainerType, 0, ScalarType>
  {

  public:

    template<typename C>
    static void ReadBinary(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop, bool fixEndian)
    {
      char * buffer = nullptr;
      int size;
      int count = prop.CountValue();
      int typeSize = prop.TypeSize();
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        file.ReadBinaryData(buffer, sizeof(char));
        const int cntList = int(*(reinterpret_cast<unsigned char *>(buffer)));
        size = typeSize * cntList;
        count = cntList;
      }
      else
        size = typeSize * count;
      file.ReadBinaryData(buffer, size);

      if (typeSize > 1 && fixEndian)
        adjustEndianess(reinterpret_cast<unsigned char *>(buffer), typeSize, count);

      C* temp = (C*)buffer;

      ContainerType* container = (ContainerType*)((unsigned char*)descr.base + descr.curPos * sizeof(ContainerType));
      (*container).resize(count);
      for (int i = 0; i < count; i++)
        (*container)[i] = (ScalarType)(temp[i]);
      ++(descr.curPos);
    }

    template<typename C>
    static void ReadAscii(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop)
    {
      int count = prop.CountValue();
      if (prop.type >= NNP_LIST_UINT8_UINT32)
        file.ReadAsciiData(count);

      C* temp = new C[count];
      for (int i = 0; i < count; i++)
        file.ReadAsciiData(temp[i]);

      ContainerType* container = (ContainerType*)((unsigned char*)descr.base + descr.curPos * sizeof(ContainerType));
      (*container).resize(count);
      for (int i = 0; i < count; i++)
        (*container)[i] = (ScalarType)(temp[i]);
      delete[] temp;
      ++(descr.curPos);
    }

    template<typename C>
    static void WriteBinary(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop, bool fixEndian)
    {
      size_t count = prop.CountValue();
      ContainerType* list = (ContainerType*)((unsigned char*)descr.base + descr.curPos * sizeof(ContainerType));
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        if (prop.IsSigned())
        {
          char listSize = (char)list->size();
          file.WriteBinaryData(&listSize, 1);
          count = list->size();
        }
        else
        {
          unsigned char listSize = (unsigned char)list->size();
          file.WriteBinaryData(&listSize, 1);
          count = list->size();
        }
      }

      std::vector<C> data(count);

      for (size_t i = 0; i < std::min(count, list->size()); i++)
        data[i] = (C)((*list)[i]);

      if (sizeof(C) > 1 && fixEndian)
        adjustEndianess((unsigned char*)data.data(), sizeof(C), std::min(count, list->size()));

      file.WriteBinaryData(data.data(), sizeof(C)*std::min(count, list->size()));
      C temp = 0;
      for (size_t i = 0; i < (count - list->size()); i++)
        file.WriteBinaryData(&temp, sizeof(C));
      ++(descr.curPos);
    }

    template<typename C>
    static void WriteAscii(DescriptorInterface& descr, PlyFile &file, PlyProperty &prop)
    {
      size_t count = prop.CountValue();
      ContainerType* list = (ContainerType*)((unsigned char*)descr.base + descr.curPos * sizeof(ContainerType));
      if (prop.type >= NNP_LIST_UINT8_UINT32)
      {
        if (prop.IsSigned())
        {
          int listSize = (int)list->size();
          file.WriteAsciiData(listSize);
          count = list->size();
        }
        else
        {
          unsigned int listSize = (unsigned int)list->size();
          file.WriteAsciiData(listSize);
          count = list->size();
        }
        file.WriteAsciiData(std::string(" "));
      }

      std::vector<C> data(count);
      for (size_t i = 0; i < std::min(count, list->size()); i++)
        data[i] = (C)((*list)[i]);

      for (size_t i = 0; i < (count - list->size()); i++)
        data[i] = 0;

      for (size_t i = 0; i < count; i++)
      {
        file.WriteAsciiData(data[i]);
        if (i < count - 1)
          file.WriteAsciiData(std::string(" "));
      }
      ++(descr.curPos);
    }
  };



  
  /** Memory descriptor of a vector of properties.
  *	The class defines how a vector of PlyProperty is saved in memory.
  *
  *  @tparam ContainerType	Type of the container of the property
  *  @tparam VectorSize		Number of values stored in the property.
  *  @tparam ScalarType		Type of the values stored in the property.
  */
  template<class ContainerType, int VectorSize, typename ScalarType>
  class DataDescriptor : public DescriptorInterface
  {
  public:

    DescriptorHelper<ContainerType, VectorSize, ScalarType> helper;

    inline DataDescriptor();

    /**
    * Constructor of the descriptor.
    *
    * @param _e	Ply entity managed by the descriptor.
    * @param _b	Pointer to the memory location that contains the data of the property.
    */
    inline DataDescriptor(PlyEntity _e, void *_b) :DescriptorInterface(_e, _b){};

    /**
    * Constructor of the descriptor.
    *
    * @param _s	Name of the PlyProperty.
    * @param _b	Pointer to the memory location that contains the data of the property.
    */
    inline DataDescriptor(const std::string& _s, void *_b) :DescriptorInterface(_s, _b){};

    inline void Restart();

    inline bool ReadElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian);

    inline bool ReadElemAscii(PlyFile &file, PlyProperty &prop);

    inline bool WriteElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian);

    inline bool WriteElemAscii(PlyFile &file, PlyProperty &prop);
  };
  

  template<class ContainerType, int VectorSize, typename ScalarType>
  void DataDescriptor<ContainerType, VectorSize, ScalarType>::Restart()
  {
    this->curPos = 0;
  }


	template<class ContainerType, int VectorSize, typename ScalarType>
	bool DataDescriptor<ContainerType, VectorSize, ScalarType>::ReadElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian)
	{
		if (prop.elem != elem && ((prop.elem != PlyEntity::NNP_CRGB && prop.elem != PlyEntity::NNP_CRGBA) || (prop.elem != PlyEntity::NNP_CRGBA && prop.elem != PlyEntity::NNP_CRGB)))
			return false;
		switch (prop.type)
		{
		case NNP_LIST_INT8_INT8:
		case NNP_LIST_UINT8_INT8:
		case NNP_INT8:
			helper.template ReadBinary<char>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_UINT8:
		case NNP_LIST_UINT8_UINT8:
		case NNP_UINT8:
			helper.template ReadBinary<unsigned char>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_INT16:
		case NNP_LIST_UINT8_INT16:
		case NNP_INT16:
			helper.template ReadBinary<short>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_UINT16:
		case NNP_LIST_UINT8_UINT16:
		case NNP_UINT16:
			helper.template ReadBinary<unsigned short>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_FLOAT32:
		case NNP_LIST_UINT8_FLOAT32:
		case NNP_FLOAT32:
			helper.template ReadBinary<float>(*this, file, prop, fixEndian); break;
		case NNP_LIST_UINT8_INT32:
		case NNP_LIST_INT8_INT32:
		case NNP_INT32:
			helper.template ReadBinary<int>(*this, file, prop, fixEndian); break;
		case NNP_LIST_UINT8_UINT32:
		case NNP_LIST_INT8_UINT32:
		case NNP_UINT32:
			helper.template ReadBinary<unsigned int>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_FLOAT64:
		case NNP_LIST_UINT8_FLOAT64:
		case NNP_FLOAT64:
			helper.template ReadBinary<double>(*this, file, prop, fixEndian); break;
		case NNP_UNKNOWN_TYPE:
			break;
		}
		return true;
	}


	template<class ContainerType, int VectorSize, typename ScalarType>
	bool DataDescriptor<ContainerType, VectorSize, ScalarType>::ReadElemAscii(PlyFile &file, PlyProperty &prop)
	{
		if (prop.elem != elem)
			return false;
		switch (prop.type)
		{
		case NNP_LIST_UINT8_INT8:
		case NNP_LIST_INT8_INT8:
		case NNP_INT8:
			helper.template ReadAscii<int>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT8:
		case NNP_LIST_INT8_UINT8:
		case NNP_UINT8:
			helper.template ReadAscii<unsigned int>(*this, file, prop); break;
		case NNP_LIST_UINT8_INT16:
		case NNP_LIST_INT8_INT16:
		case NNP_INT16:
			helper.template ReadAscii<short>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT16:
		case NNP_LIST_INT8_UINT16:
		case NNP_UINT16:
			helper.template ReadAscii<unsigned short>(*this, file, prop); break;
		case NNP_LIST_UINT8_FLOAT32:
		case NNP_LIST_INT8_FLOAT32:
		case NNP_FLOAT32:
			helper.template ReadAscii<float>(*this, file, prop); break;
		case NNP_LIST_UINT8_INT32:
		case NNP_LIST_INT8_INT32:
		case NNP_INT32:
			helper.template ReadAscii<int>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT32:
		case NNP_LIST_INT8_UINT32:
		case NNP_UINT32:
			helper.template ReadAscii<unsigned int>(*this, file, prop); break;
		case NNP_LIST_UINT8_FLOAT64:
		case NNP_LIST_INT8_FLOAT64:
		case NNP_FLOAT64:
			helper.template ReadAscii<double>(*this, file, prop); break;
		case NNP_UNKNOWN_TYPE:
			break;
		}
		return true;
	}


	template<class ContainerType, int VectorSize, typename ScalarType>
	bool DataDescriptor<ContainerType, VectorSize, ScalarType>::WriteElemBinary(PlyFile &file, PlyProperty &prop, bool fixEndian)
	{
		if (prop.elem != elem)
			return false;
		switch (prop.type)
		{
		case NNP_LIST_INT8_INT8:
		case NNP_LIST_UINT8_INT8:
		case NNP_INT8:
			helper.template WriteBinary<char>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_UINT8:
		case NNP_LIST_UINT8_UINT8:
		case NNP_UINT8:
			helper.template WriteBinary<unsigned char>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_INT16:
		case NNP_LIST_UINT8_INT16:
		case NNP_INT16:
			helper.template WriteBinary<short>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_UINT16:
		case NNP_LIST_UINT8_UINT16:
		case NNP_UINT16:
			helper.template WriteBinary<unsigned short>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_FLOAT32:
		case NNP_LIST_UINT8_FLOAT32:
		case NNP_FLOAT32:
			helper.template WriteBinary<float>(*this, file, prop, fixEndian); break;
		case NNP_LIST_UINT8_INT32:
		case NNP_LIST_INT8_INT32:
		case NNP_INT32:
			helper.template WriteBinary<int>(*this, file, prop, fixEndian); break;
		case NNP_LIST_UINT8_UINT32:
		case NNP_LIST_INT8_UINT32:
		case NNP_UINT32:
			helper.template WriteBinary<unsigned int>(*this, file, prop, fixEndian); break;
		case NNP_LIST_INT8_FLOAT64:
		case NNP_LIST_UINT8_FLOAT64:
		case NNP_FLOAT64:
			helper.template WriteBinary<double>(*this, file, prop, fixEndian); break;
		case NNP_UNKNOWN_TYPE:
			break;
		}
		return true;
	}


	template<class ContainerType, int VectorSize, typename ScalarType>
	bool DataDescriptor<ContainerType, VectorSize, ScalarType>::WriteElemAscii(PlyFile &file, PlyProperty& prop)
	{
		if (prop.elem != elem)
			return false;
		if (prop.elem == PlyEntity::NNP_UNKNOWN_ENTITY && prop.name != name)
			return false;
		switch (prop.type)
		{
		case NNP_LIST_UINT8_INT8:
		case NNP_LIST_INT8_INT8:
		case NNP_INT8:
			helper.template WriteAscii<int>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT8:
		case NNP_LIST_INT8_UINT8:
		case NNP_UINT8:
			helper.template WriteAscii<unsigned int>(*this, file, prop); break;
		case NNP_LIST_UINT8_INT16:
		case NNP_LIST_INT8_INT16:
		case NNP_INT16:
			helper.template WriteAscii<short>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT16:
		case NNP_LIST_INT8_UINT16:
		case NNP_UINT16:
			helper.template WriteAscii<unsigned short>(*this, file, prop); break;
		case NNP_LIST_UINT8_FLOAT32:
		case NNP_LIST_INT8_FLOAT32:
		case NNP_FLOAT32:
			helper.template WriteAscii<float>(*this, file, prop); break;
		case NNP_LIST_UINT8_INT32:
		case NNP_LIST_INT8_INT32:
		case NNP_INT32:
			helper.template WriteAscii<int>(*this, file, prop); break;
		case NNP_LIST_UINT8_UINT32:
		case NNP_LIST_INT8_UINT32:
		case NNP_UINT32:
			helper.template WriteAscii<unsigned int>(*this, file, prop); break;
		case NNP_LIST_UINT8_FLOAT64:
		case NNP_LIST_INT8_FLOAT64:
		case NNP_FLOAT64:
			helper.template WriteAscii<double>(*this, file, prop); break;
		case NNP_UNKNOWN_TYPE:
			break;
		}
		return true;
	}



  template <size_t ActionType>
  inline bool ElemProcessing(ElementDescriptor& elemDescr, PlyElement &elem, PlyFile& file, bool fixEndian)
  {
    if ((elemDescr.elem != PlyElemEntity::NNP_UNKNOWN_ELEM && elemDescr.elem == elem.plyElem) ||
      (elemDescr.elem == PlyElemEntity::NNP_UNKNOWN_ELEM && elemDescr.name == elem.name))
    {
      if (ActionType == 0)
        elemDescr.ReadElemBinary(file, elem, fixEndian);
      else if (ActionType == 1)
        elemDescr.ReadElemAscii(file, elem);
      else if (ActionType == 2)
        elemDescr.WriteElemBinary(file, elem, fixEndian);
      else if (ActionType == 3)
        elemDescr.WriteElemAscii(file, elem);
      else if (ActionType == 4)
        elemDescr.CheckDescriptor(elem);
      return true;
    }
    return false;
  }

  typedef std::vector<ElementDescriptor*> MeshDescriptor;

  /**
  * Load a 3D model from a PLY file.
  *
  * @param meshElements			Vector that defines how to manage the ply element data in memory.
  * @param info					Info of the file to load.
  */
  inline bool OpenModel(Info& info, MeshDescriptor& meshElements)
  {
    PlyFile file;
    if (!file.OpenFileToRead(info.filename))
    {
      info.errInfo = NNP_UNABLE_TO_OPEN;
      return false;
    }
    bool last;
    std::string line;
    do
    {
      file.NextHeaderLine(line, last);
    } while (!last);
    bool fixEndian = false;
    if (checkEndianness() == 1)
    {
      if (info.bigEndian)
        fixEndian = true;
    }
    else
    {
      if (!info.bigEndian)
        fixEndian = true;
    }

    if (info.binary)
    {
      for (size_t i = 0; i < info.elemVec.size(); ++i)
      {
        PlyElement& pe = info.elemVec[i];
        size_t j = 0;
        for (; j < meshElements.size(); j++)
          if (ElemProcessing<0>(*meshElements[j], pe, file, fixEndian))
            break;
        if (j == meshElements.size())
          pe.SkipBinaryElementsInFile(file);
        //if (!TupleForEach(meshElements, pe, file, fixEndian, SizeT<0>()))
        //	pe.SkipBinaryElementsInFile(file);
      }
    }
    else
    {
      for (size_t i = 0; i < info.elemVec.size(); ++i)
      {
        PlyElement& pe = info.elemVec[i];
        size_t j = 0;
        for (; j < meshElements.size(); j++)
          if (ElemProcessing<1>(*meshElements[j], pe, file, false))
            break;
        if (j == meshElements.size())
          pe.SkipAsciiElementsInFile(file);
        //if (!TupleForEach(meshElements, pe, file, fixEndian, SizeT<1>()))
        //	pe.SkipAsciiElementsInFile(file);
      }
    }
    return true;
  }



  /**
  * Save a 3D model in a PLY file.
  *
  * @param filename				Path to the file to save.
  * @param meshElements			Vector that defines how to manage the ply element data in memory.
  * @param info					Info to saved in the PLY header.
  */
  inline bool SaveModel(std::string& filename, MeshDescriptor& meshElements, Info& info)
  {
    PlyFile file;
    if (!file.OpenFileToWrite(filename))
    {
      info.errInfo = NNP_UNABLE_TO_OPEN;
      return false;
    }
    for (size_t i = 0; i < info.elemVec.size(); ++i)
    {
      PlyElement& pe = info.elemVec[i];
      for (size_t j = 0; j < meshElements.size(); j++)
        if (ElemProcessing<4>(*meshElements[j], pe, file, false))
          break;
    }
    info.WriteHeader(file);
    bool fixEndian = false;
    if (checkEndianness() == 1)
    {
      if (info.bigEndian)
        fixEndian = true;
    }
    else
    {
      if (!info.bigEndian)
        fixEndian = true;
    }
	(void)fixEndian;

    if (info.binary)
    {
      for (size_t i = 0; i < info.elemVec.size(); ++i)
      {
        PlyElement& pe = info.elemVec[i];
        if (pe.validToWrite)
        {
          for (size_t j = 0; j < meshElements.size(); j++)
            if (ElemProcessing<2>(*meshElements[j], pe, file, false))
              break;
        }
      }
    }
    else
    {
      for (size_t i = 0; i < info.elemVec.size(); ++i)
      {
        PlyElement& pe = info.elemVec[i];
        if (pe.validToWrite)
        {
          for (size_t j = 0; j < meshElements.size(); j++)
            if (ElemProcessing<3>(*meshElements[j], pe, file, false))
              break;
        }
      }
    }
    file.Flush();
    return true;
  }



  /**
  * @cond HIDDEN_SYMBOLS
  */
  template < typename TupleType, size_t ActionType>
  inline bool TupleForEach(TupleType &tuple, PlyElement &elem, PlyFile& file, bool fixEndian, SizeT<ActionType> a)
  {
    return TupleForEach(tuple, elem, file, fixEndian, SizeT<std::tuple_size<TupleType>::value>(), a);
  }

  template < typename TupleType, size_t ActionType>
  inline bool TupleForEach(TupleType &tuple, PlyElement &elem, PlyFile& file, bool fixEndian, SizeT<0> t, SizeT<ActionType> a)
  {
    (void)tuple; (void)elem; (void)file; (void)fixEndian; (void)t; (void)a;
    return false;
  }

  template < typename TupleType, size_t N, size_t ActionType>
  inline bool TupleForEach(TupleType &tuple, PlyElement &elem, PlyFile& file, bool fixEndian, SizeT<N> t, SizeT<ActionType> a)
  {
    (void)t;
    typename std::tuple_element<N - 1, TupleType>::type &elemDescr = std::get<N - 1>(tuple);
    if ((elemDescr.elem != PlyElemEntity::NNP_UNKNOWN_ELEM && elemDescr.elem == elem.plyElem) ||
      (elemDescr.elem == PlyElemEntity::NNP_UNKNOWN_ELEM && elemDescr.name == elem.name))
    {
      if (ActionType == 0)
        elemDescr.ReadElemBinary(file, elem, fixEndian);
      else if (ActionType == 1)
        elemDescr.ReadElemAscii(file, elem);
      else if (ActionType == 2)
        elemDescr.WriteElemBinary(file, elem, fixEndian);
      else if (ActionType == 3)
        elemDescr.WriteElemAscii(file, elem);
      else if (ActionType == 4)
        elemDescr.CheckDescriptor(elem);
      return true;
    }
    return TupleForEach(tuple, elem, file, fixEndian, SizeT<N - 1>(), a);
  }
  /**
  * @endcond
  */

} // end namespace nanoply
#endif // NANOPLY_HPP
