/**
 * @file awavefront.cpp
 * @brief A system which allows saving in-world objects to Wavefront .OBJ files for offline texturizing/shading.
 * @authors Apelsin, Lirusaito
 *
 * $LicenseInfo:firstyear=2011&license=LGPLV3$
 * Copyright (C) 2011-2013 Apelsin
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA */

#include "llviewerprecompiledheaders.h"

#include "awavefront.h"

// library includes
#include "aifilepicker.h"
#include "llnotificationsutil.h"

// newview includes
#include "lfsimfeaturehandler.h"
#include "llavatarappearancedefines.h"
#include "llface.h"
#include "llvoavatar.h"
#include "llvovolume.h"
#include "llviewerinventory.h"
#include "llinventorymodel.h"
#include "llinventoryfunctions.h"

// menu includes
#include "llevent.h"
#include "llmemberlistener.h"
#include "llview.h"
#include "llselectmgr.h"

LLVOAvatar* find_avatar_from_object(LLViewerObject* object);
extern LLUUID gAgentID;

//Typedefs used in other files, using here for consistency.
typedef std::vector<LLAvatarJoint*> avatar_joint_list_t;
typedef LLMemberListener<LLView> view_listener_t;

namespace
{
	const std::string OBJ(".obj");

	void save_wavefront_continued(WavefrontSaver* wfsaver, AIFilePicker* filepicker)
	{
		if (filepicker->hasFilename())
		{
			const std::string selected_filename = filepicker->getFilename();
			if (LLFILE* fp = LLFile::fopen(selected_filename, "wb"))
			{
				wfsaver->saveFile(fp);
				llinfos << "OBJ file saved to " << selected_filename << llendl;
				if (gSavedSettings.getBOOL("OBJExportNotifySuccess"))
					LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename));
				fclose(fp);
			}
			else llerrs << "can't open: " << selected_filename << llendl;
		}
		else llwarns << "No file; bailing" << llendl;

		delete wfsaver;
	}

	void save_wavefront_picker(WavefrontSaver* wfsaver, std::string name)
	{
		AIFilePicker* filepicker = AIFilePicker::create();
		filepicker->open(name);
		filepicker->run(boost::bind(&save_wavefront_continued, wfsaver, filepicker));
	}

	void save_wavefront_on_confirm(const LLSD& notification, const LLSD& response, WavefrontSaver* wfsaver, std::string name)
	{
		if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) // 0 - Proceed, first choice
		{
			save_wavefront_picker(wfsaver, name);
		}
		else
		{
			delete wfsaver;
		}
	}
}

Wavefront::Wavefront(vert_t v, tri_t t)
:	name("")
,	vertices(v)
,	triangles(t)
{
}

Wavefront::Wavefront(const LLVolumeFace* face, const LLXform* transform, const LLXform* transform_normals)
:	name("")
{
	class v4adapt
	{
		private:
			LLStrider<LLVector4a> mV4aStrider;
		public:
			v4adapt(LLVector4a* vp){ mV4aStrider = vp; }
			inline LLVector3 operator[] (const unsigned int i)
			{
				return LLVector3((F32*)&mV4aStrider[i]);
			}
	};
	v4adapt verts(face->mPositions);
	for (S32 i = 0; i < face->mNumVertices; ++i)
	{
		LLVector3 v = verts[i];
		vertices.push_back(std::pair<LLVector3, LLVector2>(v, face->mTexCoords[i]));
	}

	if (transform) Transform(vertices, transform);

	v4adapt norms(face->mNormals);
	for (S32 i = 0; i < face->mNumVertices; ++i)
		normals.push_back(norms[i]);

	if (transform_normals) Transform(normals, transform_normals);

	for (S32 i = 0; i < face->mNumIndices/3; ++i)
	{
		triangles.push_back(tri(face->mIndices[i*3+0], face->mIndices[i*3+1], face->mIndices[i*3+2]));
	}
}

Wavefront::Wavefront(LLFace* face, LLPolyMesh* mesh, const LLXform* transform, const LLXform* transform_normals)
:	name("")
{
	LLVertexBuffer* vb = face->getVertexBuffer();
	if (!vb) return;

	LLStrider<LLVector3> getVerts;
	LLStrider<LLVector3> getNorms;
	LLStrider<LLVector2> getCoord;
	LLStrider<U16> getIndices;
	face->getGeometry(getVerts, getNorms, getCoord, getIndices);

	const U16 start = face->getGeomStart();
	const U32 end = start + (mesh ? mesh->getNumVertices() : vb->getNumVerts()) - 1; //vertices
	for (U32 i = start; i <= end; ++i)
		vertices.push_back(std::make_pair(getVerts[i], getCoord[i]));

	if (transform) Transform(vertices, transform);

	for (U32 i = start; i <= end; ++i)
		normals.push_back(getNorms[i]);

	if (transform_normals) Transform(normals, transform_normals);

	const U32 pcount = mesh ? mesh->getNumFaces() : (vb->getNumIndices()/3); //indices
	const U16 offset = face->getIndicesStart(); //indices
	for (U32 i = 0; i < pcount; ++i)
	{
		triangles.push_back(tri(getIndices[i * 3  + offset] + start, getIndices[i * 3 + 1 + offset] + start, getIndices[i * 3 + 2 + offset] + start));
	}
}

void Wavefront::Transform(vert_t& v, const LLXform* x) //recursive
{
	LLMatrix4 m;
	x->getLocalMat4(m);
	for (vert_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv)
	{
		iterv->first = iterv->first * m;
	}

	if (const LLXform* xp = x->getParent()) Transform(v, xp);
}

void Wavefront::Transform(vec3_t& v, const LLXform* x) //recursive
{
	LLMatrix4 m;
	x->getLocalMat4(m);
	for (vec3_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv)
	{
		*iterv = *iterv * m;
	}

	if (const LLXform* xp = x->getParent()) Transform(v, xp);
}

WavefrontSaver::WavefrontSaver()
{}

void WavefrontSaver::Add(const Wavefront& obj)
{
	obj_v.push_back(obj);
}

void WavefrontSaver::Add(const LLVolume* vol, const LLXform* transform, const LLXform* transform_normals)
{
	const int faces = vol->getNumVolumeFaces();
	for(int i = 0; i < faces; ++i) //each face will be treated as a separate Wavefront object
	{
		Add(Wavefront(&vol->getVolumeFace(i), transform, transform_normals));
	}
}
void WavefrontSaver::Add(const LLViewerObject* some_vo)
{
	LLXform v_form;
	v_form.setScale(some_vo->getScale());
	v_form.setPosition(some_vo->getRenderPosition());
	v_form.setRotation(some_vo->getRenderRotation());

	LLXform normfix;
	normfix.setRotation(v_form.getRotation()); //Should work...
	Add(some_vo->getVolume(), &v_form, &normfix);
}
namespace
{
	// Identical to the one in daeexport.cpp.
	bool can_export_node(LLSelectNode* node)
	{
		LLPermissions* perms = node->mPermissions;	// Is perms ever NULL?
		// This tests the PERM_EXPORT bit too, which is not really necessary (just checking if it's set
		// on the root prim would suffice), but also isn't hurting.
		if (!(perms && perms->allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy())))
		{
			return false;
		}

		// Additionally chack if this is a sculpt
		LLViewerObject* obj = node->getObject();
		if (obj->isSculpted() && !obj->isMesh())
		{
			LLSculptParams *sculpt_params = (LLSculptParams *)obj->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
			LLUUID sculpt_id = sculpt_params->getSculptTexture();

			// Find inventory items with asset id of the sculpt map
			LLViewerInventoryCategory::cat_array_t cats;
			LLViewerInventoryItem::item_array_t items;
			LLAssetIDMatches asset_id_matches(sculpt_id);
			gInventory.collectDescendentsIf(LLUUID::null,
							cats,
							items,
							LLInventoryModel::INCLUDE_TRASH,
							asset_id_matches);

			// See if any of the inventory items matching this sculpt id are exportable
			for (S32 i = 0; i < items.count(); i++)
			{
				const LLPermissions item_permissions = items[i]->getPermissions();
				if (item_permissions.allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy()))
				{
					return true;
				}
			}

			return false;
		}
		else // not sculpt, we already checked generic permissions
		{
			return true;
		}
	}

	class LFSaveSelectedObjects : public view_listener_t
	{
		bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)
		{
			if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection())
			{
				if (!selection->getFirstRootObject())
				{
					if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
						LLNotificationsUtil::add("ExportFailed");
					return true;
				}

				WavefrontSaver* wfsaver = new WavefrontSaver; // deleted in callback
				wfsaver->offset = -selection->getFirstRootObject()->getRenderPosition();
				S32 total = 0;
				S32 included = 0;
				for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter)
				{
					total++;
					LLSelectNode* node = *iter;
					if (!can_export_node(node)) continue;
					included++;
					wfsaver->Add(node->getObject());
				}
				if (wfsaver->obj_v.empty())
				{
					if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
						LLNotificationsUtil::add("ExportFailed");
					delete wfsaver;
					return true;
				}

				if (total != included)
				{
					LLSD args;
					args["TOTAL"] = total;
					args["FAILED"] = total - included;
					LLNotificationsUtil::add("WavefrontExportPartial", args, LLSD(), boost::bind(&save_wavefront_on_confirm, _1, _2, wfsaver, selection->getFirstNode()->mName.c_str() + OBJ));
					return true;
				}

				save_wavefront_picker(wfsaver, selection->getFirstNode()->mName.c_str() + OBJ);
			}
			return true;
		}
	};
}

void WavefrontSaver::Add(const LLVOAvatar* av_vo) //adds attachments, too!
{
	offset = -av_vo->getRenderPosition();
	avatar_joint_list_t vjv = av_vo->mMeshLOD;
	for (avatar_joint_list_t::const_iterator itervj = vjv.begin(); itervj != vjv.end(); ++itervj)
	{
		const LLViewerJoint* vj = dynamic_cast<LLViewerJoint*>(*itervj);
		if (!vj || vj->mMeshParts.empty()) continue;

		LLViewerJointMesh* vjm = dynamic_cast<LLViewerJointMesh*>(vj->mMeshParts[0]); //highest LOD
		if (!vjm) continue;

		vjm->updateJointGeometry();
		LLFace* face = vjm->mFace;
		if (!face) continue;

		//Beware: this is a hack because LLFace has multiple LODs
		//'pm' supplies the right number of vertices and triangles!
		LLPolyMesh* pm = vjm->getMesh();
		if (!pm) continue;
		LLXform normfix;
		normfix.setRotation(pm->getRotation());

		//Special case for balls...I mean eyeballs!
		static const std::string eyeLname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_LEFT)->mName;
		static const std::string eyeRname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_RIGHT)->mName;
		const std::string name = vj->getName();
		if (name == eyeLname || name == eyeRname)
		{
			LLXform lol;
			lol.setPosition(-offset);
			Add(Wavefront(face, pm, &lol, &normfix));
		}
		else
			Add(Wavefront(face, pm, NULL, &normfix));
	}

	for (LLVOAvatar::attachment_map_t::const_iterator iter = av_vo->mAttachmentPoints.begin(); iter != av_vo->mAttachmentPoints.end(); ++iter)
	{
		LLViewerJointAttachment* ja = iter->second;
		if (!ja) continue;

		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator itero = ja->mAttachedObjects.begin(); itero != ja->mAttachedObjects.end(); ++itero)
		{
			LLViewerObject* o = *itero;
			if (!o) continue;

			LLDynamicArray<LLViewerObject*> prims = LLDynamicArray<LLViewerObject*>();
			o->addThisAndAllChildren(prims);
			for (LLDynamicArray<LLViewerObject*>::iterator iterc = prims.begin(); iterc != prims.end(); ++iterc)
			{
				const LLViewerObject* c = *iterc;
				if (!c) continue;
				if (LLSelectNode* n = LLSelectMgr::getInstance()->getSelection()->findNode(const_cast<LLViewerObject*>(c)))
				{
					if (!can_export_node(n)) continue;
				}
				else continue;
				const LLVolume* vol = c->getVolume();
				if (!vol) continue;

				LLXform v_form;
				v_form.setScale(c->getScale());
				v_form.setPosition(c->getRenderPosition());
				v_form.setRotation(c->getRenderRotation());

				LLXform normfix;
				normfix.setRotation(v_form.getRotation());

				if (c->isHUDAttachment())
				{
					v_form.addPosition(-offset);
					//Normals of HUD elements are funky
					//TO-DO: fix 'em!
				}
				Add(vol, &v_form, &normfix);
			}
		}
	}
}
namespace
{
	class LFSaveSelectedAvatar : public view_listener_t
	{
		bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)
		{
			if (const LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()))
			{
				if (!avatar->isSelf())
				{
					if (gSavedSettings.getBOOL("OBJExportNotifyFailed")) LLNotificationsUtil::add("ExportFailed");
					return true;
				}
				WavefrontSaver* wfsaver = new WavefrontSaver; // deleted in callback
				wfsaver->Add(avatar);
				if (wfsaver->obj_v.empty())
				{
					if (gSavedSettings.getBOOL("OBJExportNotifyFailed"))
						LLNotificationsUtil::add("ExportFailed");
					delete wfsaver;
					return true;
				}

				AIFilePicker* filepicker = AIFilePicker::create();
				filepicker->open(avatar->getFullname()+OBJ);
				filepicker->run(boost::bind(save_wavefront_continued, wfsaver, filepicker));
			}
			return true;
		}
	};
}

namespace
{
	void write_or_bust(LLFILE* fp, const std::string outstring)
	{
		const size_t size = outstring.length();
		if (fwrite(outstring.c_str(), 1, size, fp) != size)
			llwarns << "Short write" << llendl;
	}
}

bool WavefrontSaver::saveFile(LLFILE* fp)
{
	if (!fp) return false;

	int num = 0;
	int index = 0;
	for (std::vector<Wavefront>::iterator w_iter = obj_v.begin(); w_iter != obj_v.end(); ++w_iter)
	{
		int count = 0;

		std::string name = (*w_iter).name;
		if (name.empty()) name = llformat("%d", num++);

		vert_t vertices = (*w_iter).vertices;
		vec3_t normals = (*w_iter).normals;
		tri_t triangles = (*w_iter).triangles;

		//Write Object
		write_or_bust(fp, "o " + name + "\n");

		//Write vertices; swap axes if necessary
		static const LLCachedControl<bool> swapYZ("OBJExportSwapYZ", false);
		const double xm = swapYZ ? -1.0 : 1.0;
		const int y = swapYZ ? 2 : 1;
		const int z = swapYZ ? 1 : 2;
		for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter)
		{
			++count;
			const LLVector3 v = v_iter->first + offset;
			write_or_bust(fp, llformat("v %f %f %f\n",v[0] * xm, v[y], v[z]));
		}

		for (vec3_t::iterator n_iter = normals.begin(); n_iter != normals.end(); ++n_iter)
		{
			const LLVector3 n = *n_iter;
			write_or_bust(fp, llformat("vn %f %f %f\n",n[0] * xm, n[y], n[z]));
		}

		for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter)
		{
			write_or_bust(fp, llformat("vt %f %f\n", v_iter->second[0], v_iter->second[1]));
		}

		//Write triangles
		for (tri_t::iterator t_iter = triangles.begin(); t_iter != triangles.end(); ++t_iter)
		{
			const int f1 = t_iter->v0 + index + 1;
			const int f2 = t_iter->v1 + index + 1;
			const int f3 = t_iter->v2 + index + 1;
			write_or_bust(fp, llformat("f %d/%d/%d %d/%d/%d %d/%d/%d\n",
										  f1,f1,f1,f2,f2,f2,f3,f3,f3));
		}
		index += count;
	}

	return true;
}

void addMenu(view_listener_t* menu, const std::string& name);
void add_wave_listeners() // Called in llviewermenu with other addMenu calls, function linked against
{
	addMenu(new LFSaveSelectedObjects(), "Object.SaveAsOBJ");
	addMenu(new LFSaveSelectedAvatar(), "Avatar.SaveAsOBJ");
}

