//-----------------------------------------------------------------------------
// Product:     OpenCTM tools
// File:        ply.cpp
// Description: Implementation of the PLY file format importer/exporter.
//-----------------------------------------------------------------------------
// Copyright (c) 2009-2010 Marcus Geelnard
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not
//     be misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//     distribution.
//-----------------------------------------------------------------------------

#include <iostream>
#include <stdexcept>
#include <string>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
#include <clocale>
#include <rply.h>
#include "ply.h"
#include "common.h"

using namespace std;

typedef struct {
  Mesh * mMesh;
  long mFaceIdx;
  long mVertexIdx;
  long mNormalIdx;
  long mTexCoordIdx;
  long mColorIdx;
} PLYReaderState;


static int PLYFaceCallback(p_ply_argument argument)
{
  PLYReaderState * state;
  long dummy, length, valueIndex;
  ply_get_argument_user_data(argument, (void **) &state, &dummy);
  double value = ply_get_argument_value(argument);
  ply_get_argument_property(argument, NULL, &length, &valueIndex);
  if((valueIndex >= 0) && (valueIndex <= 2))
    state->mMesh->mIndices[state->mFaceIdx * 3 + valueIndex] = int(value);
  if(valueIndex == 2)
    ++ state->mFaceIdx;
  return 1;
}

static int PLYVertexCallback(p_ply_argument argument)
{
  PLYReaderState * state;
  long index;
  ply_get_argument_user_data(argument, (void **) &state, &index);
  double value = ply_get_argument_value(argument);
  switch(index)
  {
    case 0:
      state->mMesh->mVertices[state->mVertexIdx].x = float(value);
      break;
    case 1:
      state->mMesh->mVertices[state->mVertexIdx].y = float(value);
      break;
    case 2:
      state->mMesh->mVertices[state->mVertexIdx].z = float(value);
      ++ state->mVertexIdx;
      break;
  }
  return 1;
}

static int PLYNormalCallback(p_ply_argument argument)
{
  PLYReaderState * state;
  long index;
  ply_get_argument_user_data(argument, (void **) &state, &index);
  double value = ply_get_argument_value(argument);
  switch(index)
  {
    case 0:
      state->mMesh->mNormals[state->mNormalIdx].x = float(value);
      break;
    case 1:
      state->mMesh->mNormals[state->mNormalIdx].y = float(value);
      break;
    case 2:
      state->mMesh->mNormals[state->mNormalIdx].z = float(value);
      ++ state->mNormalIdx;
      break;
  }
  return 1;
}

static int PLYTexCoordCallback(p_ply_argument argument)
{
  PLYReaderState * state;
  long index;
  ply_get_argument_user_data(argument, (void **) &state, &index);
  double value = ply_get_argument_value(argument);
  switch(index)
  {
    case 0:
      state->mMesh->mTexCoords[state->mTexCoordIdx].u = float(value);
      break;
    case 1:
      state->mMesh->mTexCoords[state->mTexCoordIdx].v = float(value);
      ++ state->mTexCoordIdx;
      break;
  }
  return 1;
}

static int PLYColorCallback(p_ply_argument argument)
{
  PLYReaderState * state;
  long index;
  ply_get_argument_user_data(argument, (void **) &state, &index);
  double value = ply_get_argument_value(argument);
  switch(index)
  {
    case 0:
      state->mMesh->mColors[state->mColorIdx].x = float(value) / 255.0f;
      break;
    case 1:
      state->mMesh->mColors[state->mColorIdx].y = float(value) / 255.0f;
      break;
    case 2:
      state->mMesh->mColors[state->mColorIdx].z = float(value) / 255.0f;
      ++ state->mColorIdx;
      break;
  }
  return 1;
}

/// Import a PLY file from a file.
void Import_PLY(const char * aFileName, Mesh * aMesh)
{
  // Start by ensuring that we use proper locale settings for the file format
  setlocale(LC_NUMERIC, "C");

  // Clear the mesh
  aMesh->Clear();

  // Initialize the state
  PLYReaderState state;
  state.mMesh = aMesh;
  state.mFaceIdx = 0;
  state.mVertexIdx = 0;
  state.mNormalIdx = 0;
  state.mTexCoordIdx = 0;
  state.mColorIdx = 0;

  // Open the PLY file
  p_ply ply = ply_open(aFileName, NULL);
  if(!ply)
    throw runtime_error("Unable to open PLY file.");
  if(!ply_read_header(ply))
    throw runtime_error("Invalid PLY file.");

  // Get the file comment (if any)
  bool firstComment = true;
  const char * comment = ply_get_next_comment(ply, NULL);
  while(comment)
  {
    if(firstComment)
      aMesh->mComment = string(comment);
    else
      aMesh->mComment += string(" ") + string(comment);
    firstComment = false;
    comment = ply_get_next_comment(ply, comment);
  }

  // Set face callback
  long faceCount = ply_set_read_cb(ply, "face", "vertex_indices", PLYFaceCallback, &state, 0);
  if(faceCount == 0)
    faceCount = ply_set_read_cb(ply, "face", "vertex_index", PLYFaceCallback, &state, 0);

  // Set vertex callback
  long vertexCount = ply_set_read_cb(ply, "vertex", "x", PLYVertexCallback, &state, 0);
  ply_set_read_cb(ply, "vertex", "y", PLYVertexCallback, &state, 1);
  ply_set_read_cb(ply, "vertex", "z", PLYVertexCallback, &state, 2);

  // Set normal callback
  long normalCount = ply_set_read_cb(ply, "vertex", "nx", PLYNormalCallback, &state, 0);
  ply_set_read_cb(ply, "vertex", "ny", PLYNormalCallback, &state, 1);
  ply_set_read_cb(ply, "vertex", "nz", PLYNormalCallback, &state, 2);

  // Set tex coord callback
  long texCoordCount = ply_set_read_cb(ply, "vertex", "s", PLYTexCoordCallback, &state, 0);
  ply_set_read_cb(ply, "vertex", "t", PLYTexCoordCallback, &state, 1);

  // Set color callback
  long colorCount = ply_set_read_cb(ply, "vertex", "red", PLYColorCallback, &state, 0);
  ply_set_read_cb(ply, "vertex", "green", PLYColorCallback, &state, 1);
  ply_set_read_cb(ply, "vertex", "blue", PLYColorCallback, &state, 2);

  // Sanity check
  if((faceCount < 1) || (vertexCount < 1))
    throw runtime_error("Empty PLY mesh - invalid file format?");

  // Prepare the mesh
  aMesh->mIndices.resize(faceCount * 3);
  aMesh->mVertices.resize(vertexCount);
  aMesh->mNormals.resize(normalCount);
  aMesh->mTexCoords.resize(texCoordCount);
  aMesh->mColors.resize(colorCount);

  // Read the PLY file
  if(!ply_read(ply))
    throw runtime_error("Unable to load PLY file.");

  // Close the PLY file
  ply_close(ply);
}

/// Export a PLY file to a file.
void Export_PLY(const char * aFileName, Mesh * aMesh, Options &aOptions)
{
  // Start by ensuring that we use proper locale settings for the file format
  setlocale(LC_NUMERIC, "C");

  // What should we export?
  bool exportTexCoords = aMesh->HasTexCoords() && !aOptions.mNoTexCoords;
  bool exportNormals = aMesh->HasNormals() && !aOptions.mNoNormals;
  bool exportColors = aMesh->HasColors() && !aOptions.mNoColors;

  // Open the output file
  ofstream f(aFileName, ios_base::out | ios_base::binary);
  if(f.fail())
    throw runtime_error("Could not open output file.");

  // Set floating point precision
  f << setprecision(8);

  // Write header
  f << "ply" << endl;
  f << "format ascii 1.0" << endl;
  if(aMesh->mComment.size() > 0)
  {
    stringstream sstr(aMesh->mComment);
    sstr.seekg(0);
    while(!sstr.eof())
    {
      string line;
      getline(sstr, line);
      line = TrimString(line);
      if(line.size() > 0)
        f << "comment " << line << endl;
    }
  }
  f << "element vertex " << aMesh->mVertices.size() << endl;
  f << "property float x" << endl;
  f << "property float y" << endl;
  f << "property float z" << endl;
  if(exportTexCoords)
  {
    f << "property float s" << endl;
    f << "property float t" << endl;
  }
  if(exportNormals)
  {
    f << "property float nx" << endl;
    f << "property float ny" << endl;
    f << "property float nz" << endl;
  }
  if(exportColors)
  {
    f << "property uchar red" << endl;
    f << "property uchar green" << endl;
    f << "property uchar blue" << endl;
  }
  f << "element face " << aMesh->mIndices.size() / 3 << endl;
  f << "property list uchar int vertex_indices" << endl;
  f << "end_header" << endl;

  // Write vertices
  for(unsigned int i = 0; i < aMesh->mVertices.size(); ++ i)
  {
    f << aMesh->mVertices[i].x << " " <<
         aMesh->mVertices[i].y << " " <<
         aMesh->mVertices[i].z;
    if(exportTexCoords)
      f << " " << aMesh->mTexCoords[i].u << " " <<
                  aMesh->mTexCoords[i].v;
    if(exportNormals)
      f << " " << aMesh->mNormals[i].x << " " <<
                  aMesh->mNormals[i].y << " " <<
                  aMesh->mNormals[i].z;
    if(exportColors)
      f << " " << int(floorf(255.0f * aMesh->mColors[i].x + 0.5f)) << " " <<
                  int(floorf(255.0f * aMesh->mColors[i].y + 0.5f)) << " " <<
                  int(floorf(255.0f * aMesh->mColors[i].z + 0.5f));
    f << endl;
  }

  // Write faces
  for(unsigned int i = 0; i < aMesh->mIndices.size() / 3; ++ i)
    f << "3 " << aMesh->mIndices[i * 3] << " " <<
                 aMesh->mIndices[i * 3 + 1] << " " <<
                 aMesh->mIndices[i * 3 + 2] << endl;

  // Close the output file
  f.close();
}
