#include "draganddrop.h"
#include "rendermanager.h"
#include "scene.h"

using namespace VRUT;


// Register with wxWidgets' dynamic class subsystem.
IMPLEMENT_DYNAMIC_CLASS(DropSourceControl, wxWindow)
IMPLEMENT_DYNAMIC_CLASS(DropSourceControlXmlHandler, wxXmlResourceHandler)

bool DropSourceControl::Create(wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size, long style, const wxString & name)
{
	if (!wxWindow::Create(parent, id, pos, size, style, name))
		return false;

	LocalInit();
	return true;
}

void DropSourceControl::LocalInit(const wxString &textLabel, const wxBitmap &bitmapLabel, wxDataObjectSimple *newData)
{
	if (!bitmapLabel.IsNull())
		bitmap = new wxStaticBitmap(this, wxID_ANY, bitmapLabel);
	else
		bitmap = new wxStaticBitmap(this, wxID_ANY, wxArtProvider::GetBitmap(wxART_FOLDER_OPEN));

	wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);

	label = new wxStaticText(this, wxID_ANY, textLabel);
	sizer->Add(bitmap, 0, wxALIGN_CENTER_VERTICAL);
	sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL);

	this->SetSizerAndFit(sizer);

	bitmap->Connect(wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler(DropSourceControl::OnLeftDown), NULL, this);

	data = NULL;
	if (newData)
		SetData(CopyData(newData));
}

wxDragResult DropTarget::OnDragOver(wxCoord x, wxCoord y, wxDragResult def)
{
#ifdef __WXMSW__ //Windows - I don't understand why it not works on Linux...
	if (!GetData())
		return wxDragNone;
#endif

	wxDataObjectComposite *dataobjComp = static_cast<wxDataObjectComposite *>(GetDataObject());

	wxDataFormat format = dataobjComp->GetReceivedFormat();
#if wxCHECK_VERSION(2, 9, 0)
	wxDataObject *dataobj = dataobjComp->GetObject(format);
#endif
	switch (format.GetType())
	{
	case wxDF_FILENAME:
		break;
	default:
		if (format.GetId() == wxT("VRUT_Material"))
		{
#if wxCHECK_VERSION(2, 9, 0)
			MaterialDataObject *mdo = static_cast<MaterialDataObject *>(dataobj);
#else
			MaterialDataObject *mdo = (MaterialDataObject *)m_dataObject;
#endif
			if (mdo->GetSceneId() == SCENE_ID_NONE)
				return wxDragNone;

			GLWindow *dst_window = dynamic_cast<GLWindow *>(target_item);

			NODE_ID camera_id = dst_window->GetRenderModule()->GetCameraID();
			NODE_ID scene_id = dst_window->GetRenderModule()->GetSceneID();

			smart_pointer_v2<Scene> dst_scene;
			KERNEL->sceneManager->GetScene(dst_scene, scene_id);
			if (!dst_scene)
				return wxDragNone;
			smart_pointer<Camera> camera;
			dst_scene->GetNode(camera, camera_id);

            int scrW, scrH;
            dst_window->GetClientSize(&scrW, &scrH);
			NODE_ID node_id = selectNodeFromScreen(dst_scene, camera, scrW, scrH, x, y);

			if (node_id == NODE_ID_NONE)
				return wxDragNone;
		}
		else
			if (format.GetId() == wxT("VRUT_SceneNodes")) {
				// no checks done here...
			}
			else
				return wxDragNone; // not a supported target

		break;
	}

	return def;
}

wxDragResult DropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
{
	if (!GetData())
		return wxDragNone;

	wxDataObjectComposite *dataobjComp = static_cast<wxDataObjectComposite *>(GetDataObject());

	wxDataFormat format = dataobjComp->GetReceivedFormat();
#if wxCHECK_VERSION(2, 9, 0)
	wxDataObject *dataobj = dataobjComp->GetObject(format);
#endif
	switch (format.GetType())
	{
	case wxDF_FILENAME:
	{
		//OnDropURL(x, y, ((wxFileDataObject *)m_dataObject)->GetURL());
		if (m_dataObject->IsSupported(wxDF_FILENAME))
		{
#if wxCHECK_VERSION(2, 9, 0)
			wxFileDataObject *fdo = static_cast<wxFileDataObject *>(dataobj);
			wxArrayString fileNames = fdo->GetFilenames();
#else
			wxArrayString fileNames = ((wxFileDataObject *)m_dataObject)->GetFilenames();
#endif

#ifdef __WXMSW__ //Windows
			if (def == wxDragMove)
#else //Linux
			if (def == wxDragCopy)
#endif
			{
				for (auto &fileName : fileNames)
				{
					SCENE_ID sceneID = KERNEL->sceneManager->GetNewSceneID();
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_IO_SCENE_IMPORT(fileName, sceneID));
					if (render)
						KERNEL->GetMessageSink()->PostEvent(GET_EVT_RENDER_SCENE(sceneID));
				}
			}
			else //wxDragCopy
			{
				GLWindow *glw = NULL;
				if (isGLWindow)
					glw = (GLWindow*)target_item;
				else
					glw = KERNEL->renderManager->GetActiveGLWindow();
				if (glw)
				{
					SCENE_ID sceneID = glw->GetRenderModule()->GetSceneID();
					smart_pointer_v2<Scene> scene;
					KERNEL->sceneManager->GetScene(scene, sceneID);
					NODE_ID rootID = (scene) ? scene->GetRootID() : NODE_ID_NONE;
					for (auto &fileName : fileNames)
						KERNEL->GetMessageSink()->PostEvent(GET_EVT_IO_SCENE_IMPORT(fileName, sceneID, rootID));
				}
			}
		}
		break;
	}
	default:
		if (format.GetId() == wxT("VRUT_Material"))
		{
			GLWindow *dst_window = dynamic_cast<GLWindow *>(target_item);

			NODE_ID camera_id = dst_window->GetRenderModule()->GetCameraID();
			NODE_ID scene_id = dst_window->GetRenderModule()->GetSceneID();

			smart_pointer_v2<Scene> dst_scene;
			KERNEL->sceneManager->GetScene(dst_scene, scene_id);
			if (!dst_scene)
				return wxDragNone;

			smart_pointer<Camera> camera;
			dst_scene->GetNode(camera, camera_id);

            int scrW, scrH;
            dst_window->GetClientSize(&scrW, &scrH);
			NODE_ID node_id = selectNodeFromScreen(dst_scene, camera, scrW, scrH, x, y);

			if (node_id != NODE_ID_NONE)
			{
#if wxCHECK_VERSION(2, 9, 0)
				MaterialDataObject *mdo = static_cast<MaterialDataObject *>(dataobj);
#else
				MaterialDataObject *mdo = (MaterialDataObject *)m_dataObject;
#endif
				setMaterialFromIDs(mdo->GetMaterialId(), mdo->GetSceneId(), node_id, dst_scene, def);
			}
		}
		else if (format.GetId() == wxT("VRUT_SceneNodes"))
			LOG("Drop Scene Nodes not supported for this type of drop target!");
		break;
	}
	return def;
}

void DropTarget::setMaterialFromIDs(MATERIAL_ID material_id, SCENE_ID scene_id, NODE_ID dst_node, smart_pointer_v2<Scene> &dst_scene, wxDragResult def)
{
	if ((material_id != MATERIAL_ID_NONE) && (scene_id != SCENE_ID_NONE))
	{
		smart_pointer_v2<Scene> srcScene;
		KERNEL->sceneManager->GetScene(srcScene, scene_id);
		if (!srcScene)
			return;
		smart_pointer<Material> mat;
		srcScene->GetMaterial(mat, material_id);
		if (!mat)
			return;
		Material *new_mat = mat->Clone();

		if (def == wxDragCopy)
			generateMaterialName(new_mat->name, dst_scene);

		MATERIAL_ID new_material_id = dst_scene->AddMaterial(new_mat);
		dst_scene->SetMaterial(dst_node, new_material_id);
	}
}

NODE_ID DropTarget::selectNodeFromScreen(smart_pointer_v2<Scene> &dst_scene, smart_pointer<Camera> &camera, int scrW, int scrH, int x, int y)
{
    const float aspectRatio = float(scrW) / float(scrH);
	float px = (((2.0f * x) / scrW) - 1.0f) / dst_scene->GetProjectionMatrix(camera->GetID(), aspectRatio, false)._m11;
	float py = (((-2.0f * y) / scrH) + 1.0f) / dst_scene->GetProjectionMatrix(camera->GetID(), aspectRatio, false)._m22;

	Vector3 origin(0, 0, 0);
	Vector3 direction(px, py, -1.0f);

	Matrix camWorldMat(dst_scene->GetWorldTransMatrix(camera->GetID()));
	origin = camWorldMat.TransformCoord(origin);
	direction = camWorldMat.TransformNormal(direction).Normalize();
	Ray ray(origin, direction);
	RayIntersectionInfo info;
	if (dst_scene->CastRay(ray, info))
		return info.nodeID;

	return NODE_ID_NONE;
}

void DropTarget::generateMaterialName(wxString &name, smart_pointer_v2<Scene> &scene)
{
	int attempt_counter = 1;
	wxString result_name;

	int substr_index = name.Find(wxT(" copy"));

	if (substr_index != wxNOT_FOUND)
		name = name.SubString(0, substr_index - 1);

	result_name = name;

	while (scene->GetMaterialID(result_name) != MATERIAL_ID_NONE)
	{
		if (attempt_counter == 1)
			result_name = name + wxT(" copy");
		else
			result_name = name + wxString::Format(wxT(" copy(%i)"), attempt_counter);
		attempt_counter++;
	}

	name = result_name;
}
