/* ========================================================================= *
 *                                                                           *
 *                               OpenMesh                                    *
 *           Copyright (c) 2001-2015, RWTH-Aachen University                 *
 *           Department of Computer Graphics and Multimedia                  *
 *                          All rights reserved.                             *
 *                            www.openmesh.org                               *
 *                                                                           *
 *---------------------------------------------------------------------------*
 * This file is part of OpenMesh.                                            *
 *---------------------------------------------------------------------------*
 *                                                                           *
 * Redistribution and use in source and binary forms, with or without        *
 * modification, are permitted provided that the following conditions        *
 * are met:                                                                  *
 *                                                                           *
 * 1. Redistributions of source code must retain the above copyright notice, *
 *    this list of conditions and the following disclaimer.                  *
 *                                                                           *
 * 2. Redistributions in binary form must reproduce the above copyright      *
 *    notice, this list of conditions and the following disclaimer in the    *
 *    documentation and/or other materials provided with the distribution.   *
 *                                                                           *
 * 3. Neither the name of the copyright holder nor the names of its          *
 *    contributors may be used to endorse or promote products derived from   *
 *    this software without specific prior written permission.               *
 *                                                                           *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A           *
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,       *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR        *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING      *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS        *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.              *
 *                                                                           *
 * ========================================================================= */

/*===========================================================================*\
 *                                                                           *
 *   $Revision$                                                         *
 *   $Date$                   *
 *                                                                           *
 \*===========================================================================*/

#define LINE_LEN 4096

//== INCLUDES =================================================================

// OpenMesh
#include <OpenMesh/Core/System/config.h>
#include <OpenMesh/Core/System/omstream.hh>
#include <OpenMesh/Core/IO/reader/PLYReader.hh>
#include <OpenMesh/Core/IO/IOManager.hh>
#include <OpenMesh/Core/Utils/color_cast.hh>

//STL
#include <fstream>
#include <iostream>
#include <memory>

#ifndef WIN32
#endif

//=== NAMESPACES ==============================================================


namespace OpenMesh {
namespace IO {

//=============================================================================

//=== INSTANCIATE =============================================================


_PLYReader_ __PLYReaderInstance;
_PLYReader_& PLYReader() {
    return __PLYReaderInstance;
}

//=== IMPLEMENTATION ==========================================================


_PLYReader_::_PLYReader_() {
    IOManager().register_module(this);

    // Store sizes in byte of each property type
    scalar_size_[ValueTypeINT8] = 1;
    scalar_size_[ValueTypeUINT8] = 1;
    scalar_size_[ValueTypeINT16] = 2;
    scalar_size_[ValueTypeUINT16] = 2;
    scalar_size_[ValueTypeINT32] = 4;
    scalar_size_[ValueTypeUINT32] = 4;
    scalar_size_[ValueTypeFLOAT32] = 4;
    scalar_size_[ValueTypeFLOAT64] = 8;

    scalar_size_[ValueTypeCHAR] = 1;
    scalar_size_[ValueTypeUCHAR] = 1;
    scalar_size_[ValueTypeSHORT] = 2;
    scalar_size_[ValueTypeUSHORT] = 2;
    scalar_size_[ValueTypeINT] = 4;
    scalar_size_[ValueTypeUINT] = 4;
    scalar_size_[ValueTypeFLOAT] = 4;
    scalar_size_[ValueTypeDOUBLE] = 8;
}

//-----------------------------------------------------------------------------


bool _PLYReader_::read(const std::string& _filename, BaseImporter& _bi, Options& _opt) {

    std::fstream in(_filename.c_str(), (std::ios_base::binary | std::ios_base::in) );

    if (!in.is_open() || !in.good()) {
        omerr() << "[PLYReader] : cannot not open file " << _filename << std::endl;
        return false;
    }

    bool result = read(in, _bi, _opt);

    in.close();
    return result;
}

//-----------------------------------------------------------------------------


bool _PLYReader_::read(std::istream& _in, BaseImporter& _bi, Options& _opt) {

    if (!_in.good()) {
        omerr() << "[PLYReader] : cannot not use stream" << std::endl;
        return false;
    }

    // filter relevant options for reading
    bool swap = _opt.check(Options::Swap);

    userOptions_ = _opt;

    // build options to be returned
    _opt.clear();

    if (options_.vertex_has_normal() && userOptions_.vertex_has_normal()) {
        _opt += Options::VertexNormal;
    }
    if (options_.vertex_has_texcoord() && userOptions_.vertex_has_texcoord()) {
        _opt += Options::VertexTexCoord;
    }
    if (options_.vertex_has_color() && userOptions_.vertex_has_color()) {
        _opt += Options::VertexColor;
    }
    if (options_.face_has_color() && userOptions_.face_has_color()) {
        _opt += Options::FaceColor;
    }
    if (options_.is_binary()) {
        _opt += Options::Binary;
    }
    if (options_.color_is_float()) {
        _opt += Options::ColorFloat;
    }
    if (options_.check(Options::Custom) && userOptions_.check(Options::Custom)) {
        _opt += Options::Custom;
    }

    //    //force user-choice for the alpha value when reading binary
    //    if ( options_.is_binary() && userOptions_.color_has_alpha() )
    //      options_ += Options::ColorAlpha;

    return (options_.is_binary() ? read_binary(_in, _bi, swap, _opt) : read_ascii(_in, _bi, _opt));

}

template<typename T, typename Handle>
struct Handle2Prop;

template<typename T>
struct Handle2Prop<T,VertexHandle>
{
  typedef OpenMesh::VPropHandleT<T> PropT;
};

template<typename T>
struct Handle2Prop<T,FaceHandle>
{
  typedef OpenMesh::FPropHandleT<T> PropT;
};

//read and assign custom properties with the given type. Also creates property, if not exist
template<bool binary, typename T, typename Handle>
void _PLYReader_::readCreateCustomProperty(std::istream& _in, BaseImporter& _bi, Handle _h, const std::string& _propName, const _PLYReader_::ValueType _valueType, const _PLYReader_::ValueType _listType) const
{
  if (_listType == Unsupported) //no list type defined -> property is not a list
  {
    //get/add property
    typename Handle2Prop<T,Handle>::PropT prop;
    if (!_bi.kernel()->get_property_handle(prop,_propName))
    {
      _bi.kernel()->add_property(prop,_propName);
      _bi.kernel()->property(prop).set_persistent(true);
    }

    //read and assign
    T in;
    read(_valueType, _in, in, OpenMesh::GenProg::Bool2Type<binary>());
    _bi.kernel()->property(prop,_h) = in;
  }
  else
  {
    //get/add property
    typename Handle2Prop<std::vector<T>,Handle>::PropT prop;
    if (!_bi.kernel()->get_property_handle(prop,_propName))
    {
      _bi.kernel()->add_property(prop,_propName);
      _bi.kernel()->property(prop).set_persistent(true);
    }

    //init vector
    int numberOfValues;
    read(_listType, _in, numberOfValues, OpenMesh::GenProg::Bool2Type<binary>());
    std::vector<T> vec;
    vec.reserve(numberOfValues);
    //read and assign
    for (int i = 0; i < numberOfValues; ++i)
    {
      T in;
      read(_valueType, _in, in, OpenMesh::GenProg::Bool2Type<binary>());
      vec.push_back(in);
    }
    _bi.kernel()->property(prop,_h) = vec;
  }
}

template<bool binary, typename Handle>
void _PLYReader_::readCustomProperty(std::istream& _in, BaseImporter& _bi, Handle _h, const std::string& _propName, const _PLYReader_::ValueType _valueType, const _PLYReader_::ValueType _listIndexType) const
{
  switch (_valueType)
  {
  case ValueTypeINT8:
  case ValueTypeCHAR:
      readCreateCustomProperty<binary,signed char>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeUINT8:
  case ValueTypeUCHAR:
      readCreateCustomProperty<binary,unsigned char>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeINT16:
  case ValueTypeSHORT:
      readCreateCustomProperty<binary,short>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeUINT16:
  case ValueTypeUSHORT:
      readCreateCustomProperty<binary,unsigned short>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeINT32:
  case ValueTypeINT:
      readCreateCustomProperty<binary,int>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeUINT32:
  case ValueTypeUINT:
      readCreateCustomProperty<binary,unsigned int>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeFLOAT32:
  case ValueTypeFLOAT:
      readCreateCustomProperty<binary,float>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  case ValueTypeFLOAT64:
  case ValueTypeDOUBLE:
      readCreateCustomProperty<binary,double>(_in,_bi,_h,_propName,_valueType,_listIndexType);
      break;
  default:
      std::cerr << "unsupported type" << std::endl;
      break;
  }
}


//-----------------------------------------------------------------------------

bool _PLYReader_::read_ascii(std::istream& _in, BaseImporter& _bi, const Options& _opt) const {

    // Reparse the header
    if (!can_u_read(_in)) {
        omerr() << "[PLYReader] : Unable to parse header\n";
        return false;
    }

    unsigned int i, j, k, l, idx;
    unsigned int nV;
    OpenMesh::Vec3f v, n;
    std::string trash;
    OpenMesh::Vec2f t;
    OpenMesh::Vec4i c;
    float tmp;
    BaseImporter::VHandles vhandles;
    VertexHandle vh;

    _bi.reserve(vertexCount_, 3* vertexCount_ , faceCount_);

    if (vertexDimension_ != 3) {
        omerr() << "[PLYReader] : Only vertex dimension 3 is supported." << std::endl;
        return false;
    }

    const bool err_enabled = omerr().is_enabled();
    size_t complex_faces = 0;
    if (err_enabled)
      omerr().disable();

    // read vertices:
    for (i = 0; i < vertexCount_ && !_in.eof(); ++i) {
        vh = _bi.add_vertex();

        v[0] = 0.0;
        v[1] = 0.0;
        v[2] = 0.0;

        n[0] = 0.0;
        n[1] = 0.0;
        n[2] = 0.0;

        t[0] = 0.0;
        t[1] = 0.0;

        c[0] = 0;
        c[1] = 0;
        c[2] = 0;
        c[3] = 255;

        for (size_t propertyIndex = 0; propertyIndex < vertexProperties_.size(); ++propertyIndex) {
            switch (vertexProperties_[propertyIndex].property) {
            case XCOORD:
                _in >> v[0];
                break;
            case YCOORD:
                _in >> v[1];
                break;
            case ZCOORD:
                _in >> v[2];
                break;
            case XNORM:
                _in >> n[0];
                break;
            case YNORM:
                _in >> n[1];
                break;
            case ZNORM:
                _in >> n[2];
                break;
            case TEXX:
                _in >> t[0];
                break;
            case TEXY:
                _in >> t[1];
                break;
            case COLORRED:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    _in >> tmp;
                    c[0] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    _in >> c[0];
                break;
            case COLORGREEN:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    _in >> tmp;
                    c[1] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    _in >> c[1];
                break;
            case COLORBLUE:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    _in >> tmp;
                    c[2] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    _in >> c[2];
                break;
            case COLORALPHA:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    _in >> tmp;
                    c[3] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    _in >> c[3];
                break;
            case CUSTOM_PROP:
                if (_opt.check(Options::Custom))
                  readCustomProperty<false>(_in, _bi, vh, vertexProperties_[propertyIndex].name, vertexProperties_[propertyIndex].value, vertexProperties_[propertyIndex].listIndexType);
                else
                  _in >> trash;
                break;
            default:
                _in >> trash;
                break;
            }
        }

        _bi.set_point(vh, v);
        if (_opt.vertex_has_normal())
          _bi.set_normal(vh, n);
        if (_opt.vertex_has_texcoord())
          _bi.set_texcoord(vh, t);
        if (_opt.vertex_has_color())
          _bi.set_color(vh, Vec4uc(c));
    }

    // faces
    for (i = 0; i < faceCount_; ++i) {
      FaceHandle fh;
      for (size_t propertyIndex = 0; propertyIndex < faceProperties_.size(); ++propertyIndex) {
        PropertyInfo prop = faceProperties_[propertyIndex];
        switch (prop.property) {

        case VERTEX_INDICES:
          // nV = number of Vertices for current face
          _in >> nV;

          if (nV == 3) {
            vhandles.resize(3);
            _in >> j;
            _in >> k;
            _in >> l;

            vhandles[0] = VertexHandle(j);
            vhandles[1] = VertexHandle(k);
            vhandles[2] = VertexHandle(l);
          } else {
            vhandles.clear();
            for (j = 0; j < nV; ++j) {
              _in >> idx;
              vhandles.push_back(VertexHandle(idx));
            }
          }

          fh = _bi.add_face(vhandles);
          if (!fh.is_valid())
            ++complex_faces;
          break;

        case CUSTOM_PROP:
          if (_opt.check(Options::Custom) && fh.is_valid())
            readCustomProperty<false>(_in, _bi, fh, prop.name, prop.value, prop.listIndexType);
          else
            _in >> trash;
          break;

        default:
          _in >> trash;
          break;
        }
      }

    }

    if (err_enabled) 
      omerr().enable();

    if (complex_faces)
      omerr() << complex_faces << "The reader encountered invalid faces, that could not be added.\n";

    // File was successfully parsed.
    return true;
}

//-----------------------------------------------------------------------------

bool _PLYReader_::read_binary(std::istream& _in, BaseImporter& _bi, bool /*_swap*/, const Options& _opt) const {

    // Reparse the header
    if (!can_u_read(_in)) {
        omerr() << "[PLYReader] : Unable to parse header\n";
        return false;
    }

    OpenMesh::Vec3f        v, n;  // Vertex
    OpenMesh::Vec2f        t;  // TexCoords
    BaseImporter::VHandles vhandles;
    VertexHandle           vh;
    OpenMesh::Vec4i        c;  // Color
    float                  tmp;

    _bi.reserve(vertexCount_, 3* vertexCount_ , faceCount_);

    const bool err_enabled = omerr().is_enabled();
    size_t complex_faces = 0;
    if (err_enabled)
      omerr().disable();

    // read vertices:
    for (unsigned int i = 0; i < vertexCount_ && !_in.eof(); ++i) {
        vh = _bi.add_vertex();

        v[0] = 0.0;
        v[1] = 0.0;
        v[2] = 0.0;

        n[0] = 0.0;
        n[1] = 0.0;
        n[2] = 0.0;

        t[0] = 0.0;
        t[1] = 0.0;

        c[0] = 0;
        c[1] = 0;
        c[2] = 0;
        c[3] = 255;

        for (size_t propertyIndex = 0; propertyIndex < vertexProperties_.size(); ++propertyIndex) {
            switch (vertexProperties_[propertyIndex].property) {
            case XCOORD:
                readValue(vertexProperties_[propertyIndex].value, _in, v[0]);
                break;
            case YCOORD:
                readValue(vertexProperties_[propertyIndex].value, _in, v[1]);
                break;
            case ZCOORD:
                readValue(vertexProperties_[propertyIndex].value, _in, v[2]);
                break;
            case XNORM:
                readValue(vertexProperties_[propertyIndex].value, _in, n[0]);
                break;
            case YNORM:
                readValue(vertexProperties_[propertyIndex].value, _in, n[1]);
                break;
            case ZNORM:
                readValue(vertexProperties_[propertyIndex].value, _in, n[2]);
                break;
            case TEXX:
                readValue(vertexProperties_[propertyIndex].value, _in, t[0]);
                break;
            case TEXY:
                readValue(vertexProperties_[propertyIndex].value, _in, t[1]);
                break;
            case COLORRED:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    readValue(vertexProperties_[propertyIndex].value, _in, tmp);

                    c[0] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    readInteger(vertexProperties_[propertyIndex].value, _in, c[0]);

                break;
            case COLORGREEN:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    readValue(vertexProperties_[propertyIndex].value, _in, tmp);
                    c[1] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    readInteger(vertexProperties_[propertyIndex].value, _in, c[1]);

                break;
            case COLORBLUE:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    readValue(vertexProperties_[propertyIndex].value, _in, tmp);
                    c[2] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    readInteger(vertexProperties_[propertyIndex].value, _in, c[2]);

                break;
            case COLORALPHA:
                if (vertexProperties_[propertyIndex].value == ValueTypeFLOAT32 ||
                    vertexProperties_[propertyIndex].value == ValueTypeFLOAT) {
                    readValue(vertexProperties_[propertyIndex].value, _in, tmp);
                    c[3] = static_cast<OpenMesh::Vec4i::value_type> (tmp * 255.0f);
                } else
                    readInteger(vertexProperties_[propertyIndex].value, _in, c[3]);

                break;
            case CUSTOM_PROP:
              if (_opt.check(Options::Custom))
                readCustomProperty<true>(_in, _bi, vh, vertexProperties_[propertyIndex].name, vertexProperties_[propertyIndex].value, vertexProperties_[propertyIndex].listIndexType);
              else
                consume_input(_in, scalar_size_[vertexProperties_[propertyIndex].value]);
              break;
            default:
                // Read unsupported property
                consume_input(_in, scalar_size_[vertexProperties_[propertyIndex].value]);
                break;
            }

        }

        _bi.set_point(vh,v);
        if (_opt.vertex_has_normal())
          _bi.set_normal(vh, n);
        if (_opt.vertex_has_texcoord())
          _bi.set_texcoord(vh, t);
        if (_opt.vertex_has_color())
          _bi.set_color(vh, Vec4uc(c));
    }

    for (unsigned i = 0; i < faceCount_; ++i) {
      FaceHandle fh;
      for (size_t propertyIndex = 0; propertyIndex < faceProperties_.size(); ++propertyIndex)
      {
        PropertyInfo prop = faceProperties_[propertyIndex];
        switch (prop.property) {

        case VERTEX_INDICES:
          // nV = number of Vertices for current face
          unsigned int nV;
          readInteger(prop.listIndexType, _in, nV);

          if (nV == 3) {
            vhandles.resize(3);
            unsigned int j,k,l;
            readInteger(prop.value, _in, j);
            readInteger(prop.value, _in, k);
            readInteger(prop.value, _in, l);

            vhandles[0] = VertexHandle(j);
            vhandles[1] = VertexHandle(k);
            vhandles[2] = VertexHandle(l);
          } else {
            vhandles.clear();
            for (unsigned j = 0; j < nV; ++j) {
              unsigned int idx;
              readInteger(prop.value, _in, idx);
              vhandles.push_back(VertexHandle(idx));
            }
          }

          fh = _bi.add_face(vhandles);
          if (!fh.is_valid())
            ++complex_faces;
          break;

        case CUSTOM_PROP:
          if (_opt.check(Options::Custom) && fh.is_valid())
            readCustomProperty<true>(_in, _bi, fh, prop.name, prop.value, prop.listIndexType);
          else
            consume_input(_in, scalar_size_[faceProperties_[propertyIndex].value]);
          break;

        default:
          consume_input(_in, scalar_size_[faceProperties_[propertyIndex].value]);
          break;
        }
      }
    }

    if (err_enabled) 
      omerr().enable();

   if (complex_faces)
      omerr() << complex_faces << "The reader encountered invalid faces, that could not be added.\n";


    return true;
}


//-----------------------------------------------------------------------------


void _PLYReader_::readValue(ValueType _type, std::istream& _in, float& _value) const {

    switch (_type) {
    case ValueTypeFLOAT32:
    case ValueTypeFLOAT:
        float32_t tmp;
        restore(_in, tmp, options_.check(Options::MSB));
        _value = tmp;
        break;
    default:
        _value = 0.0;
        std::cerr << "unsupported conversion type to float: " << _type << std::endl;
        break;
    }
}


//-----------------------------------------------------------------------------


void _PLYReader_::readValue(ValueType _type, std::istream& _in, double& _value) const {

    switch (_type) {

        case ValueTypeFLOAT64:

        case ValueTypeDOUBLE:

            float64_t tmp;
            restore(_in, tmp, options_.check(Options::MSB));
            _value = tmp;

            break;

    default:

        _value = 0.0;
        std::cerr << "unsupported conversion type to double: " << _type << std::endl;

        break;
    }
}


//-----------------------------------------------------------------------------

void _PLYReader_::readValue(ValueType _type, std::istream& _in, unsigned char& _value) const{
  unsigned int tmp;
  readValue(_type,_in,tmp);
  _value = tmp;
}

//-----------------------------------------------------------------------------

void _PLYReader_::readValue(ValueType _type, std::istream& _in, unsigned short& _value) const{
  unsigned int tmp;
  readValue(_type,_in,tmp);
  _value = tmp;
}

//-----------------------------------------------------------------------------

void _PLYReader_::readValue(ValueType _type, std::istream& _in, signed char& _value) const{
  int tmp;
  readValue(_type,_in,tmp);
  _value = tmp;
}

//-----------------------------------------------------------------------------

void _PLYReader_::readValue(ValueType _type, std::istream& _in, short& _value) const{
  int tmp;
  readValue(_type,_in,tmp);
  _value = tmp;
}


//-----------------------------------------------------------------------------
void _PLYReader_::readValue(ValueType _type, std::istream& _in, unsigned int& _value) const {

    uint32_t tmp_uint32_t;
    uint16_t tmp_uint16_t;
    uint8_t tmp_uchar;

    switch (_type) {

        case ValueTypeUINT:

        case ValueTypeUINT32:

            restore(_in, tmp_uint32_t, options_.check(Options::MSB));
            _value = tmp_uint32_t;

        break;

        case ValueTypeUSHORT:

        case ValueTypeUINT16:

            restore(_in, tmp_uint16_t, options_.check(Options::MSB));
            _value = tmp_uint16_t;

            break;

        case ValueTypeUCHAR:

        case ValueTypeUINT8:

            restore(_in, tmp_uchar, options_.check(Options::MSB));
            _value = tmp_uchar;

            break;

        default:

            _value = 0;
            std::cerr << "unsupported conversion type to unsigned int: " << _type << std::endl;

            break;
    }
}


//-----------------------------------------------------------------------------


void _PLYReader_::readValue(ValueType _type, std::istream& _in, int& _value) const {

    int32_t tmp_int32_t;
    int16_t tmp_int16_t;
    int8_t tmp_char;

    switch (_type) {

        case ValueTypeINT:

        case ValueTypeINT32:

            restore(_in, tmp_int32_t, options_.check(Options::MSB));
            _value = tmp_int32_t;

            break;

        case ValueTypeSHORT:

        case ValueTypeINT16:

            restore(_in, tmp_int16_t, options_.check(Options::MSB));
            _value = tmp_int16_t;

            break;

        case ValueTypeCHAR:

        case ValueTypeINT8:

            restore(_in, tmp_char, options_.check(Options::MSB));
            _value = tmp_char;

            break;

        default:

            _value = 0;
            std::cerr << "unsupported conversion type to int: " << _type << std::endl;

            break;
    }
}


//-----------------------------------------------------------------------------


void _PLYReader_::readInteger(ValueType _type, std::istream& _in, int& _value) const {

    int32_t tmp_int32_t;
    uint32_t tmp_uint32_t;
    int8_t tmp_char;
    uint8_t tmp_uchar;

    switch (_type) {

        case ValueTypeINT:

        case ValueTypeINT32:

            restore(_in, tmp_int32_t, options_.check(Options::MSB));
            _value = tmp_int32_t;

            break;

        case ValueTypeUINT:

        case ValueTypeUINT32:

            restore(_in, tmp_uint32_t, options_.check(Options::MSB));
            _value = tmp_uint32_t;

            break;

        case ValueTypeCHAR:

        case ValueTypeINT8:

            restore(_in, tmp_char, options_.check(Options::MSB));
            _value = tmp_char;

            break;

        case ValueTypeUCHAR:

        case ValueTypeUINT8:

            restore(_in, tmp_uchar, options_.check(Options::MSB));
            _value = tmp_uchar;

            break;

        default:

            _value = 0;
            std::cerr << "unsupported conversion type to int: " << _type << std::endl;

            break;
    }
}


//-----------------------------------------------------------------------------


void _PLYReader_::readInteger(ValueType _type, std::istream& _in, unsigned int& _value) const {

    int32_t tmp_int32_t;
    uint32_t tmp_uint32_t;
    int8_t tmp_char;
    uint8_t tmp_uchar;

    switch (_type) {

        case ValueTypeUINT:

        case ValueTypeUINT32:

            restore(_in, tmp_uint32_t, options_.check(Options::MSB));
            _value = tmp_uint32_t;

            break;

        case ValueTypeINT:

        case ValueTypeINT32:

            restore(_in, tmp_int32_t, options_.check(Options::MSB));
            _value = tmp_int32_t;

            break;

        case ValueTypeUCHAR:

        case ValueTypeUINT8:

            restore(_in, tmp_uchar, options_.check(Options::MSB));
            _value = tmp_uchar;

            break;

        case ValueTypeCHAR:

        case ValueTypeINT8:

            restore(_in, tmp_char, options_.check(Options::MSB));
            _value = tmp_char;

            break;

        default:

            _value = 0;
            std::cerr << "unsupported conversion type to unsigned int: " << _type << std::endl;

            break;
    }
}


//------------------------------------------------------------------------------


bool _PLYReader_::can_u_read(const std::string& _filename) const {

    // !!! Assuming BaseReader::can_u_parse( std::string& )
    // does not call BaseReader::read_magic()!!!

    if (BaseReader::can_u_read(_filename)) {
        std::ifstream ifs(_filename.c_str());
        if (ifs.is_open() && can_u_read(ifs)) {
            ifs.close();
            return true;
        }
    }
    return false;
}



//-----------------------------------------------------------------------------

std::string get_property_name(std::string _string1, std::string _string2) {

    if (_string1 == "float32" || _string1 == "float64" || _string1 == "float" || _string1 == "double" ||
        _string1 == "int8" || _string1 == "uint8" || _string1 == "char" || _string1 == "uchar" ||
        _string1 == "int32" || _string1 == "uint32" || _string1 == "int" || _string1 == "uint" ||
        _string1 == "int16" || _string1 == "uint16" || _string1 == "short" || _string1 == "ushort")
        return _string2;

    if (_string2 == "float32" || _string2 == "float64" || _string2 == "float" || _string2 == "double" ||
        _string2 == "int8" || _string2 == "uint8" || _string2 == "char" || _string2 == "uchar" ||
        _string2 == "int32" || _string2 == "uint32" || _string2 == "int" || _string2 == "uint" ||
        _string2 == "int16" || _string2 == "uint16" || _string2 == "short" || _string2 == "ushort")
        return _string1;


    std::cerr << "Unsupported entry type" << std::endl;
    return "Unsupported";
}

//-----------------------------------------------------------------------------

_PLYReader_::ValueType get_property_type(std::string _string1, std::string _string2) {

    if (_string1 == "float32" || _string2 == "float32")

        return _PLYReader_::ValueTypeFLOAT32;

    else if (_string1 == "float64" || _string2 == "float64")

        return _PLYReader_::ValueTypeFLOAT64;

    else if (_string1 == "float" || _string2 == "float")

        return _PLYReader_::ValueTypeFLOAT;

    else if (_string1 == "double" || _string2 == "double")

        return _PLYReader_::ValueTypeDOUBLE;

    else if (_string1 == "int8" || _string2 == "int8")

        return _PLYReader_::ValueTypeINT8;

    else if (_string1 == "uint8" || _string2 == "uint8")

        return _PLYReader_::ValueTypeUINT8;

    else if (_string1 == "char" || _string2 == "char")

        return _PLYReader_::ValueTypeCHAR;

    else if (_string1 == "uchar" || _string2 == "uchar")

        return _PLYReader_::ValueTypeUCHAR;

    else if (_string1 == "int32" || _string2 == "int32")

        return _PLYReader_::ValueTypeINT32;

    else if (_string1 == "uint32" || _string2 == "uint32")

        return _PLYReader_::ValueTypeUINT32;

    else if (_string1 == "int" || _string2 == "int")

        return _PLYReader_::ValueTypeINT;

    else if (_string1 == "uint" || _string2 == "uint")

        return _PLYReader_::ValueTypeUINT;

    else if (_string1 == "int16" || _string2 == "int16")

        return _PLYReader_::ValueTypeINT16;

    else if (_string1 == "uint16" || _string2 == "uint16")

        return _PLYReader_::ValueTypeUINT16;

    else if (_string1 == "short" || _string2 == "short")

        return _PLYReader_::ValueTypeSHORT;

    else if (_string1 == "ushort" || _string2 == "ushort")

        return _PLYReader_::ValueTypeUSHORT;

    return _PLYReader_::Unsupported;
}


//-----------------------------------------------------------------------------

bool _PLYReader_::can_u_read(std::istream& _is) const {

    // Clear per file options
    options_.cleanup();

    // clear property maps, will be recreated
    vertexProperties_.clear();
    faceProperties_.clear();

    // read 1st line
    std::string line;
    std::getline(_is, line);
    trim(line);

    // Handle '\r\n' newlines 
    const int s = line.size(); 
    if( s > 0 && line[s - 1] == '\r') line.resize(s - 1); 

    //Check if this file is really a ply format
    if (line != "PLY" && line != "ply")
        return false;

    vertexCount_ = 0;
    faceCount_ = 0;
    vertexDimension_ = 0;

    std::string keyword;
    std::string fileType;
    std::string elementName = "";
    std::string propertyName;
    std::string listIndexType;
    std::string listEntryType;
    float version;

    _is >> keyword;
    _is >> fileType;
    _is >> version;

    if (_is.bad()) {
        omerr() << "Defect PLY header detected" << std::endl;
        return false;
    }

    if (fileType == "ascii") {
        options_ -= Options::Binary;
    } else if (fileType == "binary_little_endian") {
        options_ += Options::Binary;
        options_ += Options::LSB;
        //if (Endian::local() == Endian::MSB)

        //  options_ += Options::Swap;
    } else if (fileType == "binary_big_endian") {
        options_ += Options::Binary;
        options_ += Options::MSB;
        //if (Endian::local() == Endian::LSB)

        //  options_ += Options::Swap;
    } else {
        omerr() << "Unsupported PLY format: " << fileType << std::endl;
        return false;
    }

    std::streamoff streamPos = _is.tellg();
    _is >> keyword;
    while (keyword != "end_header") {

        if (keyword == "comment") {
            std::getline(_is, line);
        } else if (keyword == "element") {
            _is >> elementName;
            if (elementName == "vertex") {
                _is >> vertexCount_;
            } else if (elementName == "face") {
                _is >> faceCount_;
            } else {
                omerr() << "PLY header unsupported element type: " << elementName << std::endl;
            }
        } else if (keyword == "property") {
            std::string tmp1;
            std::string tmp2;

            // Read first keyword, as it might be a list
            _is >> tmp1;

            if (tmp1 == "list") {
              _is >> listIndexType;
              _is >> listEntryType;
              _is >> propertyName;

              ValueType indexType = Unsupported;
              ValueType entryType = Unsupported;

              if (listIndexType == "uint8") {
                indexType = ValueTypeUINT8;
              } else if (listIndexType == "uchar") {
                indexType = ValueTypeUCHAR;
              } else if (listIndexType == "int") {
                indexType = ValueTypeINT;
              } else {
                omerr() << "Unsupported Index type for property list: " << listIndexType << std::endl;
                continue;
              }

              entryType = get_property_type(listEntryType, listEntryType);

              if (entryType == Unsupported) {
                omerr() << "Unsupported Entry type for property list: " << listEntryType << std::endl;
              }

                PropertyInfo property(CUSTOM_PROP, entryType, propertyName);
                property.listIndexType = indexType;

                // just 2 elements supported by now
                if (elementName == "vertex")
                {
                  vertexProperties_.push_back(property);
                }
                else if (elementName == "face")
                {
                  // special case for vertex indices
                  if (propertyName == "vertex_index" || propertyName == "vertex_indices")
                  {
                    property.property = VERTEX_INDICES;
                    if (!faceProperties_.empty())
                    {
                      omerr() << "Custom face Properties defined, before 'vertex_indices' property was defined. They will be skipped" << std::endl;
                      faceProperties_.clear();
                    }
                  }
                  faceProperties_.push_back(property);

                }
                else
                  omerr() << "property " << propertyName << " belongs to unsupported element " << elementName << std::endl;

            } else {
              // as this is not a list property, read second value of property
              _is >> tmp2;


              // Extract name and type of property
              // As the order seems to be different in some files, autodetect it.
              ValueType valueType = get_property_type(tmp1, tmp2);
              propertyName = get_property_name(tmp1, tmp2);

              PropertyInfo entry;

              //special treatment for some vertex properties.
              if (elementName == "vertex") {
                if (propertyName == "x") {
                  entry = PropertyInfo(XCOORD, valueType);
                  vertexDimension_++;
                } else if (propertyName == "y") {
                  entry = PropertyInfo(YCOORD, valueType);
                  vertexDimension_++;
                } else if (propertyName == "z") {
                  entry = PropertyInfo(ZCOORD, valueType);
                  vertexDimension_++;
                } else if (propertyName == "nx") {
                  entry = PropertyInfo(XNORM, valueType);
                  options_ += Options::VertexNormal;
                } else if (propertyName == "ny") {
                  entry = PropertyInfo(YNORM, valueType);
                  options_ += Options::VertexNormal;
                } else if (propertyName == "nz") {
                  entry = PropertyInfo(ZNORM, valueType);
                  options_ += Options::VertexNormal;
                } else if (propertyName == "u" || propertyName == "s") {
                  entry = PropertyInfo(TEXX, valueType);
                  options_ += Options::VertexTexCoord;
                } else if (propertyName == "v" || propertyName == "t") {
                  entry = PropertyInfo(TEXY, valueType);
                  options_ += Options::VertexTexCoord;
                } else if (propertyName == "red") {
                  entry = PropertyInfo(COLORRED, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "green") {
                  entry = PropertyInfo(COLORGREEN, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "blue") {
                  entry = PropertyInfo(COLORBLUE, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "diffuse_red") {
                  entry = PropertyInfo(COLORRED, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "diffuse_green") {
                  entry = PropertyInfo(COLORGREEN, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "diffuse_blue") {
                  entry = PropertyInfo(COLORBLUE, valueType);
                  options_ += Options::VertexColor;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                } else if (propertyName == "alpha") {
                  entry = PropertyInfo(COLORALPHA, valueType);
                  options_ += Options::VertexColor;
                  options_ += Options::ColorAlpha;
                  if (valueType == ValueTypeFLOAT || valueType == ValueTypeFLOAT32)
                    options_ += Options::ColorFloat;
                }
              }

              //not a special property, load as custom
              if (entry.value == Unsupported){
                Property prop =  CUSTOM_PROP;
                options_ += Options::Custom;
                entry  = PropertyInfo(prop, valueType, propertyName);
              }

              if (entry.property != UNSUPPORTED)
              {
                if (elementName == "vertex")
                  vertexProperties_.push_back(entry);
                else if (elementName == "face")
                  faceProperties_.push_back(entry);
                else
                  omerr() << "Properties not supported in element " << elementName << std::endl;
              }

            }

        } else {
            omlog() << "Unsupported keyword : " << keyword << std::endl;
        }

        streamPos = _is.tellg();
        _is >> keyword;
        if (_is.bad()) {
            omerr() << "Error while reading PLY file header" << std::endl;
            return false;
        }
    }

    // As the binary data is directy after the end_header keyword
    // and the stream removes too many bytes, seek back to the right position
    if (options_.is_binary()) {
        _is.seekg(streamPos);

        char c1 = 0;
        char c2 = 0;
        _is.get(c1);
        _is.get(c2);

        if (c1 == 0x0D && c2 == 0x0A) {
            _is.seekg(streamPos + 14);
        }
        else {
            _is.seekg(streamPos + 12);
        }
    }

    return true;
}

//=============================================================================
} // namespace IO
} // namespace OpenMesh
//=============================================================================
