/* Copyright (C) 2013 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "ScenarioEditor.h"

#include "wx/busyinfo.h"
#include "wx/config.h"
#include "wx/dir.h"
#include "wx/evtloop.h"
#include "wx/ffile.h"
#include "wx/filename.h"
#include "wx/image.h"
#include "wx/sysopt.h"
#include "wx/tooltip.h"

#include "General/AtlasEventLoop.h"
#include "General/Datafile.h"

#include "CustomControls/Buttons/ToolButton.h"
#include "CustomControls/Canvas/Canvas.h"
#include "CustomControls/HighResTimer/HighResTimer.h"

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

#include "AtlasScript/ScriptInterface.h"

#include "Misc/KeyMap.h"

#include "Tools/Common/Tools.h"
#include "Tools/Common/Brushes.h"
#include "Tools/Common/MiscState.h"

static HighResTimer g_Timer;

using namespace AtlasMessage;

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

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

ATLASDLLIMPEXP void Atlas_GLSwapBuffers(void* canvas)
{
	static_cast<Canvas*>(canvas)->SwapBuffers();
}


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

class GameCanvas : public Canvas
{
public:
	GameCanvas(ScenarioEditor& scenarioEditor, wxWindow* parent, int* attribList)
		: Canvas(parent, attribList, wxWANTS_CHARS),
		m_ScenarioEditor(scenarioEditor), m_MouseState(NONE), m_LastMouseState(NONE)
	{
	}

private:

	bool KeyScroll(wxKeyEvent& evt, bool enable)
	{
		int dir;
		switch (evt.GetKeyCode())
		{
		case 'A': case WXK_LEFT:  dir = eScrollConstantDir::LEFT; break;
		case 'D': case WXK_RIGHT: dir = eScrollConstantDir::RIGHT; break;
		case 'W': case WXK_UP:    dir = eScrollConstantDir::FORWARDS; break;
		case 'S': case WXK_DOWN:  dir = eScrollConstantDir::BACKWARDS; break;
		case 'E': case ']':       dir = eScrollConstantDir::CLOCKWISE; break;
		case 'Q': case '[':       dir = eScrollConstantDir::ANTICLOCKWISE; break;
		case WXK_SHIFT: case WXK_CONTROL: dir = -1; break;
		default: return false;
		}

		float speed = 120.f * ScenarioEditor::GetSpeedModifier();

		if (dir == -1) // changed modifier keys - update all currently-scrolling directions
		{
			if (wxGetKeyState(WXK_LEFT))       POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, speed));
			if (wxGetKeyState(WXK_RIGHT))      POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, speed));
			if (wxGetKeyState(WXK_UP))         POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, speed));
			if (wxGetKeyState(WXK_DOWN))       POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, speed));
			if (wxGetKeyState((wxKeyCode)']')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, speed));
			if (wxGetKeyState((wxKeyCode)'[')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, speed));
			return false;
		}
		else
		{
			POST_MESSAGE(ScrollConstant, (eRenderView::GAME, dir, enable ? speed : 0.0f));
			return true;
		}
	}

	void OnKeyDown(wxKeyEvent& evt)
	{
		if (m_ScenarioEditor.GetToolManager().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;

		// Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead
		if (evt.GetKeyCode() >= 256)
			POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true));

		evt.Skip();
	}

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

		if (KeyScroll(evt, false))
			return;

		// Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead
		if (evt.GetKeyCode() >= 256)
			POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), false));

		evt.Skip();
	}

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

		// Alt+enter toggles fullscreen
		if (evt.GetKeyCode() == WXK_RETURN && wxGetKeyState(WXK_ALT))
		{
			if (m_ScenarioEditor.IsFullScreen())
				m_ScenarioEditor.ShowFullScreen(false);
			else
				m_ScenarioEditor.ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION);
			return;
		}

		if (evt.GetKeyCode() == 'c')
		{
			POST_MESSAGE(CameraReset, ());
			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 * ScenarioEditor::GetSpeedModifier();
			POST_MESSAGE(SmoothZoom, (eRenderView::GAME, speed*dir));
		}
		else
		{
			// Slight hack: Only pass 'normal' keys; special keys will generate a KeyDown/KeyUp event instead
			if (evt.GetKeyCode() < 256)
				POST_MESSAGE(GuiCharEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey()));

			evt.Skip();
		}
	}

	void OnKillFocus(wxFocusEvent& evt)
	{
		// Stop any scrolling, since otherwise we'll carry on forever if
		// we lose focus and the KeyUp events go to a different window
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, 0.0f));
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, 0.0f));
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, 0.0f));
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, 0.0f));
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, 0.0f));
		POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, 0.0f));

		evt.Skip();
	}

	virtual void HandleMouseEvent(wxMouseEvent& evt)
	{

		// 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();

		if (m_ScenarioEditor.GetToolManager().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 * ScenarioEditor::GetSpeedModifier();
			POST_MESSAGE(SmoothZoom, (eRenderView::GAME, 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, (eRenderView::GAME, eScrollType::FROM, evt.GetPosition())); break;
				case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, 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, (eRenderView::GAME, eScrollType::TO, evt.GetPosition())); break;
				case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::TO, evt.GetPosition())); break;
				default: wxFAIL;
				}
			}
		}

		// Button down and double click appear to be mutually exclusive events,
		//   meaning a second button down event is not sent before a double click
		if (evt.ButtonDown() || evt.ButtonDClick())
			POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), true, evt.GetPosition()));
		else if (evt.ButtonUp())
			POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), false, evt.GetPosition()));
		else if (evt.GetEventType() == wxEVT_MOTION)
			POST_MESSAGE(GuiMouseMotionEvent, (evt.GetPosition()));
	}

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

	ScenarioEditor& m_ScenarioEditor;

	DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(GameCanvas, Canvas)
	EVT_KEY_DOWN(GameCanvas::OnKeyDown)
	EVT_KEY_UP(GameCanvas::OnKeyUp)
	EVT_CHAR(GameCanvas::OnChar)
	EVT_KILL_FOCUS(GameCanvas::OnKillFocus)
END_EVENT_TABLE()

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

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

enum
{
	ID_Quit = 1,

 	ID_New,
	ID_Open,
	ID_Save,
	ID_SaveAs,
	ID_ImportHeightmap,

	ID_Wireframe,
	ID_MessageTrace,
	ID_Screenshot,
	ID_BigScreenshot,
	ID_JavaScript,
	ID_CameraReset,
	ID_RenderPathFixed,
	ID_RenderPathShader,
	ID_DumpState,
	ID_DumpBinaryState,

	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_New, ScenarioEditor::OnNew)
	EVT_MENU(ID_Open, ScenarioEditor::OnOpen)
	EVT_MENU(ID_Save, ScenarioEditor::OnSave)
	EVT_MENU(ID_SaveAs, ScenarioEditor::OnSaveAs)
	EVT_MENU(ID_ImportHeightmap, ScenarioEditor::OnImportHeightmap)
	EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, ScenarioEditor::OnMRUFile)

	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_MENU(ID_BigScreenshot, ScenarioEditor::OnScreenshot)
	EVT_MENU(ID_JavaScript, ScenarioEditor::OnJavaScript)
	EVT_MENU(ID_CameraReset, ScenarioEditor::OnCameraReset)
	EVT_MENU(ID_DumpState, ScenarioEditor::OnDumpState)
	EVT_MENU(ID_DumpBinaryState, ScenarioEditor::OnDumpState)
	EVT_MENU(ID_RenderPathFixed, ScenarioEditor::OnRenderPath)
	EVT_MENU(ID_RenderPathShader, ScenarioEditor::OnRenderPath)

	EVT_IDLE(ScenarioEditor::OnIdle)
END_EVENT_TABLE()

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

ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface)
: wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768))
, m_FileHistory(_T("Scenario Editor")), m_ScriptInterface(scriptInterface)
, m_ObjectSettings(g_SelectedObjects, AtlasMessage::eRenderView::GAME)
, m_ToolManager(this)
{
	// Global application initialisation:

	wxImage::AddHandler(new wxICOHandler);

	/* "osx.openfiledialog.always-show-types: Per default a wxFileDialog with wxFD_OPEN
	   does not show a types-popup on OSX but allows the selection of files from any of
	   the supported types. Setting this to 1 shows a wxChoice for selection (if there
	   is more than one supported filetype)." */
	wxSystemOptions::SetOption(_T("osx.openfiledialog.always-show-types"), 1);	// has global effect

	// wxLog::SetTraceMask(wxTraceMessages);

	g_SelectedTexture = _T("grass1_spring");
	g_SelectedTexture.NotifyObservers();

	SetOpenFilename(_T(""));

#if defined(__WXMSW__)
	SetIcon(wxIcon(_T("ICON_ScenarioEditor"))); // load from atlas.rc
#else
	{
		const wxString relativePath (_T("tools/atlas/icons/ScenarioEditor.ico"));
		wxFileName filename (relativePath, wxPATH_UNIX);
		filename.MakeAbsolute(Datafile::GetDataDirectory());
		SetIcon(wxIcon(filename.GetFullPath(), wxBITMAP_TYPE_ICO));
	}
#endif

	wxToolTip::Enable(true);

	wxImage::AddHandler(new wxPNGHandler);

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

	// Do some early game initialisation:
	// (This must happen before constructing the GL canvas.)

	POST_MESSAGE(Init, ());

	// Wait for it to finish running Init
	qPing qry;
	qry.Post();

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

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

	wxMenu *menuFile = new wxMenu;
	menuBar->Append(menuFile, _("&File"));
	{
 		menuFile->Append(ID_New, _("&New..."));
		menuFile->Append(ID_Open, _("&Open..."));
		menuFile->Append(ID_Save, _("&Save"));
		menuFile->Append(ID_SaveAs, _("Save &As..."));
		menuFile->AppendSeparator();//-----------
		menuFile->Append(ID_ImportHeightmap, _("&Import Heightmap..."));
		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"));
		menuMisc->Append(ID_BigScreenshot, _("Big screenshot"));
		menuMisc->Append(ID_JavaScript, _("&JS console"));
		menuMisc->Append(ID_CameraReset, _("&Reset camera"));

		wxMenu *menuSS = new wxMenu;
		menuMisc->AppendSubMenu(menuSS, _("Si&mulation state"));
		menuSS->Append(ID_DumpState, _("&Dump to disk"));
		menuSS->Append(ID_DumpBinaryState, _("Dump &binary to disk"));

		wxMenu *menuRP = new wxMenu;
		menuMisc->AppendSubMenu(menuRP, _("Render &path"));
		menuRP->Append(ID_RenderPathFixed, _("&Fixed function"));
		menuRP->Append(ID_RenderPathShader, _("&Shader (default)"));
	}

	m_FileHistory.LoadFromSubDir(*wxConfigBase::Get());


	m_SectionLayout.SetWindow(this);

	// Toolbar:
	// wxOSX/Cocoa 2.9 doesn't seem to like SetToolBar, so we use CreateToolBar which implicitly associates
	//	the toolbar with the frame, and use OnCreateToolBar to construct our custom toolbar
	//	(this should be equivalent behavior on all platforms)
	CreateToolBar(wxNO_BORDER|wxTB_FLAT|wxTB_HORIZONTAL, ID_Toolbar)->Realize();

	// Set the default tool to be selected
	m_ToolManager.SetCurrentTool(_T(""));


	// 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_STENCIL_SIZE, 8,
		WX_GL_BUFFER_SIZE, 24, // colour bits
		WX_GL_MIN_ALPHA, 8, // alpha bits
		0
	};
	Canvas* canvas = new GameCanvas(*this, m_SectionLayout.GetCanvasParent(), glAttribList);
	m_SectionLayout.SetCanvas(canvas);

	// Set up sidebars:

	m_SectionLayout.Build(*this);

#if defined(__WXMSW__)
	// 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);
#elif defined(__WXGTK__) || defined(__WXOSX__) || defined(__WXMAC__)
	// Need to make sure the canvas is realised, so that its context is valid
	// this solves the "invalid drawable" error
	Show(true);
#endif
#ifdef __WXGTK__
	// TODO: wxSafeYield causes issues on wxOSX 2.9, is it necessary?
	wxSafeYield();
#endif

	// Send setup messages to game engine:

	POST_MESSAGE(SetCanvas, (static_cast<wxGLCanvas*>(canvas),
		canvas->GetClientSize().GetWidth(), canvas->GetClientSize().GetHeight()));

	POST_MESSAGE(InitGraphics, ());

	canvas->InitSize();

	// Start with a blank map (so that the editor can assume there's always
	// a valid map loaded)
	POST_MESSAGE(LoadMap, (_T("_default")));
	POST_MESSAGE(SimPlay, (0.f, false));

	// Select the initial sidebar (after the map has loaded)
	m_SectionLayout.SelectPage(_T("MapSidebar"));

	// Wait for blank map
	qry.Post();

	POST_MESSAGE(RenderEnable, (eRenderView::GAME));

	// 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);
}

wxToolBar* ScenarioEditor::OnCreateToolBar(long style, wxWindowID id, const wxString& WXUNUSED(name))
{
	ToolButtonBar* toolbar = new ToolButtonBar(m_ToolManager, this, &m_SectionLayout, id, style);
	// TODO: configurable small vs large icon images

	// (button label; tooltip text; image; internal tool name; section to switch to)
	toolbar->AddToolButton(_("Default"),       _("Default"),                   _T("default.png"),          _T(""),                 _T(""));
	toolbar->AddToolButton(_("Move"),          _("Move/rotate object"),        _T("moveobject.png"),       _T("TransformObject"),  _T("")/*_T("ObjectSidebar")*/);
	toolbar->AddToolButton(_("Elevation"),     _("Alter terrain elevation"),   _T("alterelevation.png"),   _T("AlterElevation"),   _T("")/*_T("TerrainSidebar")*/);
	toolbar->AddToolButton(_("Smooth"),        _("Smooth terrain elevation"),  _T("smoothelevation.png"),  _T("SmoothElevation"),  _T("")/*_T("TerrainSidebar")*/);
	toolbar->AddToolButton(_("Flatten"),       _("Flatten terrain elevation"), _T("flattenelevation.png"), _T("FlattenElevation"), _T("")/*_T("TerrainSidebar")*/);
	toolbar->AddToolButton(_("Paint Terrain"), _("Paint terrain texture"),     _T("paintterrain.png"),     _T("PaintTerrain"),     _T("")/*_T("TerrainSidebar")*/);

	return toolbar;
}

float ScenarioEditor::GetSpeedModifier()
{
	if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL))
		return 1.f/64.f;
	else if (wxGetKeyState(WXK_CONTROL))
		return 1.f/4.f;
	else if (wxGetKeyState(WXK_SHIFT))
		return 4.f;
	else
		return 1.f;
}


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

	m_FileHistory.SaveToSubDir(*wxConfigBase::Get());

	POST_MESSAGE(Shutdown, ());

	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(ToolManager& toolManager)
{
	// 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();
		toolManager.GetCurrentTool()->OnTick(time-last);
		last = time;
	}
}
void ScenarioEditor::OnTimer(wxTimerEvent&)
{
	UpdateTool(m_ToolManager);
}
void ScenarioEditor::OnIdle(wxIdleEvent&)
{
	UpdateTool(m_ToolManager);
}

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

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

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

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

void ScenarioEditor::OnNew(wxCommandEvent& WXUNUSED(event))
{
	if (wxMessageBox(_("Discard current map and start blank new map?"), _("New map"), wxOK|wxCANCEL|wxICON_QUESTION, this) == wxOK)
		OpenFile(_T("_default"), _T(""));
}

bool ScenarioEditor::OpenFile(const wxString& name, const wxString& filename)
{
	wxBusyInfo busy(_("Loading ") + name);
	wxBusyCursor busyc;
	
	wxString mapPath(_T("maps/scenarios/"));
	wxFileName fullFilename(filename);

	// For compatibility it's still possible to load xml maps but in that case,
	// we want a blank filename so that we're forced to Save As
	if (filename.IsEmpty() || fullFilename.GetExt().IsSameAs(_T("xml"), false))
	{
		fullFilename = _T("");
	}
	else
	{	// force extension to pmp
		fullFilename.SetExt(_T("pmp"));

		// check if pmp exists
		qVFSFileExists qry(std::wstring((mapPath + fullFilename.GetFullName()).wc_str()));
		qry.Post();
		if (!qry.exists)
		{
			return false;
		}
	}

	// Every map requires an xml file, so check that it exists
	wxFileName xmlName(name);
	xmlName.SetExt(_T("xml"));
	qVFSFileExists qry(std::wstring((mapPath + xmlName.GetFullName()).wc_str()));
	qry.Post();
	if (!qry.exists)
	{
		return false;
	}

	// Deactivate tools, so they don't carry forwards into the new CWorld
	// and crash.
	m_ToolManager.SetCurrentTool(_T(""));
	// TODO: clear the undo buffer, etc

	// TODO: Work when the map is not in .../maps/scenarios/
	std::wstring map(name.wc_str());
	POST_MESSAGE(LoadMap, (map));

	SetOpenFilename(fullFilename.GetFullName());

	{	// Wait for it to load, while the wxBusyInfo is telling the user that we're doing that
		qPing qry;
		qry.Post();
	}

	NotifyOnMapReload();

	return true;
	// TODO: Make this a non-undoable command
}

// TODO (eventually): replace all this file-handling stuff with the Workspace Editor

void ScenarioEditor::OnOpen(wxCommandEvent& WXUNUSED(event))
{
	wxFileDialog dlg (NULL, wxFileSelectorPromptStr,
		Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename,
		_T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"),
		wxFD_OPEN);
	// Set default filter
	dlg.SetFilterIndex(0);

	wxString cwd = wxFileName::GetCwd();

	if (dlg.ShowModal() == wxID_OK)
	{
		// TODO: Handle maps in subdirectories of maps/scenarios
		wxFileName filename(dlg.GetFilename());
		if (!OpenFile(filename.GetName(), filename.GetFullName()))
		{
			wxLogError(_("Map '%ls' does not exist"), filename.GetName().c_str());
		}
		
		// paranoia - MSDN says OFN_NOCHANGEDIR (used when we don't give wxCHANGE_DIR)
		// "is ineffective for GetOpenFileName", but it seems to work anyway
		wxCHECK_RET(cwd == wxFileName::GetCwd(), _T("cwd changed"));
	}

	// TODO: Make this a non-undoable command
}

void ScenarioEditor::OnImportHeightmap(wxCommandEvent& WXUNUSED(event))
{
	wxFileDialog dlg (NULL, wxFileSelectorPromptStr,
		_T(""), _T(""),
		_T("Valid Image files|*.png;*.jpg;*.bmp|All files (*.*)|*.*"),
		wxFD_OPEN);
	// Set default filter
	dlg.SetFilterIndex(0);

	wxString cwd = wxFileName::GetCwd();

	if (dlg.ShowModal() != wxID_OK)
		return;
	
	OpenFile(_T("_default"), _T(""));
	
	std::wstring image(dlg.GetPath().wc_str());
	POST_MESSAGE(ImportHeightmap, (image));

	// TODO: Make this a non-undoable command
}

void ScenarioEditor::OnMRUFile(wxCommandEvent& event)
{
	wxString filename(m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1));

	if (!OpenFile(filename, filename))
	{	// Missing or invalid - warn and remove from MRU
		wxLogError(_("Map '%ls' does not exist"), filename.c_str());
		m_FileHistory.RemoveFileFromHistory(event.GetId() - wxID_FILE1);
	}
}

void ScenarioEditor::OnSave(wxCommandEvent& event)
{
	if (m_OpenFilename.IsEmpty())
	{
		OnSaveAs(event);
	}
	else
	{
		wxBusyInfo busy(_("Saving ") + m_OpenFilename);
		wxBusyCursor busyc;

		// Deactivate tools, so things like unit previews don't get saved.
		// (TODO: Would be nicer to leave the tools active, and just not save
		// the preview units.)
		m_ToolManager.SetCurrentTool(_T(""));

		// TODO: Handle maps in subdirectories of maps/scenarios
		std::wstring map(m_OpenFilename.wc_str());
		POST_MESSAGE(SaveMap, (map));

		// Wait for it to finish saving
		qPing qry;
		qry.Post();
	}
}

void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event))
{
	wxFileDialog dlg (NULL, wxFileSelectorPromptStr,
		Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename,
		_T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"),
		wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
	// Set default filter
	dlg.SetFilterIndex(0);

	if (dlg.ShowModal() == wxID_OK)
	{
		// On wxMSW the extension is automatically set to pmp if that filter is selected
		// but not on wxGTK or wxOSX. Set it explicitly since it's the only possible format.
		wxFileName filename(dlg.GetFilename());
		filename.SetExt(_T("pmp"));
		if (!filename.IsOk())
		{	// Shouldn't happen
			wxLogError(_("Invalid filename '%ls'"), filename.GetFullName().c_str());
			return;
		}

		wxBusyInfo busy(_("Saving ") + filename.GetFullName());
		wxBusyCursor busyc;

		m_ToolManager.SetCurrentTool(_T(""));

		// TODO: Handle maps in subdirectories of maps/scenarios
		std::wstring map(filename.GetFullName().wc_str());
		POST_MESSAGE(SaveMap, (map));

		SetOpenFilename(filename.GetFullName());

		// Wait for it to finish saving
		qPing qry;
		qry.Post();
	}
}

void ScenarioEditor::SetOpenFilename(const wxString& filename)
{
	SetTitle(wxString::Format(_("Atlas - Scenario Editor - %s"),
		(filename.IsEmpty() ? wxString(_("(untitled)")) : filename).c_str()));

	m_OpenFilename = filename;

	if (! filename.IsEmpty())
		m_FileHistory.AddFileToHistory(filename);
}

void ScenarioEditor::NotifyOnMapReload()
{
	m_SectionLayout.OnMapReload();

	// Notify observers, here so it's independent of individual panels
	m_MapSettings.NotifyObservers();
}

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

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

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

void ScenarioEditor::OnScreenshot(wxCommandEvent& event)
{
	switch (event.GetId())
	{
	case ID_BigScreenshot:
		POST_MESSAGE(Screenshot, (true, 10));
		break;
	case ID_Screenshot:
		POST_MESSAGE(Screenshot, (false, 0));
		break;
	}
}

void ScenarioEditor::OnJavaScript(wxCommandEvent& WXUNUSED(event))
{
	wxString cmd = ::wxGetTextFromUser(_T(""), _("JS command"), _T(""), this);
	if (cmd.IsEmpty())
		return;
	POST_MESSAGE(JavaScript, ((std::wstring)cmd.wc_str()));
}

void ScenarioEditor::OnCameraReset(wxCommandEvent& WXUNUSED(event))
{
	POST_MESSAGE(CameraReset, ());
}

void ScenarioEditor::OnRenderPath(wxCommandEvent& event)
{
	switch (event.GetId())
	{
	case ID_RenderPathFixed:
		POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"fixed"));
		break;
	case ID_RenderPathShader:
		POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"shader"));
		break;
	}
}

void ScenarioEditor::OnDumpState(wxCommandEvent& event)
{
	wxDateTime time = wxDateTime::Now();
	wxString filename;
	bool doBinary = false;

	switch (event.GetId())
	{
	case ID_DumpState:
		filename = wxString::Format(_T("sim-dump-%d.txt"), time.GetTicks());
		break;
	case ID_DumpBinaryState:
		doBinary = true;
		filename = wxString::Format(_T("sim-dump-%d.dat"), time.GetTicks());
		break;
	}

	qSimStateDebugDump q(doBinary);
	q.Post();

	std::wstring dump = *q.dump;
	wxString state(dump.c_str());

	wxFFile file(filename.c_str(), _T("w"));
	if (file.IsOpened() && !file.Error())
	{
		file.Write(state);
		file.Close();
	}
	else
	{
		wxLogError(_("Error writing to file '%ls'"), filename.c_str());
	}
}


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

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

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

/* Disabled (and should be removed if it turns out to be unnecessary)
   - see MessagePasserImpl.cpp for information

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?)

#ifdef _WIN32
	AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive();

	// evtLoop might be NULL, particularly if we're still initialising windows
	// and haven't got into the normal event loop yet. But we'd have to process
	// messages anyway, to avoid the deadlocks that this is for. So, don't bother
	// with that and just crash instead.
	// (Maybe it could be solved better by constructing/finding an event loop
	// object here and setting it as the global one, assuming it's not overwritten
	// later by wx.)

	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);
		}
	}
#endif
}
*/
void QueryMessage::Post()
{
//	g_MessagePasser->Query(this, &QueryCallback);
	g_MessagePasser->Query(this, NULL);
}
