//============================================================================
//
// This file is part of the 'reorientmeshfaces' project.
//
// This software is covered by the following BSD license, except for portions
// derived from other works which are covered by their respective licenses.
// For full licensing information including reproduction of these external
// licenses, see the file LICENSE.txt provided in the documentation.
//
// Copyright (c) 2015, Evangelos Kalogerakis, UMass Amherst
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * 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.
//
// * Neither the name of the copyright holders nor the names of contributors
// to this software 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 HOLDERS 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.
//
//============================================================================

#ifndef __DEF_MESH_FEATURE_EXPORTER_H
#define __DEF_MESH_FEATURE_EXPORTER_H

#include <KDTree3.hpp> // Siddharta's code

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/johnson_all_pairs_shortest.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/numeric/conversion/bounds.hpp>
#include <boost/limits.hpp>

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include <limits>
#include <string>
#include <map>

#ifdef _MSC_VER
#include <direct.h>
#else
#include <dir.h>
#endif

#if defined(_MSC_VER) && defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
#define new DEBUG_NEW
#endif

#include "TriMesh.h"
#include "TriMesh_algo.h"
#define NOENGINE 1
#ifndef NOENGINE
#include "engine.h"
#endif

using namespace std;
using namespace Thea;

/////////////////////////// DEFINITIONS ////////////////////////
#define USE_MAP_ESTIMATES     1
#define USE_DETERMINISTIC_SAMPLING 1

#define DEBUG_COORD_SYSTEM		0
#define DEBUG_BOUNDARIES			0
#define DEBUG_TRAINING			  0
#if defined(_DEBUG)
#define DEBUG_PFE               1
#define POINT_SAMPLES           90
#else
#define DEBUG_PFE               0
#define POINT_SAMPLES           100000
#endif

#define KMEANS_ITERATIONS								100
#if defined(_DEBUG)
#define	KMEANS_REPLICATES								10
#else
#define	KMEANS_REPLICATES								3000
#endif
#define MIN_DOMAIN_HIDDEN_NODE_SIZE			2
#define MAX_DOMAIN_HIDDEN_NODE_SIZE			15
#define MIN_CONFIG_HIDDEN_NODE_SIZE			0
#define MAX_CONFIG_HIDDEN_NODE_SIZE			5   // additional styles beyond 2
#define MAX_EM_ITERATIONS								30
#define EM_INITIALIZATION_RANDOMNESS		0.01		// no more than 0.5
#define VERY_LARGE_MESHES_MODE				  1
#define NU														  0.01
//#define REJECT_SAMPLE_PROB_THRESHOLD		.001



#define MESHFILELIST			"meshlist.txt"
#define LABELSFILENAME		"labels.txt"
#define PARTDIR				"parts/"

#undef  max  

#ifndef M_PI
#define M_PI       3.14159265358979323846
#endif
#ifndef M_PI_2
#define M_PI_2     1.57079632679489661923
#endif

// General definitions
#define PRINT_EVERY_N								2000
#define SUPERMAXEPSILOND					 1e+30
#define SUPEREPSILOND							 1e-30
#define EPSILOND									 1e-6
#define EPSILON										 1e-6f
#define T_0												 1e-4
#define FILE_STRING_SIZE						4096


////////////////////////// CLASSES ///////////////////////////////

typedef struct 
{
  bool add_faces;
  bool subdivide_mesh;
  bool do_nothing;
  char outputExtension[4];

	char inputPath[FILE_STRING_SIZE];
  char outputPath[FILE_STRING_SIZE];
	unsigned int numPointSamples;

	char currentObjFilename[FILE_STRING_SIZE];
} MeshFeatureExporterOptions;


template<class T> 
struct IndexCmp 
{
	T V;
	IndexCmp(T _V) : V(_V) {}
	bool operator()(const size_t a, const size_t b) const { 
		return V[a] < V[b]; 
	}
};



////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/////////// HELPER FUNCTIONS //////////////

//CopyFile is a simple function that copies a file from arg1 to arg2
inline bool copy_file(const char* initial_filename, const char* output_filename)
{
	char full_output_filename[ FILE_STRING_SIZE ];
	strcpy( full_output_filename, output_filename );
	if ( output_filename[ strlen(output_filename)-1 ] == '\\' ||
		   output_filename[ strlen(output_filename)-1 ] == '/' )
	{
		size_t posafterslash = 0;
		for (size_t l = strlen( initial_filename )-1; l>=0; l--)
		{
			if ( initial_filename[l] == '\\' || initial_filename[l] == '/' )
			{
				posafterslash = l+1;
				break;
			}
		}
		string initial_abs_filename = "";
		for (size_t l = posafterslash; l < strlen( initial_filename ); l++)
		{
			initial_abs_filename += initial_filename[l];
		}
		strcat( full_output_filename, initial_abs_filename.c_str() );
	}

	std::cout << "Copying file " << initial_filename << " to " << full_output_filename << "...";
	ifstream initial_file(initial_filename, ios::in|ios::binary);
	ofstream output_file(full_output_filename, ios::out|ios::binary);
	initial_file.seekg(0, ios::end);
	long file_size = initial_file.tellg();

	if(initial_file.is_open() && output_file.is_open())
	{
		char* buffer = new char[file_size];
		initial_file.seekg(0, ios::beg);
		initial_file.read(buffer, file_size);
		output_file.write(buffer, file_size);
		delete[] buffer;
	}
	else if(!output_file.is_open())
	{
		std::cout<<"couldn't open output file " << full_output_filename <<" for writing!" << std::endl;
		initial_file.close();
		return false;
	}
	else if(!initial_file.is_open())
	{
		output_file.close();
		std::cout<<"couldn't open source file" << initial_filename <<" for reading!" << std::endl;
		return false;
	}		
	std::cout << "Done!" << std::endl;
	initial_file.close();
	output_file.close();
	return true;
}




template <class T>
inline size_t* sortArrayIndices(T* A, size_t size) {
	vector<size_t> VI(size);
	for (size_t i = 0; i < size; i++) {
		VI[i] = i;
	}		
	vector<T> V(A, A+size);
	std::sort( VI.begin(), VI.end(), IndexCmp<vector<T>&>(V) );
	size_t *I = new size_t[size];
	for (size_t i = 0; i < size; i++) {
		I[i] = VI[i];
	}		
	return I;
};

template <class T>
inline void sortArrayIndices(T* A, size_t size, size_t* I) {
	vector<size_t> VI(size);
	for (size_t i = 0; i < size; i++) {
		VI[i] = i;
	}		
	vector<T> V(A, A+size);
	std::sort( VI.begin(), VI.end(), IndexCmp<vector<T>&>(V) );
	for (size_t i = 0; i < size; i++) {
		I[i] = VI[i];
	}		
};


inline point cartesian_to_spherical(point &p)
{
	if ( p[0] == FLT_MAX || p[1] == FLT_MAX || p[2] == FLT_MAX)
		return point( FLT_MAX, FLT_MAX, FLT_MAX );
	float r = sqrt( p[0]*p[0] + p[1]*p[1] + p[2]*p[2] );
	float theta = acos( p[2] / (r+EPSILON) );
	float phi = atan2( p[1], p[0] );
	return point( r, theta, phi );
}

inline point cartesian_to_cylindrical(point &p)
{
	if ( p[0] == FLT_MAX || p[1] == FLT_MAX || p[2] == FLT_MAX)
		return point( FLT_MAX, FLT_MAX, FLT_MAX );
	float r = sqrt( p[0]*p[0] + p[1]*p[1] );
	float theta = atan2( p[1], p[0] );
	float z = p[2];
	return point( r, theta, z );
}

inline point spherical_to_cartesian(point &p)
{
	if ( p[0] == FLT_MAX || p[1] == FLT_MAX || p[2] == FLT_MAX)
		return point( FLT_MAX, FLT_MAX, FLT_MAX );
	float x = p[0] * sin( p[1] ) * cos( p[2] );
	float y = p[0] * sin( p[1] ) * sin( p[2] );
	float z = p[0] * cos( p[1] );
	return point( x, y, z );
}

inline point cylindrical_to_cartesian(point &p)
{
	if ( p[0] == FLT_MAX )
		return point( FLT_MAX, FLT_MAX, FLT_MAX );
	float x = p[0] * cos( p[1] );
	float y = p[0] * sin( p[1] );
	float z = p[2];
	return point( x, y, z );
}

template <class T>
inline string num2str(T a)
{
	stringstream out;
	out << a;
	return out.str();
}

template <class T>
T unifRand(T a, T b)
{
  double ad = double(a);
  double bd = double(b);
  double unifrand = double( rand() ) / double(RAND_MAX);
  return T( (bd-ad)*unifrand + ad );
}


template <class T>
inline T acosd(T d) {
	d = (d<-1.0f)?-1.0f:d;
	d = (d>1.0f)?1.0f:d;
	return acos(d) * (180.0f / M_PI);
}

inline float getAngleFromUnitVectors(vec& v1, vec &v2 ) {
	float cosphi = v1 DOT v2;
	float sinphi = len(v1 CROSS v2);

	return (float)( atan2( sinphi, cosphi ) * (180.0f / M_PI) );
}

inline float getAngleFromUnitVectors(vec v1, vec v2 ) {
	float cosphi = v1 DOT v2;
	float sinphi = len(v1 CROSS v2);

	return (float)( atan2( sinphi, cosphi ) * (180.0f / M_PI) );
}


template <class T>
inline T logabs(T d) {
	return fabs( log( abs(d) + EPSILON ) );
}

template <class T>
inline T cosd(T d) {
	return cos( (d / 180.0f) * M_PI );
}

template <class T>
inline T sind(T d) {
	return sin( (d / 180.0f) * M_PI );
}




inline bool isemptychar(char *c) {
	if (c == NULL)
		return true;

	int i = 0;
	while( c[i] != '\0' && c[i] != 0) {
		if ( isspace( c[i++] ) == false ) {
			return false;
		}
	}
	return true;
}


inline string getAbsoluteFilename(const string& filename, const bool& addparentdir = false) {
  size_t found = filename.find_last_of("/\\");
  if ( found == string::npos ) {
    return filename;
  }
  if ( addparentdir == false ) {
    return filename.substr(found+1);
  } else {
    string folder = filename.substr(0, found);
    found = folder.find_last_of("/\\");
    return filename.substr(found+1);
  }
}


# endif

