// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements a Maya ASCII file reader
		\author Ed Millard (emillard@direcway.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/file_helpers.h>
#include <k3dsdk/high_res_timer.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/imaterial_collection.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/k3dilocation.h>
#include <k3dsdk/k3dimesh.h>
#include <k3dsdk/k3dipath.h>
#include <k3dsdk/k3dipathpoint.h>
#include <k3dsdk/k3dipathpointcollection.h>
#include <k3dsdk/material.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/transform.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/vectors.h>

#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.hpp>

#include <iostream>
#include <map>

namespace
{

class Edge
{
public:
	Edge(int v1, int v2, bool smooth) :
		m_Smooth(smooth)
	{
		m_Index[0] = v1;
		m_Index[1] = v2;
	}

	int	v1() const { return m_Index[0]; }
	int	v2() const { return m_Index[1]; }
	bool	smooth() const { return m_Smooth; }

private:
	int	m_Index[2];
	bool	m_Smooth;
};

/////////////////////////////////////////////////////////////////////////////
// maya_reader_implementation

class maya_reader_implementation :
	public k3d::ifile_format,
	public k3d::igeometry_read_format,
	public k3d::ideletable
{
public:
	unsigned long priority()
	{
		return 0;
	}

	bool query_can_handle(const boost::filesystem::path& FilePath)
	{
		 return "ma" == k3d::file_extension(FilePath);
	}

	bool pre_read(k3d::idocument&, const boost::filesystem::path& FilePath)
	{
		return true;
	}

	bool read_options(k3d::idocument& Document, const boost::filesystem::path& FilePath)
	{
		return true;
	}

	bool read_file(k3d::idocument& Document, const boost::filesystem::path& FilePath);

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::application_plugin<maya_reader_implementation>, k3d::interface_list<k3d::igeometry_read_format> > factory(
			k3d::uuid(0x43588899, 0x535d46d4, 0xa5aef51d, 0x0c4d019a),
			"MAYAReader",
			"Maya ASCII ( .ma )",
			"");

		return factory;
	}
private:
	std::string LoadMesh(k3d::iobject*);
	std::string LoadNurbsSurface(k3d::iobject*);
	std::string LoadTransform(k3d::iobject*);
	std::string LoadCamera(k3d::iobject* object);
	std::string LoadLight(k3d::iobject* object, const std::string& type);

private:
	// Hierarchy loading
	void LoadHierarchy(std::istringstream&, k3d::iobject_collection::objects_t&, k3d::iobject*);

	// NURBS loading
	bool LoadNurbsGeometry(k3dINurbsSurface*);
	void LoadKnots(k3dINurbsSurface::KnotCollection& knots);

	// Mesh loading
	typedef std::vector<Edge> EdgeArray;

	void LoadVertexes(std::istringstream& stream, k3dIMesh* mesh, k3dPointArray& points);
	void LoadEdges(std::istringstream& stream, EdgeArray& edges);
	void LoadUV(std::istringstream& stream);
	void LoadFaces(k3dIMesh* mesh, k3dPointArray& points, EdgeArray& edges);

private:
	bool isSetAttr(std::istringstream& stream);
	std::string getAttr(std::istringstream& stream);

	bool getArg(std::istringstream&, std::string& arg);
	bool getArg(std::istringstream&, std::string& flag, std::string& arg);

	bool			getBool(std::istringstream& stream);
	k3d::vector3	getVector3(std::istringstream& stream);

private:
	k3d::iobject_collection::objects_t m_Parents;

	boost::filesystem::ifstream m_File;
};

class material_factory
{
public:
	material_factory() :
		m_generic(0)
	{
	}

	k3d::imaterial* generic(k3d::idocument& Document)
	{
		if(!m_generic)
			m_generic = create_material(Document, "Shader", k3d::vector3(0, 1, 0));
		return m_generic;
	}

private:
	k3d::imaterial* create_material(k3d::idocument& Document, const std::string& Name, const k3d::vector3 Color)
	{
		static k3d::idocument_plugin_factory* const material_factory = dynamic_cast<k3d::idocument_plugin_factory*>(k3d::plugin(k3d::classes::RenderManMaterial()));
		return_val_if_fail(material_factory, 0);

		k3d::iobject* const material_object = material_factory->create_plugin(Document);
		return_val_if_fail(material_object, 0);

		material_object->set_name(Name);
		k3d::set_property_value(*material_object, "color", Color);

		Document.objects().add_objects(k3d::make_collection<k3d::iobject_collection::objects_t>(material_object));

		return dynamic_cast<k3d::imaterial*>(material_object);
	}

	k3d::imaterial* m_generic;
};

struct factory_pair {
	const char*		name;
	k3d::uuid	classID;
};

struct factory_pair factory_pairs[] = {
	// Camera
	{"camera",			k3d::classes::Camera() },

	// Geometry
	{"mesh",			k3d::classes::FrozenMesh() },
	{"nurbsSurface",	k3d::classes::FrozenMesh() },

	// Hierarchy
	{"joint",			k3d::classes::Transform() },
	{"transform",		k3d::classes::Transform() },

	// Lights
	{"ambientLight",	k3d::classes::RenderManLight() },
	{"directionalLight",k3d::classes::RenderManLight() },
	{"pointLight",		k3d::classes::RenderManLight() },
	{"spotLight",		k3d::classes::RenderManLight() },

	// Misc
	{"script",			k3d::uuid::null() },

	{ NULL,				k3d::uuid::null() }
};

// Nodes not implemented that will be instanced as transform proxy nodes
// "nurbsCurve"
// "implicitSphere"
// "curveVarGroup"
// "polySoftEdge"
//
// "AnimCurveTA"		// Time to Angular
// "AnimCurveTL"		// Time to Linear
// "animCurveTU"		// Time to Unitless
// "animCurveTT"		// Time to Time
// "animCurveUA"		// Unitless to Angular
// "animCurveUL"		// Unitless to Linear
// "animCurveUT"		// Unitless to Time
// "animCurveUU"		// Unitless to Unitless
//
// "lattice"
// "baseLattice"
// "flexorShape"
//
// "phong"
// "phongE"
// "lambert"
// "ramp"
// "file"
// "materialInfo"
// "place2dTexture"
// "place3dTexture"
// "shadingEngine"
//
// "groupId"
// "groupParts"
// "objectSet"
//
// "jointCluster"
// "jointLattice"
// "jointFfd"
// "ikEffector"
// "ikHandle"
// "clusterHandle"
// "locator"
//
// "poleVectorConstraint"
// "pointConstraint"
// "orientConstraint"

bool maya_reader_implementation::read_file(k3d::idocument& Document, const boost::filesystem::path& FilePath)
{
	// Try to open the input file ...
	m_File.open(FilePath);
	if(!m_File.good()) {
		std::cerr << __PRETTY_FUNCTION__ << ": error opening [" << FilePath.native_file_string() << "]" << std::endl;
		return_val_if_fail(0, false);
	}

	// Get ready to create materials as-needed
	//material_factory materials;

	// Initialize node factories
	typedef std::map<std::string, k3d::idocument_plugin_factory* const>factory_t;
	factory_t factories;

	factory_pair* pair = factory_pairs;
	while(pair->name) {
		k3d::idocument_plugin_factory* factory = 0;
		if(pair->classID != k3d::uuid::null()) {
			factory = dynamic_cast<k3d::idocument_plugin_factory*>(k3d::plugin(pair->classID));
			if(!factory)
				{
					std::cerr << "Couldn't find factory for " << pair->name << std::endl;
					return_val_if_fail(0, false);
				}
		}
		factories.insert(std::make_pair(pair->name, factory));
		pair++;
	}

	// Transform is used as proxy node for unimplemented nodes
	k3d::idocument_plugin_factory* const transform_factory = dynamic_cast<k3d::idocument_plugin_factory*>(k3d::plugin(k3d::classes::Transform()));
	return_val_if_fail(transform_factory, false);

	// Keep track of objects as they're created ...
	k3d::iobject_collection::objects_t records;

	while(!m_File.eof()) {
		// Grab one line at a time ...
		std::string line;
		k3d::getline(m_File, line);
	nextLine:
		std::istringstream stream(line);
		std::string cmd;
		stream >> cmd;
		if(cmd == "createNode"){
			std::string nodeType;
			stream >> nodeType;

			k3d::iobject* object = NULL;

			factory_t::const_iterator factory = factories.find(nodeType);
			if(factory != factories.end()) {
				if(factory->second) {
					object = factory->second->create_plugin(Document);
				}
			}else{
				// Unimplemented node use a simple transform as proxy
				object = transform_factory->create_plugin(Document);
				//printf("node type %s unimplemented\n",nodeType.c_str());
			}

			if(object) {
				LoadHierarchy(stream, records, object);

				// Load attributes specific to the node, if implemented
				if(nodeType == "transform" || nodeType == "joint") {
					line = LoadTransform(object);
				}else if(nodeType == "nurbsSurface") {
					line = LoadNurbsSurface(object);
				}else if(nodeType == "mesh") {
					line = LoadMesh(object);
				}else if(nodeType == "camera") {
					line = LoadCamera(object);
				}else if(nodeType == "ambientLight" ||
						 nodeType == "directionalLight" ||
						 nodeType == "pointLight" ||
						 nodeType == "spotLight") {
					line = LoadLight(object, nodeType);
				}else{
					continue;
				}
				goto nextLine;
			}
		}else if(cmd == "connectAttr"){
			//printf("connectAttr\n");
		}else if(cmd == "currentUnit"){
			//printf("currentUnit\n");
		}else if(cmd == "requires"){
			//printf("requires\n");
		}
	}

	printf("==============================================\n");
	Document.objects().add_objects(records);

	return true;
}

static void
UnimplementedCommonAttr(const std::string& attr)
{
	//std::cerr << "Common attribute " << attr << " unimplemented" << std::endl;
}

// Attributes some or all nodes have in common
bool static LoadCommonAttr(const std::string& attr, k3d::iobject* object)
{
	bool status = true;

	if(attr == ".csh") { // Cast shadows
		UnimplementedCommonAttr(attr);
	}else if(attr == ".io") {	// Intermediate object(i.e. constuction hitory)
		UnimplementedCommonAttr(attr);
	}else if(attr == ".iog") {	// Grouping, I think
		UnimplementedCommonAttr(attr);
	}else if(attr == ".vif") {	// Visibile in refractions
		UnimplementedCommonAttr(attr);
	}else if(attr == ".vir") {		// Visibile in reflections
		UnimplementedCommonAttr(attr);
	}else{
		status = false;
	}

	return status;
}

// Hierarchy
//
void maya_reader_implementation::LoadHierarchy(std::istringstream& stream, k3d::iobject_collection::objects_t& records, k3d::iobject* object)
{
	std::string name, parent;

	const std::string quote("\"");

	// Read node name and parent
	while(!stream.eof()) {
		std::string flag, arg;
		getArg(stream, flag);
		if(flag == "-n") {
			getArg(stream, arg);
			name.assign(arg, 1, arg.rfind("\"")-1);
			int pathSep = name.rfind("|");
			if(pathSep != -1) {
				printf("%s ",name.c_str());
			}
		}else if(flag == "-p") {
			getArg(stream, arg);
			parent.assign(arg, 1, arg.rfind("\"")-1);

			// Check for full path
			int pathSep = parent.rfind("|");
			if(pathSep != -1) {
				parent.assign(parent, pathSep+1, parent.size());
			}
		}
	}

	// Build hierarchy
	if(parent.size() == 0) {
		records.insert(object);
		m_Parents.clear();
	}else{
		int depth = 0;
		bool found = false;
		for(k3d::iobject_collection::objects_t::iterator Parent = m_Parents.begin(); Parent != m_Parents.end(); ++Parent) {
			depth++;
			if((*Parent)->name() == parent) {
				records.insert(object);
				++Parent;
				m_Parents.erase(Parent, m_Parents.end());
				found = true;
				break;
			}
		}

		if(!found) {
			printf("parent %s not found for %s\n", parent.c_str(), name.c_str());
			records.insert(object);
		}
	}
	m_Parents.insert(object);

	object->set_name(name);
}

static void
UnimplementedTransformAttr(const std::string& attr)
{
	//std::cerr << "Transform attribute " << attr << " unimplemented" << std::endl;
}

std::string maya_reader_implementation::LoadTransform(k3d::iobject* object)
{
	std::string line;

	while(!m_File.eof()) {
		k3d::getline(m_File, line);
		std::istringstream stream(line);

		if(!isSetAttr(stream))
			break;

		std::string attr(getAttr(stream));

		if(attr == "-k" || attr == "-ac" || attr == "-av" || attr == "-l") {
			// boolean argument for visibility among other things
			// FIXME
		}else if(attr == ".t") { // Translate
			k3d::set_position(*object, getVector3(stream));
		}else if(attr == ".r") { // Rotate
			k3d::euler_angles euler(getVector3(stream), k3d::euler_angles::XYZstatic);
			// FIXME k3d::set_orientation(*object, euler);
		}else if(attr == ".s") { // Scale
			k3d::set_scale(*object, getVector3(stream));
		}else if(attr == ".v") { // Visibility
			k3d::set_property_value(*object, "displayLocalAxis", getBool(stream));
			// FIXME
		}else if(attr == ".rp") { // Rotate pivot
			getVector3(stream); // FIXME
		}else if(attr == ".rpt") { // Rotate pivot translate
			getVector3(stream); // FIXME
		}else if(attr == ".sp") { // Scale pivot
			getVector3(stream); // FIXME
		}else if(attr == ".spt") { // Scale pivot translate
			getVector3(stream); // FIXME
		}else if(attr == ".hdl") { // Handle
			k3d::set_property_value(*object, "handle", getVector3(stream));
		}else if(attr == ".dh") { // Display Handle
			k3d::set_property_value(*object, "displayHandle", getBool(stream));
		}else if(attr == ".dla") { // Display local axis
			k3d::set_property_value(*object, "displayLocalAxis", getBool(stream));
		}else if(attr == ".mntl") { // Min translate limit
			getVector3(stream); // FIXME
		}else if(attr == ".mxtl") { // Max translate limit
			getVector3(stream); // FIXME
		}else if(attr == ".mnrl") { // Min rotate limit
			getVector3(stream); // FIXME
		}else if(attr == ".mxrl") { // Max rotate limit
			getVector3(stream); // FIXME
		}else if(attr == ".mnsl") { // Min scale limit
			getVector3(stream); // FIXME
		}else if(attr == ".mxsl") { // Max scale limit
			getVector3(stream); // FIXME

		// Joint specific
		}else if(attr == ".jtx") { // Joint X
			UnimplementedTransformAttr(attr);
		}else if(attr == ".jty") { // Joint Y
			UnimplementedTransformAttr(attr);
		}else if(attr == ".jtz") { // Joint Z
			UnimplementedTransformAttr(attr);
		}else if(attr == ".bps") { // Bind Pose
			UnimplementedTransformAttr(attr);
		}else if(attr == ".jo") { // Joint orientation
			UnimplementedTransformAttr(attr);
		}else if(attr == ".pa") { // Prefered angle
			UnimplementedTransformAttr(attr);

		// Unknown
		}else if(attr == ".uoc") { // ???
			UnimplementedTransformAttr(attr);
		}else if(attr == ".oc") { // ???
			UnimplementedTransformAttr(attr);
		}else if(attr == ".sh") { // ???
			UnimplementedTransformAttr(attr);
		}else if(attr == ".ihi") { // ???
			UnimplementedTransformAttr(attr);
		}else if(attr == ".ove") { // ???
			UnimplementedTransformAttr(attr);
		}else if(attr == ".ro") { // ??
			UnimplementedTransformAttr(attr);
		}else if(attr == ".ra") { // ??
			UnimplementedTransformAttr(attr);
		}else if(attr == ".ovdt") { // ??
			UnimplementedTransformAttr(attr);
		}else if(!LoadCommonAttr(attr, object)){
			UnimplementedTransformAttr(attr);
		}
	}
	return line;
}

// Mesh loading
//
void
maya_reader_implementation::LoadUV(std::istringstream& stream)
{
	std::string line;
	k3d::vector2 v2;

	int n = 0;
	bool end = false;
	while(1) {
		if(stream >> v2[n]) {
			n++;
			if(n > 1) {
				n = 0;
			}
		}else{
			if(!end) {
				k3d::getline(m_File, line);
				int semicolon = line.rfind(";");
			   	if(semicolon != -1) {
					end = true;
				}
				stream.clear();
				stream.str(line);
			}else{
				break;
			}
		}
	}
}

void
maya_reader_implementation::LoadVertexes(std::istringstream& stream, k3dIMesh* mesh, k3dPointArray& points)
{
	std::string line;
	k3d::vector3 v;

	int n = 0;
	bool end = false;
	while(1) {
		if(stream >> v[n]) {
			n++;
			if(n > 2) {
				k3dIPoint* const point = mesh->CreatePoint();
				if(point == NULL) return;
				point->Location()->SetLocalXYZ(v);
				points.push_back(point);
				n = 0;
			}
		}else{
			if(!end) {
				k3d::getline(m_File, line);
				int semicolon = line.rfind(";");
				if(semicolon != -1) {
					end = true;
				}
				stream.clear();
				stream.str(line);
			}else{
				break;
			}
		}
	}
}

void
maya_reader_implementation::LoadEdges(std::istringstream& stream, EdgeArray& edges)
{
	std::string line;

	bool end = false;
	int values[3];
	int n = 0;
	while(1) {
		unsigned int i;
		if(stream >> i) {
			values[n] = i;
			if(n < 2) {
				n++;
			}else{
				edges.push_back(Edge(values[0], values[1], values[2] == 1));
				n = 0;
			}
		}else{
			if(!end) {
				k3d::getline(m_File, line);
				int semicolon = line.rfind(";");
				if(semicolon != -1) {
					end = true;
				}
				stream.clear();
				stream.str(line);
			}else{
				break;
			}
		}
	}
}

void
maya_reader_implementation::LoadFaces(k3dIMesh* mesh, k3dPointArray& points, EdgeArray& edges)
{
	std::string line;

	bool end = false;

	while(!end) {
		k3d::getline(m_File, line);
		int semicolon = line.rfind(";");
		if(semicolon != -1) {
			end = true;
		}
		std::istringstream stream(line);

		std::string type;
		stream >> type;

		int numEdges;
		stream >> numEdges;

		if(type == "f") {			// Face
			int prevPoint = -1;
			k3dIPathPoint* pathpoint;
			k3dIPath* const path = mesh->CreatePath();
			for(int i = 0 ; i < numEdges ; ++i) {
				int index;
				if(stream >> index) {
					unsigned int j;
					// Edge reversed if index is negative
					if(index >= 0) {
						j = (unsigned int)index;
						Edge edge = edges[j];
						if(edge.v1() != prevPoint) {
							pathpoint = path->Points()->AddPoint(points[edge.v1()]);
						}
						pathpoint = path->Points()->AddPoint(points[edge.v2()]);
						prevPoint = edge.v2();
					}else{
						// Negative edge indexes mean reversed edge
						// Reversed edges are offset by 1 which is a little weird
						j = (unsigned int)(-index) - 1;
						Edge edge = edges[j];
						if(edge.v2() != prevPoint) {
							pathpoint = path->Points()->AddPoint(points[edge.v2()]);
						}
						pathpoint = path->Points()->AddPoint(points[edge.v1()]);
						prevPoint = edge.v1();
					}
				}else{
					return;
				}
			}
		}else if(type == "mf"){		// Map face
			//printf("map face not implemented\n");
		}else if(type == "h"){		// Hole
			//printf("mesh hole not implemented\n");
		}else if(type == "mh"){		// Map hole
			//printf("map hole not implemented\n");
		}else if(type == "fc"){		// Face color
			//printf("face color not implemented\n");
		}else if(type == "mu"){		// Multi UV I think
			//printf("multi UV, I think, not implemented\n");
		}
	}
}

static void
UnimplementedMeshAttr(const std::string& attr)
{
	//std::cerr << "Mesh attribute " << attr << " unimplemented" << std::endl;
}

std::string maya_reader_implementation::LoadMesh(k3d::iobject* object)
{
	k3dIMesh* mesh = dynamic_cast<k3dIMesh*>(object);

	std::string flag, arg;
	std::string line;

	k3dPointArray points;
	k3dPointArray vertex;
	EdgeArray edges;

	while(!m_File.eof()) {
		k3d::getline(m_File, line);
		std::istringstream stream(line);

		if(!isSetAttr(stream))
			break;

		std::string attr(getAttr(stream));
		if(attr == "-k") {
			// boolean argument for visibility in particular
			// FIXME
		}else if(attr == "-s") {
			// reserves storage for components
			// FIXME
		}else if(attr == ".vt") { // vertexes
			LoadVertexes(stream, mesh, vertex);
		}else if(attr == ".pt") { // ??
			LoadVertexes(stream, mesh, points);
		}else if(attr == ".ed") { // edges
			LoadEdges(stream, edges);
		}else if(attr == ".fc") { // Faces
			LoadFaces(mesh, vertex, edges);
		}else if(attr == ".uv") { // UV mappping
			LoadUV(stream);
		}else if(attr == ".bck") { // Backface culling mode
			UnimplementedMeshAttr(attr);
		}else if(attr == ".uvst") { // UV Set
			UnimplementedMeshAttr(attr);
		}else if(attr == ".cuvs") { // Current UV set
			UnimplementedMeshAttr(attr);
		}else if(attr == ".dv") { // Display vertices, I think
			UnimplementedMeshAttr(attr);
		}else if(!LoadCommonAttr(attr, object)){
			UnimplementedMeshAttr(attr);
		}
	}
	return line;
}

// Nurbs
//
void
maya_reader_implementation::LoadKnots(k3dINurbsSurface::KnotCollection& knots)
{
	std::string line;

	k3d::getline(m_File, line);
	std::istringstream stream(line);
	int numKnots;

	if(!(stream >> numKnots) || numKnots < 1) {
		stream.clear();
		k3d::getline(m_File, line);
		stream.str(line);
		stream >> numKnots;
	}
	for(int i = 0 ; i < numKnots ; ++i) {
		double knot;
		if(!(stream >> knot)) {
			stream.clear();
			k3d::getline(m_File, line);
			stream.str(line);
			stream >> knot;
		}
		knots.push_back(knot);
	}
}

bool maya_reader_implementation::LoadNurbsGeometry(k3dINurbsSurface* surface)
{
	bool status = true;

	std::string line;

	// U,V degreee and some other control parameters
	k3d::getline(m_File, line);
	std::istringstream stream1(line);

	int degreeU, degreeV, formU, formV;
	stream1 >> degreeU;
	stream1 >> degreeV;
	stream1 >> formU;
	stream1 >> formV;
	std::string rational;
	stream1 >> rational;
	//printf("%d %d  %d %d  %s\n", degreeU, degreeV, formU, formV, rational.c_str());

	k3d::set_property_value(*surface, "degreeu", degreeU);
	k3d::set_property_value(*surface, "degreev", degreeV);
	k3d::set_property_value(*surface, "formu", formU == 1);
	k3d::set_property_value(*surface, "formv", formV == 1);
	k3d::set_property_value(*surface, "rational", rational == "yes");

	// U knots
	k3dINurbsSurface::KnotCollection knotsU;
	LoadKnots(knotsU);
	surface->SetKnotsU(knotsU);

	// V knots
	k3dINurbsSurface::KnotCollection knotsV;
	LoadKnots(knotsV);
	surface->SetKnotsV(knotsV);

	// Should be blank
	k3d::getline(m_File, line);

	// CV's
	k3d::getline(m_File, line);
	std::istringstream stream5(line);
	int numCVs;
	stream5 >> numCVs;

	k3dINurbsSurface::CVCollection cvs;
	for(int i = 0 ; i < numCVs ; ++i) {
		k3d::getline(m_File, line);
		std::istringstream streamCV(line);

		k3d::vector3 cv;
		if(!(streamCV >> cv)) {
			status = false;
		}
		cvs.push_back(cv);
	}
	surface->SetCVs(cvs);

	// Should be blank
	k3d::getline(m_File, line);

	// Should be ";"
	k3d::getline(m_File, line);

	return status;
}

static void
UnimplementedNurbsAttr(const std::string& attr)
{
	//std::cerr << "Nurbs attribute " << attr << " unimplemented" << std::endl;
}

std::string maya_reader_implementation::LoadNurbsSurface(k3d::iobject* object)
{
	k3dINurbsSurface* surface = dynamic_cast<k3dINurbsSurface*>(object);

	// set attributes
	std::string line;
	while(!m_File.eof()) {
		k3d::getline(m_File, line);
		std::istringstream stream(line);
		//std::cerr << line << std::endl;

		if(!isSetAttr(stream))
			break;

		std::string attr(getAttr(stream));
		if(attr == "-k" || attr == "-s") {
			// boolean argument for visibility in particular
		}else if(attr == ".dvu") { // Divisions per spa on U
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".dvv") { // Divisions per spa on V
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".cpr") { // Curve precision wireframe
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".cps") { // Curve precision shaded
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".tw") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".tf") { // Trim face
			bool end = false;
			while(!end) {
				k3d::getline(m_File, line);
				int semicolon = line.rfind(";");
				if(semicolon != -1) {
					end = true;
				}
			}
		}else if(attr == ".cc") {  // The surface
			std::string flag, arg;
			getArg(stream, flag, arg);
			LoadNurbsGeometry(surface);
		}else if(attr == ".nufa") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".nvfa") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".cp") {  // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".dh") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".dcv") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(attr == ".op") { // ??
			UnimplementedNurbsAttr(attr);
		}else if(!LoadCommonAttr(attr, object)){
			UnimplementedNurbsAttr(attr);
		}
	}
	return line;
}

// Camera
static void
UnimplementedCameraAttr(const std::string& attr)
{
	//std::cerr << "Camera attribute " << attr << " unimplemented" << std::endl;
}

std::string maya_reader_implementation::LoadCamera(k3d::iobject* object)
{
	//k3dICamera* camera = dynamic_cast<k3dICamera*>(object);

	// set attributes
	std::string line;
	while(!m_File.eof()) {
		k3d::getline(m_File, line);
		std::istringstream stream(line);

		if(!isSetAttr(stream))
			break;

		std::string attr(getAttr(stream));
		if(attr == "-k" || attr == "-s") {
			// boolean argument for visibility in particular
		}else if(attr == ".cap") { // Camera Aperature
			UnimplementedCameraAttr(attr);
		}else if(attr == ".coi") { // Center of Interest
			UnimplementedCameraAttr(attr);
		}else if(attr == ".fl") { // Focal length
			UnimplementedCameraAttr(attr);
		}else if(attr == ".rnd") { // Renderable
			UnimplementedCameraAttr(attr);
		}else if(attr == ".fcp") { // Far clip plane
			UnimplementedCameraAttr(attr);
		}else if(attr == ".ncp") { // Near clip plane
			UnimplementedCameraAttr(attr);
		}else if(attr == ".o") { // Orthographic if true
			UnimplementedCameraAttr(attr);
		}else if(attr == ".ow") { // Ortho width
			UnimplementedCameraAttr(attr);
		}else if(attr == ".imn") { // Image name
			UnimplementedCameraAttr(attr);
		}else if(attr == ".den") { // Depth name
			UnimplementedCameraAttr(attr);
		}else if(attr == ".man") { // Mask name
			UnimplementedCameraAttr(attr);
		}else if(attr == ".hc") { // ??
			UnimplementedCameraAttr(attr);
		}else if(!LoadCommonAttr(attr, object)){
			UnimplementedCameraAttr(attr);
		}
	}
	return line;
}

// Lights
static void
UnimplementedLightAttr(const std::string& attr)
{
	//std::cerr << "Light attribute " << attr << " unimplemented" << std::endl;
}

std::string maya_reader_implementation::LoadLight(k3d::iobject* object, const std::string& type)
{
	//k3dIEditorLight* light = dynamic_cast<k3dIEditorLight*>(object);
	if(type == "ambientLight") {
		k3d::set_property_value(*object, "shader", "k3d_ambientlight");
	}else if(type == "directionalLight") {
		k3d::set_property_value(*object, "shader", "k3d_directionallight");
	}else if(type == "pointLight") {
		k3d::set_property_value(*object, "shader", "k3d_pointlight");
	}else if(type == "spotLight") {
		k3d::set_property_value(*object, "shader", "k3d_spotlight");
	}

	// set attributes
	std::string line;
	while(!m_File.eof()) {
		k3d::getline(m_File, line);
		std::istringstream stream(line);

		if(!isSetAttr(stream))
			break;

		std::string attr(getAttr(stream));
		if(attr == "-k" || attr == "-s") {
			// boolean argument for visibility in particular
		}else if(attr == ".cl") { // Color
			UnimplementedLightAttr(attr);
		}else if(attr == ".col") { // ??
			UnimplementedLightAttr(attr);
		}else if(attr == ".in") { // Intensity
			UnimplementedLightAttr(attr);
		}else if(attr == ".do") { // Drop off
			UnimplementedLightAttr(attr);
		}else if(attr == ".ca") { // Cone angle
			UnimplementedLightAttr(attr);
		}else if(attr == ".pa") { // Penumbra angle
			UnimplementedLightAttr(attr);
		}else if(attr == ".as") { // Ambient Shader
			UnimplementedLightAttr(attr);
		}else if(!LoadCommonAttr(attr, object)){
			UnimplementedLightAttr(attr);
		}
	}
	return line;
}

// Helpers
//
bool maya_reader_implementation::getArg(std::istringstream& stream, std::string& arg)
{
	if(!(stream >> arg)) {
		std::string line;
		k3d::getline(m_File, line);
		stream.clear();
		stream.str(line);
		stream >> arg;
	}
	return true;
}

bool maya_reader_implementation::getArg(std::istringstream& stream, std::string& flag, std::string& arg)
{
	stream >> flag;
	stream >> arg;
	return true;
}

bool maya_reader_implementation::isSetAttr(std::istringstream& stream)
{
	std::string cmd;
	stream >> cmd;
	return cmd == "setAttr";
}

std::string maya_reader_implementation::getAttr(std::istringstream& stream)
{
	std::string tmp;
	stream >> tmp;

	int array = tmp.find("[");
	int rquote = tmp.rfind("\"");

	std::string attr;
	if(array != -1) {
		attr.assign(tmp, 1, array-1);
	}else if(rquote != -1) {
		attr.assign(tmp, 1, rquote-1);
	}else{
		attr = tmp;
	}
	return attr;
}

bool maya_reader_implementation::getBool(std::istringstream& stream)
{
	std::string tmp;
	stream >> tmp;
	int semicolon = tmp.rfind(";");
	if(semicolon != -1) {
		tmp.assign(tmp, 0, semicolon);
	}

	return (tmp == "yes");
}

k3d::vector3 maya_reader_implementation::getVector3(std::istringstream& stream)
{
	std::string line;

	getArg(stream, line);
	getArg(stream, line);

	k3d::vector3 v;

	for(int i = 0 ; i < 3 ; ++i) {
		if(stream >> v[i]) {
		}else{
			k3d::getline(m_File, line);
			stream.clear();
			stream.str(line);
			stream >> v[i];
		}
	}

	return v;
}

} // namespace

namespace libk3dgeometry
{

k3d::iplugin_factory& maya_reader_factory()
{
	return maya_reader_implementation::get_factory();
}

} // namespace libk3dgeometry


