#include "stdafx.h"

#include "ScenarioEditor.h"

#include "wx/glcanvas.h"
#include "wx/evtloop.h"
#include "wx/tooltip.h"
#include "wx/image.h"

#include "General/AtlasEventLoop.h"

#include "SnapSplitterWindow/SnapSplitterWindow.h"
#include "HighResTimer/HighResTimer.h"
#include "Buttons/ToolButton.h"

#include "GameInterface/MessagePasser.h"
#include "GameInterface/Messages.h"

#include "tools/Common/Tools.h"

// #define UI_ONLY

static HighResTimer g_Timer;

//////////////////////////////////////////////////////////////////////////

// TODO: move into another file
class Canvas : public wxGLCanvas
{
public:
	Canvas(wxWindow* parent, int* attribList)
		: wxGLCanvas(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS, _T("GLCanvas"), attribList),
		m_SuppressResize(true),
		m_MouseState(NONE), m_LastMouseState(NONE), m_MouseCaptured(false),
		m_LastMousePos(-1, -1)
	{
	}

	void OnResize(wxSizeEvent&)
	{
#ifndef UI_ONLY
		// Be careful not to send 'resize' messages to the game before we've
		// told it that this canvas exists
		if (! m_SuppressResize)
			POST_MESSAGE(ResizeScreen, (GetClientSize().GetWidth(), GetClientSize().GetHeight()));
			// TODO: fix flashing
#endif // UI_ONLY
	}

	void InitSize()
	{
		m_SuppressResize = false;
		SetSize(320, 240);
	}

	bool KeyScroll(wxKeyEvent& evt, bool enable)
	{
#ifndef UI_ONLY
		int dir;
		switch (evt.GetKeyCode())
		{
		case WXK_LEFT:  dir = AtlasMessage::eScrollConstantDir::LEFT; break;
		case WXK_RIGHT: dir = AtlasMessage::eScrollConstantDir::RIGHT; break;
		case WXK_UP:    dir = AtlasMessage::eScrollConstantDir::FORWARDS; break;
		case WXK_DOWN:  dir = AtlasMessage::eScrollConstantDir::BACKWARDS; break;
		case WXK_SHIFT: case WXK_CONTROL: dir = -1; break;
		default: return false;
		}

		float speed = 120.f;
		if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL))
			speed /= 64.f;
		else if (wxGetKeyState(WXK_CONTROL))
			speed /= 4.f;
		else if (wxGetKeyState(WXK_SHIFT))
			speed *= 4.f;

		if (dir == -1) // changed modifier keys - update all currently-scrolling directions
		{
			if (wxGetKeyState(WXK_LEFT))  POST_MESSAGE(ScrollConstant, (AtlasMessage::eScrollConstantDir::LEFT, speed));
			if (wxGetKeyState(WXK_RIGHT)) POST_MESSAGE(ScrollConstant, (AtlasMessage::eScrollConstantDir::RIGHT, speed));
			if (wxGetKeyState(WXK_UP))    POST_MESSAGE(ScrollConstant, (AtlasMessage::eScrollConstantDir::FORWARDS, speed));
			if (wxGetKeyState(WXK_DOWN))  POST_MESSAGE(ScrollConstant, (AtlasMessage::eScrollConstantDir::BACKWARDS, speed));
		}
		else
		{
			POST_MESSAGE(ScrollConstant, (dir, enable ? speed : 0.0f));
		}
#endif // UI_ONLY
		return true;
	}

	void OnKeyDown(wxKeyEvent& evt)
	{
		if (GetCurrentTool().OnKey(evt, ITool::KEY_DOWN))
		{
			// Key event has been handled by the tool, so don't try
			// to use it for camera motion too
			return;
		}

		if (KeyScroll(evt, true))
			return;

		evt.Skip();
	}

	void OnKeyUp(wxKeyEvent& evt)
	{
		if (GetCurrentTool().OnKey(evt, ITool::KEY_UP))
			return;

		if (KeyScroll(evt, false))
			return;

		evt.Skip();
	}

	void OnChar(wxKeyEvent& evt)
	{
		if (GetCurrentTool().OnKey(evt, ITool::KEY_CHAR))
			return;

		int dir = 0;
		if (evt.GetKeyCode() == '-' || evt.GetKeyCode() == '_')
			dir = -1;
		else if (evt.GetKeyCode() == '+' || evt.GetKeyCode() == '=')
			dir = +1;
		// TODO: internationalisation (-/_ and +/= don't always share a key)

		if (dir)
		{
			float speed = 16.f;
			if (wxGetKeyState(WXK_SHIFT))
				speed *= 4.f;
			POST_MESSAGE(SmoothZoom, (speed*dir));
		}
		else
			evt.Skip();

	}

	void OnMouseCapture(wxMouseCaptureChangedEvent& WXUNUSED(evt))
	{
		if (m_MouseCaptured)
		{
			// unexpected loss of capture (i.e. not through ReleaseMouse)
			m_MouseCaptured = false;
			
			// (Note that this can be made to happen easily, by alt-tabbing away,
			// and mouse events will be missed; so it is never guaranteed that e.g.
			// two LeftDown events will be separated by a LeftUp.)
		}
	}

	void OnMouse(wxMouseEvent& evt)
	{
		// Capture on button-down, so we can respond even when the mouse
		// moves off the window
		if (!m_MouseCaptured && evt.ButtonDown())
		{
			m_MouseCaptured = true;
			CaptureMouse();
		}
		// Un-capture when all buttons are up
		else if (m_MouseCaptured && evt.ButtonUp() &&
			! (evt.ButtonIsDown(wxMOUSE_BTN_LEFT) || evt.ButtonIsDown(wxMOUSE_BTN_MIDDLE) || evt.ButtonIsDown(wxMOUSE_BTN_RIGHT))
			)
		{
			m_MouseCaptured = false;
			ReleaseMouse();
		}

		// Set focus when clicking
		if (evt.ButtonDown())
			SetFocus();

		// TODO or at least to think about: When using other controls in the
		// editor, it's annoying that keyboard/scrollwheel no longer navigate
		// around the world until you click on it.
		// Setting focus back whenever the mouse moves over the GL window
		// feels like a fairly natural solution to me, since I can use
		// e.g. brush-editing controls normally, and then move the mouse to
		// see the brush outline and magically get given back full control
		// of the camera.
		if (evt.Moving())
			SetFocus();

		// Reject motion events if the mouse has not actually moved
		if (evt.Moving() || evt.Dragging())
		{
			if (m_LastMousePos == evt.GetPosition())
				return;
			m_LastMousePos = evt.GetPosition();
		}

#ifndef UI_ONLY

		if (GetCurrentTool().OnMouse(evt))
		{
			// Mouse event has been handled by the tool, so don't try
			// to use it for camera motion too
			return;
		}

		// Global mouse event handlers (for camera motion)

		if (evt.GetWheelRotation())
		{
			float speed = 16.f;
			if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL))
				speed /= 64.f;
			else if (wxGetKeyState(WXK_CONTROL))
				speed /= 4.f;
			else if (wxGetKeyState(WXK_SHIFT))
				speed *= 4.f;

			POST_MESSAGE(SmoothZoom, (evt.GetWheelRotation() * speed / evt.GetWheelDelta()));
		}
		else
		{
			if (evt.MiddleIsDown())
			{
				if (wxGetKeyState(WXK_CONTROL) || evt.RightIsDown())
					m_MouseState = ROTATEAROUND;
				else
					m_MouseState = SCROLL;
			}
			else
				m_MouseState = NONE;

			if (m_MouseState != m_LastMouseState)
			{
				switch (m_MouseState)
				{
				case NONE: break;
				case SCROLL: POST_MESSAGE(Scroll, (AtlasMessage::eScrollType::FROM, evt.GetPosition())); break;
				case ROTATEAROUND: POST_MESSAGE(RotateAround, (AtlasMessage::eRotateAroundType::FROM, evt.GetPosition())); break;
				default: wxFAIL;
				}
				m_LastMouseState = m_MouseState;
			}
			else if (evt.Dragging())
			{
				switch (m_MouseState)
				{
				case NONE: break;
				case SCROLL: POST_MESSAGE(Scroll, (AtlasMessage::eScrollType::TO, evt.GetPosition())); break;
				case ROTATEAROUND: POST_MESSAGE(RotateAround, (AtlasMessage::eRotateAroundType::TO, evt.GetPosition())); break;
				default: wxFAIL;
				}
			}
		}
#endif // UI_ONLY
	}

private:
	bool m_SuppressResize;

	enum { NONE, SCROLL, ROTATEAROUND };
	int m_MouseState, m_LastMouseState;
	bool m_MouseCaptured;

	wxPoint m_LastMousePos;

	DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(Canvas, wxGLCanvas)
	EVT_SIZE      (Canvas::OnResize)
	EVT_KEY_DOWN  (Canvas::OnKeyDown)
	EVT_KEY_UP    (Canvas::OnKeyUp)
	EVT_CHAR      (Canvas::OnChar)
	
	EVT_LEFT_DOWN  (Canvas::OnMouse)
	EVT_LEFT_UP    (Canvas::OnMouse)
	EVT_RIGHT_DOWN (Canvas::OnMouse)
	EVT_RIGHT_UP   (Canvas::OnMouse)
	EVT_MIDDLE_DOWN(Canvas::OnMouse)
	EVT_MIDDLE_UP  (Canvas::OnMouse)
	EVT_MOUSEWHEEL (Canvas::OnMouse)
	EVT_MOTION     (Canvas::OnMouse)
	EVT_MOUSE_CAPTURE_CHANGED(Canvas::OnMouseCapture)
END_EVENT_TABLE()

// GL functions exported from DLL, and called by game (in a separate
// thread to the standard wx one)
ATLASDLLIMPEXP void Atlas_GLSetCurrent(void* context)
{
	((wxGLContext*)context)->SetCurrent();
}

ATLASDLLIMPEXP void Atlas_GLSwapBuffers(void* context)
{
	((wxGLContext*)context)->SwapBuffers();
}


//////////////////////////////////////////////////////////////////////////


volatile bool g_FrameHasEnded;
// Called from game thread
ATLASDLLIMPEXP void Atlas_NotifyEndOfFrame()
{
	g_FrameHasEnded = true;
}

enum
{
	ID_Quit = 1,
//	ID_New,
//	//	ID_Import,
//	//	ID_Export,
//	ID_Open,
//	ID_Save,
//	ID_SaveAs,

	ID_Wireframe,
	ID_MessageTrace,
	ID_Screenshot,

	ID_Toolbar // must be last in the list
};

BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame)
	EVT_CLOSE(ScenarioEditor::OnClose)
	EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer)

	EVT_MENU(ID_Quit, ScenarioEditor::OnQuit)
	EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo)
	EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo)

	EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe)
	EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace)
	EVT_MENU(ID_Screenshot, ScenarioEditor::OnScreenshot)

	EVT_IDLE(ScenarioEditor::OnIdle)
END_EVENT_TABLE()


static AtlasWindowCommandProc g_CommandProc;
AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; }

ScenarioEditor::ScenarioEditor(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Atlas - Scenario Editor"), wxDefaultPosition, wxSize(1024, 768))
{
	// Global application initialisation:
	//	wxLog::SetTraceMask(wxTraceMessages);
	SetIcon(wxIcon(_T("ICON_ScenarioEditor")));
	wxToolTip::Enable(true);
	wxImage::AddHandler(new wxPNGHandler);

	//////////////////////////////////////////////////////////////////////////
	// Menu

	wxMenuBar* menuBar = new wxMenuBar;
	SetMenuBar(menuBar);

	wxMenu *menuFile = new wxMenu;
	menuBar->Append(menuFile, _("&File"));
	{
//		menuFile->Append(ID_New, _("&New"));
//		//		menuFile->Append(ID_Import, _("&Import..."));
//		//		menuFile->Append(ID_Export, _("&Export..."));
//		menuFile->Append(ID_Open, _("&Open..."));
//		menuFile->Append(ID_Save, _("&Save"));
//		menuFile->Append(ID_SaveAs, _("Save &As..."));
//		menuFile->AppendSeparator();//-----------
		menuFile->Append(ID_Quit,   _("E&xit"));
//		m_FileHistory.UseMenu(menuFile);//-------
//		m_FileHistory.AddFilesToMenu();
	}

//	m_menuItem_Save = menuFile->FindItem(ID_Save); // remember this item, to let it be greyed out
//	wxASSERT(m_menuItem_Save);

	wxMenu *menuEdit = new wxMenu;
	menuBar->Append(menuEdit, _("&Edit"));
	{
		menuEdit->Append(wxID_UNDO, _("&Undo"));
		menuEdit->Append(wxID_REDO, _("&Redo"));
	}

	GetCommandProc().SetEditMenu(menuEdit);
	GetCommandProc().Initialize();


	wxMenu *menuMisc = new wxMenu;
	menuBar->Append(menuMisc, _("&Misc hacks"));
	{
		menuMisc->AppendCheckItem(ID_Wireframe, _("&Wireframe"));
		menuMisc->AppendCheckItem(ID_MessageTrace, _("Message debug trace"));
		menuMisc->Append(ID_Screenshot, _("&Screenshot"));
	}

	// Toolbar:

	ToolButtonBar* toolbar = new ToolButtonBar(this, ID_Toolbar);
	// TODO: configurable small vs large icon images
	// (button label; tooltip text; image; internal tool name)
	toolbar->AddToolButton(_("Default"), _("Default"), _T("default.png"), _T(""));
	toolbar->AddToolButton(_("Move"), _("Move/rotate object"), _T("moveobject.png"), _T("TransformObject"));
	toolbar->AddToolButton(_("Elevation"), _("Alter terrain elevation"), _T("alterelevation.png"), _T("AlterElevation"));
	toolbar->AddToolButton(_("Flatten"), _("Flatten terrain elevation"), _T("flattenelevation.png"), _T("FlattenElevation"));
	toolbar->AddToolButton(_("Paint Terrain"), _("Paint terrain texture"), _T("paintterrain.png"), _T("PaintTerrain"));
	toolbar->Realize();
	SetToolBar(toolbar);
	// Set the default tool to be selected
	SetCurrentTool(_T(""));

	//////////////////////////////////////////////////////////////////////////
	// Main window

	m_SectionLayout.SetWindow(this);

	// Set up GL canvas:

	int glAttribList[] = {
		WX_GL_RGBA,
		WX_GL_DOUBLEBUFFER,
		WX_GL_DEPTH_SIZE, 24, // TODO: wx documentation doesn't say 24 is valid
		WX_GL_BUFFER_SIZE, 24, // colour bits
		WX_GL_MIN_ALPHA, 8, // alpha bits
		0
	};
	Canvas* canvas = new Canvas(m_SectionLayout.GetCanvasParent(), glAttribList);
	m_SectionLayout.SetCanvas(canvas);
	// The canvas' context gets made current on creation; but it can only be
	// current for one thread at a time, and it needs to be current for the
	// thread that is doing the draw calls, so disable it for this one.
	wglMakeCurrent(NULL, NULL);

	// Set up sidebars:

	m_SectionLayout.Build();

	// Send setup messages to game engine:

#ifndef UI_ONLY
	POST_MESSAGE(SetContext, (canvas->GetContext()));

	POST_MESSAGE(Init, ());

	canvas->InitSize();

	// Start with a blank map (so that the editor can assume there's always
	// a valid map loaded)
	POST_MESSAGE(GenerateMap, (9));

	POST_MESSAGE(RenderEnable, (true));
#endif

	// Set up a timer to make sure tool-updates happen frequently (in addition
	// to the idle handler (which makes them happen more frequently if there's nothing
	// else to do))
	m_Timer.SetOwner(this);
	m_Timer.Start(20);
}


void ScenarioEditor::OnClose(wxCloseEvent&)
{
	SetCurrentTool(_T(""));

#ifndef UI_ONLY
	POST_MESSAGE(Shutdown, ());
#endif

	AtlasMessage::qExit().Post();
		// blocks until engine has noticed the message, so we won't be
		// destroying the GLCanvas while it's still rendering

	Destroy();
}


static void UpdateTool()
{
	// Don't keep posting events if the game can't keep up
	if (g_FrameHasEnded)
	{
		g_FrameHasEnded = false; // (thread safety doesn't matter here)
		// TODO: Smoother timing stuff?
		static double last = g_Timer.GetTime();
		double time = g_Timer.GetTime();
		GetCurrentTool().OnTick(time-last);
		last = time;
	}
}
void ScenarioEditor::OnTimer(wxTimerEvent&)
{
	UpdateTool();
}
void ScenarioEditor::OnIdle(wxIdleEvent&)
{
	UpdateTool();
}

void ScenarioEditor::OnQuit(wxCommandEvent&)
{
	Close();
}

void ScenarioEditor::OnUndo(wxCommandEvent&)
{
	GetCommandProc().Undo();
}

void ScenarioEditor::OnRedo(wxCommandEvent&)
{
	GetCommandProc().Redo();
}

//////////////////////////////////////////////////////////////////////////

void ScenarioEditor::OnWireframe(wxCommandEvent& event)
{
	POST_MESSAGE(RenderStyle, (event.IsChecked()));
}

void ScenarioEditor::OnMessageTrace(wxCommandEvent& event)
{
	POST_MESSAGE(MessageTrace, (event.IsChecked()));
}

void ScenarioEditor::OnScreenshot(wxCommandEvent& WXUNUSED(event))
{
	POST_MESSAGE(Screenshot, (10));
}

//////////////////////////////////////////////////////////////////////////

AtlasMessage::Position::Position(const wxPoint& pt)
: type(1)
{
	type1.x = pt.x;
	type1.y = pt.y;
}

//////////////////////////////////////////////////////////////////////////


static void QueryCallback()
{
	// If this thread completely blocked on the semaphore inside Query, it would
	// never respond to window messages, and the system deadlocks if the
	// game tries to display an assertion failure dialog. (See
	// WaitForSingleObject on MSDN.)
	// So, this callback is called occasionally, and gives wx a change to
	// handle messages.

	// This is kind of like wxYield, but without the ProcessPendingEvents -
	// it's enough to make Windows happy and stop deadlocking, without actually
	// calling the event handlers (which could lead to nasty recursion)

// 	while (wxEventLoop::GetActive()->Pending())
// 		wxEventLoop::GetActive()->Dispatch();

	// Oh dear, we can't use that either - it (at least in wx 2.6.3) still
	// processes messages, which causes reentry into various things that we
	// don't want to be reentrant. So do it all manually, accepting Windows
	// messages and sticking them on a list for later processing (in a custom
	// event loop class):

	// (TODO: Rethink this entire process on Linux)
	// (Alt TODO: Could we make the game never pop up windows (or use the Win32
	// GUI in any other way) when it's running under Atlas, so we wouldn't need
	// to do any message processing here at all?)

	AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive();

	while (evtLoop->Pending())
	{
		// Based on src/msw/evtloop.cpp's wxEventLoop::Dispatch()

		MSG msg;
		BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);

		if (rc == 0)
		{
			// got WM_QUIT
			return;
		}

		if (rc == -1)
		{
			wxLogLastError(wxT("GetMessage"));
			return;
		}

		// Our special bits:

		if (msg.message == WM_PAINT)
		{
			// "GetMessage does not remove WM_PAINT messages from the queue.
			// The messages remain in the queue until processed."
			// So let's process them, to avoid infinite loops...
			PAINTSTRUCT paint;
			::BeginPaint(msg.hwnd, &paint);
			::EndPaint(msg.hwnd, &paint);
			// Remember that some painting was needed - we'll just repaint
			// the whole screen when this is finished.
			evtLoop->NeedsPaint();
		}
		else
		{
			// Add this message to a queue for later processing. (That's
			// probably kind of valid, at least in most cases.)
			MSG* pMsg = new MSG(msg);
			evtLoop->AddMessage(pMsg);
		}
	}
}

void AtlasMessage::QueryMessage::Post()
{
	g_MessagePasser->Query(this, &QueryCallback);
}
