/*
 * $Id: glwindow.cpp 4717 2017-09-27 12:08:17Z antonin.misek-skoda-auto.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 *
 * Purpose      :
 *     Long description of what the file is for.
 */


#include <wx/dcclient.h>
#include "kernel.h"
#include "glwindow.h"
#include "rendermanager.h"
#include "wx/display.h" 

using namespace VRUT;


GLWindow::GLWindow() : 
#if !(defined VRUT_HPC)
	wxGLCanvas((wxWindow *)NULL, wxID_ANY, (int *)NULL)
#else
	wxWindow((wxWindow *)NULL, wxID_ANY)
#endif
{
	module = (RenderModule *)NULL;
	renderManager = (RenderManager *)NULL;
	glContext = NULL;
	fsWindow = (wxWindow *)NULL;
	nbPageIndex = wxNOT_FOUND;
	beingReparented = 0;
	windowID = RENDER_WINDOW_ID_NONE;
	fsFrame = NULL;
	SetBackgroundColour(*wxBLACK);
}


GLWindow::GLWindow(wxWindow * parent, RenderModule * _module, RENDER_WINDOW_ID winID, wxPoint position, wxSize size, int * attribList, wxString title)
#if !(defined VRUT_HPC)
		: wxGLCanvas(parent, wxID_ANY, attribList, position, size, wxWANTS_CHARS, title)
#else
		: wxWindow(parent, wxID_ANY, position, size, 0, title)
#endif
{
	windowID = winID;
	module = _module;
	renderManager = (RenderManager *)NULL;
	glContext = NULL;
	fsWindow = (wxWindow *)NULL;
	nbPageIndex = wxNOT_FOUND;
	beingReparented = 0;
	SetBackgroundColour(*wxBLACK);

	Connect(wxID_ANY, wxEVT_PAINT, wxPaintEventHandler(GLWindow::onPaint), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_SIZE, wxSizeEventHandler(GLWindow::onResize), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_KEY_DOWN, wxKeyEventHandler(GLWindow::onKeyEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_KEY_UP, wxKeyEventHandler(GLWindow::onKeyEvent), (wxObject *)NULL, this);

	Connect(wxID_ANY, wxEVT_ENTER_WINDOW, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_LEFT_UP, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_LEFT_DCLICK, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_RIGHT_DOWN, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_RIGHT_UP, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_RIGHT_DCLICK, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_MIDDLE_DOWN, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_MIDDLE_UP, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_MIDDLE_DCLICK, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_MOTION, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);
	Connect(wxID_ANY, wxEVT_MOUSEWHEEL, wxMouseEventHandler(GLWindow::onMouseEvent), (wxObject *)NULL, this);

	fsFrame = new wxFrame(0, wxID_ANY, GetName(), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
	fsFrame->SetBackgroundColour(*wxBLACK);
	fsFrame->Connect(wxID_ANY, wxEVT_SIZE, wxSizeEventHandler(GLWindow::onResize), (wxObject *)NULL, this);
}


GLWindow::~GLWindow()
{
	///Let RenderManager know about our death
	if (renderManager)
		renderManager->DestroyGLWindow(this);
	SAFE_DELETE(glContext);
	if (!fsWindow)
	{
		//remove from NoteBook
		if (KERNEL && !KERNEL->IsExiting())
		{
			fsWindow = GetParent();
			wxAuiNotebook * renderNotebook = KERNEL->GetMainWindow()->GetRenderNotebook();
			nbPageIndex = renderNotebook->GetPageIndex(this);
			if (nbPageIndex != wxNOT_FOUND)
				renderNotebook->RemovePage(nbPageIndex);
		}
	}
	fsFrame->Destroy();
}

void GLWindow::ToggleFullscreen()
{
	SetFullscreen(fsWindow?0:3, -1, -1, -1, -1, 0);
}

/* SetFullscreen - the name should be something like setRenderingWindow
fsMode is bit mask: 1 - use extern window, 2 - fullscreen, 4 - always on top, 8 - hide mouse
	some combinations have not sense: fullscreen without extern window, always on top without extern window
positionX, positionY, sizeX, sizeY - define position and size of rendering window; the values are used only for extern window and they have also dependency on fullscreen
	example: HW configuration - two monitors 1920x1200, dual view
		SetFullscreen(1, 0, 0, 2500, 300, 0) - rendering window will start at upper left corner of left monitor and will be also partially on second monitor
		SetFullscreen(3, 0, 0, 2500, 300, 0) - rendering window will start at upper left corner of left monitor, the background of left monitor will be black, INVALID: the sizeX should be maximally to the right corner of left monitor (ie. 1920 in this case)
extWindowBgColorRGB - packed 8bit color (255 is red, 65280 is green, 16711680 is blue)
*/
void GLWindow::SetFullscreen(int fsMode, const int positionX, const int positionY, const int sizeX, const int sizeY, const unsigned long colorRGB)
{
	LOGDEBUG(wxString::Format(wxT("<GLWindow>SetFullscreen windowID %lu, fsMode %i, position (%i, %i), size (%i, %i), color %lu"), (unsigned long)windowID, fsMode, positionX, positionY, sizeX, sizeY, colorRGB));
	/*//check validity of input
	if ((fsMode&2) && !(fsMode&1))
	{
		LOGWARNING(wxT("GLWindow::SetFullscreen: fullscreen needs external window for renderer (corrected)"));
		fsMode|=1;
	}*/

	//set window basic parameters
	fsFrame->SetBackgroundColour(wxColour(colorRGB));
	ShowCursor(!(fsMode&8));

	//enable/disable external window
	if ((fsWindow == (wxWindow *)NULL) == (fsMode&1))
	{
		wxAuiNotebook * renderNotebook = KERNEL->GetMainWindow()->GetRenderNotebook();
		if (fsWindow) //disable fsWindow
		{
			wxWindow * fsParent = GetParent();
			Reparent(fsWindow);
			renderNotebook->InsertPage(nbPageIndex, this, GetName(), true, wxNullBitmap);
			fsFrame->ShowFullScreen(false);
			fsParent->Hide();
			fsWindow = (wxWindow *)NULL;
		}
		else //enable fsWindow
		{
			fsWindow = GetParent();
			nbPageIndex = renderNotebook->GetPageIndex(this);
			wxASSERT_MSG(nbPageIndex != wxNOT_FOUND, wxT("<GLWindow>Render window not found in notebook pages"));
			renderNotebook->RemovePage(nbPageIndex);
			Reparent(fsFrame);
		}
	}
	//set parameters for external window
	if (fsWindow)
	{
		fsFrame->Show(true);
		if (fsMode&2)
		{
			if ((sizeX>=0) && (sizeY>=0))
			{
				fsFrame->SetSize(positionX, positionY, sizeX, sizeY);
				fsFrame->ShowFullScreen(true);
				int displayID=wxDisplay::GetFromWindow(fsFrame);
				wxDisplay display(displayID);
				wxRect area = display.GetGeometry(); 
				fsFrame->SetSize(area);
				int pX, pY;
				fsFrame->GetPosition(&pX, &pY);
				this->SetSize(positionX-pX, positionY-pY, sizeX, sizeY);
				//LOG(wxString::Format(wxT("<GLWindow>SetFullscreen glwindow position (%lu, %lu), size (%lu, %lu)"), positionX-pX, positionY-pY, sizeX, sizeY));
			}
			else
			{
				fsFrame->ShowFullScreen(true);
				int displayID=wxDisplay::GetFromWindow(fsFrame);
				wxDisplay display(displayID);
				wxRect area = display.GetGeometry(); 
				fsFrame->SetSize(area);
				int sX, sY;
				fsFrame->GetSize(&sX, &sY);
				this->SetSize(0, 0, sX, sY);
				//LOG(wxString::Format(wxT("<GLWindow>SetFullscreen glwindow size (%lu, %lu)"), sX, sY));
			}
		}
		else
		{
			fsFrame->ShowFullScreen(false);
			if ((sizeX>=0) && (sizeY>=0))
			{
				fsFrame->SetSize(positionX, positionY, sizeX, sizeY/*, wxSIZE_ALLOW_MINUS_ONE*/);
				this->SetSize(0, 0, sizeX, sizeY);
				//LOG(wxString::Format(wxT("<GLWindow>SetFullscreen glwindow size (%lu, %lu) (fullscreen)"), sizeX, sizeY));
			}
		}

		//set STAY_ON_TOP
		if (fsMode&4)
			fsFrame->SetWindowStyle(wxBORDER_NONE | wxSTAY_ON_TOP);
		else
			fsFrame->SetWindowStyle(wxBORDER_NONE);

	}
	//set resting parameters
	fsFrame->Refresh(true);
	SetFocus();
	KERNEL->GetMainWindow()->GetAUIManager()->Update();
}

void GLWindow::onPaint(wxPaintEvent & WXUNUSED(evt))
{
	wxPaintDC(this);
	if (module)
		module->PostEvent(GET_EVT_RENDER_UPDATE(GetID())); //Handle redraws when the window was covered by another window and the window content is revealed again (there would be a gray area left otherwise)
}

void GLWindow::onResize(wxSizeEvent & evt)
{
	int w = 0, h = 0;
	GetClientSize(&w, &h);
	if (module)
	{
		if (module->GetCameraID() != NODE_ID_NONE)
		{
			smart_pointer_v2<Scene> scene;
			KERNEL->sceneManager->GetScene(scene, module->GetSceneID());
			if (scene)
			{
				NODE_ID camID = module->GetCameraID();
				//LOG(wxString::Format(wxT("GLWindow::onResize before SetProjectionParams camID=%lu, scene=%lu, w=%i, h=%i"), (unsigned long)camID, (unsigned long)KERNEL->sceneManager->GetScene(module->GetSceneID()), w, h));
				scene->SetProjectionParams(camID, w, h);
			}
		}

		module->PostEvent(GET_EVT_RENDER_WIN_RESIZED(GetID()));
		//LOG(wxString::Format(wxT("GLWindow::onResize camID=%lu, scene=%lu, w=%i, h=%i"), (unsigned long)module->GetCameraID(), (unsigned long)KERNEL->sceneManager->GetScene(module->GetSceneID()), w, h));
	}
	//else
	//	LOG(wxString::Format(wxT("GLWindow::onResize module=NULL, w=%i, h=%i"), w, h));
	///Fix size if sent explicitly
	evt.m_size = GetClientSize();
	evt.Skip();
}


void GLWindow::onKeyEvent(wxKeyEvent & evt)
{
	if (!module || !KERNEL || KERNEL->IsExiting())
		return;

	evt.SetId(int(windowID));
	KERNEL->GetMessageSink()->ProcessEvent(evt);
	evt.Skip();

#ifdef DEBUG_EVENTS
	LOG(wxT("<GLWindow>key event"));
#endif
}


void GLWindow::onMouseEvent(wxMouseEvent & evt)
{
	if (module && KERNEL && !KERNEL->IsExiting())
	{
		evt.SetId(int(windowID));
		KERNEL->GetMessageSink()->ProcessEvent(evt);
	}
	evt.Skip();
}


void GLWindow::Deinitialize(NODE_ID protectedCameraID)
{
	KERNEL->GetMessageSink()->PostEvent(GET_EVT_RENDER_WIN_CLOSED(windowID), true);
	///Deinitialize render module and assert there will be no further attempt to render scene
	if (module && glContext)
	{
		smart_pointer_v2<Scene> scene;
		KERNEL->sceneManager->GetScene(scene, module->GetSceneID());
		if (scene) {
			smart_pointer<SceneNode> cam(NULL);
			scene->GetNode(cam, module->GetCameraID());
			if (cam && module->GetCameraID() != protectedCameraID) {
				///Camera is bound to render window and no other renderer uses it, so it should be released now
				scene->Remove(module->GetCameraID());
			}
		}
		module->Deinitialize();

		/*///Emulate \c EVT_PARAM_SET to set variable in module right away
		Parameter::ParameterIdentifier camPI(module->GetName(), wxT("cameraID"), module->GetID());
		module->PostEvent(GET_EVT_PARAM_SET(camPI, wxString::Format(wxT("%i"), NODE_ID_NONE)), true);
		Parameter::ParameterIdentifier scenePI(module->GetName(), wxT("sceneID"), module->GetID());
		module->PostEvent(GET_EVT_PARAM_SET(scenePI, wxString::Format(wxT("%i"), SCENE_ID_NONE)), true);*/

		module = (RenderModule *)NULL;
	}
}

#if !(defined VRUT_HPC)
#if wxCHECK_VERSION(3, 1, 0)
bool GLWindow::CreateGLContext(const wxGLContext * sharedContext, wxGLContextAttrs* attributes)
{
	SAFE_DELETE(glContext);
	glContext = new wxGLContext(this, sharedContext, attributes);
	return glContext->IsOK();
}
#else
bool GLWindow::CreateGLContext(const wxGLContext * sharedContext, const int *attributes)
{
	SAFE_DELETE(glContext);
	if (!this->Create(this->GetParent(), this->GetId(), this->GetPosition(), this->GetSize(), this->GetWindowStyle(), this->GetName(), attributes))
		LOGERROR(wxT("GLWindow::CreateGLContext: Create failed"));
	glContext = new wxGLContext(this, sharedContext);
	return true;
}
#endif
#endif

void GLWindow::Focus()
{
	if (!fsWindow)
	{
		wxAuiNotebook * renderNotebook = KERNEL->GetMainWindow()->GetRenderNotebook();
		renderNotebook->SetSelection(renderNotebook->GetPageIndex(this));
	}
	SetFocus();
	beingReparented = 10;
	KERNEL->GetMainWindow()->GetAUIManager()->Update();
}
