#include "StdAfx.h"
#include "SceneMain.h"

#include <LibGeo/Path.h>

#include <C2/graph/MaterialSamples.h>
#include <C2/gl/GlApiExt.h>

#include <C2/util/string_util.h>

#include <QFileInfo>

#include <sstream>
#include <map>

#include <boost/algorithm/string.hpp>

#include "Format/GeomReaderBuilder.h"



SceneMain::SceneMain(void)
{
	m_CurFrame = 0;
	m_ConstantColorStrength = 1.0f;
}

void SceneMain::Initialize(void)
{
	m_DefaultMaterial = lib_graph::MaterialSamples::GetSilver();

	m_DefaultAlphaMaterial = m_DefaultMaterial;
	m_DefaultAlphaMaterial.SetAlphaAll( 0.75f );

	m_DefaultTexture.InitializeTexture();

	ClearObjects();

	m_EnvImg.InitEnvMap();
}

void SceneMain::FinalizeScene(void)
{
	m_DefaultTexture.FinalizeTexture();

	m_Observers.clear();

	m_EnvImg.ClearEnv();
}

void SceneMain::ClearObjects(void)
{
	for (GeomObject& obj : m_Objects)
	{
		obj.ClearObject();
	}

	m_Objects.clear();
	m_Cursor3d.ResetCloseFace();

	ReportDoneEditGeometry();
}


void SceneMain::ImportFileAutoFmt(const QString& path)
{
	geom::GeomFileFormat fmt = FormatType::GetGeomFileTypeFromPath(path);
	if (fmt == GeomFileFormat::None)
		throw FileLoadErrorException("Unknown format");

	std::string f = path.toLocal8Bit().data();
	if (!ImportFile(f, fmt))
		throw FileLoadErrorException("Failed Load Geometry");
}

bool SceneMain::ImportFile(const std::string& filename, geom::GeomFileFormat fmt)
{
	std::auto_ptr<GeomFileReader> reader;
	reader.reset(GeomReaderBuilder::Create(fmt));
	if (reader.get() == NULL)
	{
		assert(false);
		return false;
	}

	RefreshObjectIndex();

	return reader->Load(*this, filename);
}

GeomObject* SceneMain::CreateNewGeometry(void)
{
	GeomObject* geom = new GeomObject();
	m_Objects.push_back(geom);
	RefreshObjectIndex();

	return geom;
}


void SceneMain::SetFrame(int frame)
{
	m_CurFrame = frame;
	for (GeomObject& g : m_Objects)
	{
		g.SetFrame(frame);
	}

	UpdateTransform();
}


void SceneMain::UpdateTransform(void)
{
	SceneTransform& t = m_WorldTransform;

	t.SetScale(1.0);
	t.m_Translate.set_zero();

	if (m_Config.m_EnableAutoCentering)
	{
		lm::range3f r = GetSceneBBox();
		if (r.is_valid())
			t.m_Translate = -r.mid_point();
	}

	if (m_Config.m_EnableAutoReisze)
	{
		lm::range3f r = GetSceneBBoxIni();
		if (r.is_valid())
			t.SetScale(1.0f / r.max_length());
	}
}


void SceneMain::LoadDefaultMatTexture(const std::string& filename)
{
	m_DefaultTexture.LoadTextureFromFile(filename.c_str(), true);
	m_DefaultTexture.SetTextureGLAndReleaseImage(m_TexConfig);
}


//! オブサーバをセット.
//! 所有権は持たない.
void SceneMain::AddObserver(SceneObserver* observer)
{
	m_Observers.push_back(observer);
}

bool SceneMain::RemoveObserver(size_t idx)
{
	assert(idx < m_Observers.size());
	if (idx < m_Observers.size())
		return false;

	std::vector<SceneObserver*>::iterator i = m_Observers.begin();
	std::advance(i, idx);
	m_Observers.erase(i);

	return true;
}

bool SceneMain::RemoveObserver(SceneObserver* registered_observer)
{
	for (size_t i = 0; i < m_Observers.size(); ++i)
	{
		if (m_Observers[i] == registered_observer)
			return RemoveObserver(i);
	}

	assert(false);
	return false;
}


void SceneMain::ReportDoneEditGeometry(void)
{
	for (SceneObserver* o : m_Observers)
	{
		o->OnGeometryBuild(*this);
	}
}

lm::range3f SceneMain::GetSceneBBox(void) const
{
	lm::range3f bbox;
	bbox.clear();

	for (const GeomObject& obj : m_Objects)
	{
		bbox.expand(obj.GetGeomBBox(false));
	}

	return bbox;
}

lm::range3f SceneMain::GetSceneBBoxIni(void) const
{
	lm::range3f bbox;
	bbox.clear();

	for (const GeomObject& obj : m_Objects)
	{
		bbox.expand(obj.GetGeomBBox(true));
	}

	return bbox;
}


lm::range3f SceneMain::GetSceneTransformedBBox(void) const
{
	lm::range3f bbox = GetSceneBBox();

	lm::range3f tbb;
	tbb.clear();
	tbb.expand(m_WorldTransform.TransformVec(bbox.min_point()));
	tbb.expand(m_WorldTransform.TransformVec(bbox.max_point()));

	return tbb;
}


GeomObject* SceneMain::GetPrimaryObject(void)
{
	int idx = GetPrimaryObjectIdx();
	if (idx == -1)
		return NULL;

	return &m_Objects[idx];
}

const GeomObject* SceneMain::GetPrimaryObject(void) const
{
	int idx = GetPrimaryObjectIdx();
	if (idx == -1)
		return NULL;

	return &m_Objects[idx];
}

int SceneMain::GetPrimaryObjectIdx(void) const
{
	if (m_Objects.empty())
		return -1;

	return m_Sels.GetSelObjectIdx();
}


void SceneMain::GetSelectedMeshes(std::vector<MeshBuf*>& meshes)
{
	GeomObject* obj = GetPrimaryObject();
	if (obj == NULL)
		return;

	if (m_Sels.IsMBufSelected())
	{
		meshes.resize(1, &obj->m_MeshAry[m_Sels.GetMBufIdx()]);
	}
	else
	{
		meshes.reserve(obj->m_MeshAry.size());
		for (MeshBuf& m : obj->m_MeshAry)
		{
			meshes.push_back(&m);
		}
	}
}

MeshBuf* SceneMain::GetPrimaryMeshbuf(void)
{
	int idx = GetPrimaryMeshbufIdx();
	if (idx == -1)
		return NULL;

	return &GetPrimaryObject()->m_MeshAry[idx];
}

const MeshBuf* SceneMain::GetPrimaryMeshbuf(void) const
{
	int idx = GetPrimaryMeshbufIdx();
	if (idx == -1)
		return NULL;

	return &GetPrimaryObject()->m_MeshAry[idx];
}

int SceneMain::GetPrimaryMeshbufIdx(void) const
{
	if (GetPrimaryObject() == NULL)
		return -1;

	if (!m_Sels.IsMBufSelected())
		return -1;

	return m_Sels.GetMBufIdx();
}

MeshBuf* SceneMain::GetSelOrFirstMeshbuf(void)
{
	GeomObject* o = GetPrimaryObject();
	if (o == NULL)
		return NULL;
	if (o->m_MeshAry.empty())
		return NULL;

	if (m_Sels.IsMBufSelected())
		return &o->m_MeshAry[m_Sels.GetMBufIdx()];

	return &o->m_MeshAry[0];
}

const MeshBuf* SceneMain::GetSelOrFirstMeshbuf(void) const
{
	const GeomObject* o = GetPrimaryObject();
	if (o == NULL)
		return NULL;
	if (o->m_MeshAry.empty())
		return NULL;

	if (m_Sels.IsMBufSelected())
		return &o->m_MeshAry[m_Sels.GetMBufIdx()];

	return &o->m_MeshAry[0];
}

GeomTextureSet* SceneMain::GetSelectedTexture(void)
{
	MeshBuf* mbuf = GetSelOrFirstMeshbuf();
	if (mbuf == NULL)
		return NULL;

	return mbuf->GeSeltTexture();
}

lib_geo::BaseMaterial* SceneMain::GetSelectedMaterial(void)
{
	MeshBuf* mbuf = GetSelOrFirstMeshbuf();
	if (mbuf == NULL)
		return NULL;

	return mbuf->GetSelMaterial();
}


void SceneMain::RemoveItem(int sel_idx)
{
	boost::ptr_vector<GeomObject>::iterator i;
	i = m_Objects.begin();
	i += sel_idx;

	if (i->IsInChild(m_Cursor3d.CloseFaceMBuf))
		m_Cursor3d.ResetCloseFace();

	m_Objects.erase(i);

	m_Sels.ClearSelect();

	RefreshObjectIndex();
}


void SceneMain::CreateSampleTexture(SampleTextureBuilder::TextureType tex_type)
{
	SDL_Surface* tex = SampleTextureBuilder::CreateTexture(tex_type, 256);
	if(tex == NULL)
		return;

	m_DefaultTexture.SetFromSDLSurface( tex );
	m_DefaultTexture.SetTextureGLAndReleaseImage(m_TexConfig);
}


void SceneMain::RefreshObjectIndex(void)
{
	size_t io = 0;
	for (size_t i = 0; i < m_Objects.size(); ++i)
	{
		GeomObject& o = m_Objects[i];
		o.SetObjectIndex((int)i);
		for (size_t j = 0; j < o.m_MeshAry.size(); ++j)
		{
			MeshBuf& m = o.m_MeshAry[j];
			m.m_MBufIdx = (int)j;
			m.m_WholeIndex = (int)(j + io);
		}
		io += o.m_MeshAry.size();
	}
}


bool SceneMain::ReloadObject(GeomObject* obj)
{
	if(!obj->IsFileObject())
		return false;

	int idx = FindObjectIdx(obj);
	if (idx == -1)
	{
		assert(false);
		return false;
	}

	geom::GeomFileFormat fmt = obj->m_FileFormat;
	const std::string filename = obj->m_FilePath;

	RemoveItem(idx);

	return ImportFile(filename, fmt);
}

int SceneMain::FindObjectIdx(const GeomObject* obj) const
{
	for (const GeomObject& o : m_Objects)
	{
		if(&o == obj)
			return o.GetObjectIndex();
	}

	return -1;
}


void SceneMain::ShowAllObject(void)
{
	for (GeomObject& o : m_Objects)
	{
		o.m_Visible = true;
		for (MeshBuf& m : o.m_MeshAry)
			m.m_Visible = true;
	}
}

void SceneMain::HideAllObject(void)
{
	for (GeomObject& o : m_Objects)
	{
		o.m_Visible = false;
		for (MeshBuf& m : o.m_MeshAry)
			m.m_Visible = false;
	}
}

void SceneMain::ClearAllVertSelect(void)
{
	for (GeomObject& obj : m_Objects)
	{
		for (MeshBuf& mbuf : obj.m_MeshAry)
		{
			mbuf.ClearSelect();
		}
	}
}


void SceneMain::AddCrossSectionRecord()
{
	UpdateCrossSectionIfRequire(true);

	for (GeomObject& obj : m_Objects)
	{
		for (MeshBuf& mbuf : obj.m_MeshAry)
		{
			mbuf.m_CrossSectionLog.push_back(lib_geo::CrossSection());
			std::swap(mbuf.m_CrossSectionLog.back(), mbuf.m_CrossSection);
		}
	}
}

void SceneMain::ClearCrossSectionRecord()
{
	for (GeomObject& obj : m_Objects)
	{
		for (MeshBuf& mbuf : obj.m_MeshAry)
		{
			mbuf.m_CrossSectionLog.clear();
		}
	}
}

void SceneMain::GetFreeCutParam(lm::vec3f& p, lm::vec3f& n)
{
	Cursor3D& c = m_Cursor3d;
	p = c.CursorPos;
	n = c.MeasureNorm;
}

void SceneMain::UpdateCrossSectionIfRequire(bool force_update)
{
	const CrossSectionConfig& conf = m_CrossSectionConfig;

	if (!force_update)
	{
		if (!conf.IsRequireUpdateCS())
			return;
	}

	lib_geo::Plane cutplane;

	if (conf.IsFreeCut())
		GetFreeCutParam(cutplane.origin, cutplane.normal);
	else
		conf.GetCutPlane(GetSceneBBox(), cutplane);

	bool cs_group = conf.m_EnableCrossSectionGroup;

	for (GeomObject& obj : m_Objects)
	{
		for (MeshBuf& mbuf : obj.m_MeshAry)
		{
			mbuf.UpdateCrossSection(cs_group, cutplane);
		}
	}
}

std::vector<MeshBuf*> SceneMain::GetCurSelMeshes(void)
{
	MeshBuf* mbuf = GetPrimaryMeshbuf();
	if (mbuf != NULL)
		std::vector<MeshBuf*>(1, mbuf);

	GeomObject* geo = GetPrimaryObject();
	std::vector<MeshBuf*> mv;
	if (geo != NULL)
	{
		for (MeshBuf& m : geo->m_MeshAry)
		{
			mv.push_back(&m);
		}
	}

	return mv;
}

void SceneMain::FlipCurselFace(bool normal_only)
{
	std::vector<MeshBuf*> mv = GetCurSelMeshes();
	for (MeshBuf* m : mv)
	{
		m->FlipFace(normal_only);
	}
}

void SceneMain::RebuildCurselNormal(void)
{
	GeomObject* geo = GetPrimaryObject();
	if (geo == NULL)
		return;

	geo->ResetSmoothNormal();
}

int SceneMain::GetKeyframeMax(void) const
{
	int kf = 0;
	for (const GeomObject& obj : m_Objects)
	{
		kf = (std::max)(kf, obj.m_Nodes.GetKeyframeMax());
	}

	return kf;
}

float SceneMain::GetCursorSphereClipLen(void) const
{
	return GetCursorSphereClipLen(GetSceneBBox());
}

float SceneMain::GetCursorSphereClipLen(const lm::range3f& scene_bb) const
{
	return m_Cursor3d.GetSphereClipLen(scene_bb);
}

bool SceneMain::IsOutOfSphereClip(const lm::vec3f& v) const
{
	return IsOutOfSphereClip(GetSceneBBox(), v);
}

bool SceneMain::IsOutOfSphereClip(const lm::range3f& scene_bb, const lm::vec3f& v) const
{
	float l = GetCursorSphereClipLen(scene_bb);
	float lsq = l * l;
	float dsq = (v - m_Cursor3d.CursorPos).square_length();

	if (m_Cursor3d.SphereClip == SphereClipType::Hole)
		return (lsq > dsq);
	if (m_Cursor3d.SphereClip == SphereClipType::ShowAround)
		return (lsq < dsq);

	return false;
}
