/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 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 PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"
#include "App.h"
#include "MainFrame.h"
#include "ConsoleLogger.h"
#include "MSWstuff.h"

#include "Utilities/Console.h"
#include "Utilities/IniInterface.h"
#include "Utilities/SafeArray.inl"
#include "DebugTools/Debug.h"

#include <wx/textfile.h>

wxDECLARE_EVENT(pxEvt_SetTitleText, wxCommandEvent);
wxDECLARE_EVENT(pxEvt_FlushQueue, wxCommandEvent);

wxDEFINE_EVENT(pxEvt_SetTitleText, wxCommandEvent);
wxDEFINE_EVENT(pxEvt_FlushQueue, wxCommandEvent);

// C++ requires abstract destructors to exist, even though they're abstract.
PipeRedirectionBase::~PipeRedirectionBase() = default;

// ----------------------------------------------------------------------------
//
void pxLogConsole::DoLogRecord(wxLogLevel level, const wxString &message, const wxLogRecordInfo &info)
{
	switch ( level )
	{
		case wxLOG_Trace:
		case wxLOG_Debug:
		{
			wxString str;
			TimeStamp( &str );
			MSW_OutputDebugString( str + message + L"\n" );
		}
		break;

		case wxLOG_FatalError:
			// This one is unused by wx, and unused by PCSX2 (we prefer exceptions, thanks).
			pxFailDev( "Stop using FatalError and use assertions or exceptions instead." );
		break;

		case wxLOG_Status:
			// Also unsed by wx, and unused by PCSX2 also (we prefer direct API calls to our main window!)
			pxFailDev( "Stop using wxLogStatus just access the Pcsx2App functions directly instead." );
		break;

		case wxLOG_Info:
			if ( !GetVerbose() ) return;
			// fallthrough!

		case wxLOG_Message:
			Console.WriteLn( L"[wx] %s", WX_STR(message));
		break;

		case wxLOG_Error:
			Console.Error(L"[wx] %s", WX_STR(message));
		break;

		case wxLOG_Warning:
			Console.Warning(L"[wx] %s", WX_STR(message));
		break;
	}
}


// ----------------------------------------------------------------------------
void ConsoleTestThread::ExecuteTaskInThread()
{
	static int numtrack = 0;

	while( !m_done )
	{
		// Two lines, both formatted, and varied colors.  This makes for a fairly realistic
		// worst case scenario (without being entirely unrealistic).
		Console.WriteLn( L"This is a threaded logging test. Something bad could happen... %d", ++numtrack );
		Console.Warning( L"Testing high stress loads %s", L"(multi-color)" );
		Yield( 0 );
	}
}

// ----------------------------------------------------------------------------
// Pass an uninitialized file object. The function will ask the user for the
// filename and try to open it. It returns true on success (file was opened),
// false if file couldn't be opened/created and -1 if the file selection
// dialog was canceled.
//
static bool OpenLogFile(wxFile& file, wxString& filename, wxWindow *parent)
{
	filename = wxSaveFileSelector(L"log", L"txt", L"log.txt", parent);
	if ( !filename ) return false; // canceled

	if( wxFile::Exists(filename) )
	{
		bool bAppend = false;
		wxString strMsg;
		strMsg.Printf(L"Append log to file '%s' (choosing [No] will overwrite it)?",
						filename.c_str());

		switch ( Msgbox::ShowModal( _("Save log question"), strMsg, MsgButtons().YesNo().Cancel() ) )
		{
			case wxID_YES:
				bAppend = true;
			break;

			case wxID_NO:
				bAppend = false;
			break;

			case wxID_CANCEL:
				return false;

			default:
				pxFailDev( "invalid message box return value" );
		}

		return ( bAppend ) ?
			file.Open(filename, wxFile::write_append) :
			file.Create(filename, true /* overwrite */);
	}

	return file.Create(filename);
}

// --------------------------------------------------------------------------------------
//  ConsoleLogFrame::ColorArray  (implementations)
// --------------------------------------------------------------------------------------

// fontsize - size of the font specified in points.
//   (actual font used is the system-selected fixed-width font)
//
ConsoleLogFrame::ColorArray::ColorArray( int fontsize )
{
	SetFont( fontsize );
}

void ConsoleLogFrame::ColorArray::SetFont( int fontsize )
{
	const wxFont fixed( pxGetFixedFont( fontsize ) );
	const wxFont fixedB( pxGetFixedFont( fontsize+1, wxFONTWEIGHT_BOLD ) );

	// Standard R, G, B format:
	for (size_t i = 0; i < Color_StrongBlack; ++i)
		m_table[i].SetFont(fixed);

	for (size_t i = Color_StrongBlack; i < ConsoleColors_Count; ++i)
		m_table[i].SetFont(fixedB);

	SetColorScheme_Light();
}

void ConsoleLogFrame::ColorArray::SetColorScheme_Dark()
{
	m_table[Color_Default]		.SetTextColour(wxColor( 208, 208, 208 ));
	m_table[Color_Black]		.SetTextColour(wxColor( 255, 255, 255 ));
	m_table[Color_Red]			.SetTextColour(wxColor( 180,   0,   0 ));
	m_table[Color_Green]		.SetTextColour(wxColor(   0, 160,   0 ));
	m_table[Color_Blue]			.SetTextColour(wxColor(  32,  32, 204 ));
	m_table[Color_Magenta]		.SetTextColour(wxColor( 160,   0, 160 ));
	m_table[Color_Orange]		.SetTextColour(wxColor( 160, 120,   0 ));
	m_table[Color_Gray]			.SetTextColour(wxColor( 128, 128, 128 ));

	m_table[Color_Cyan]			.SetTextColour(wxColor( 128, 180, 180 ));
	m_table[Color_Yellow]		.SetTextColour(wxColor( 180, 180, 128 ));
	m_table[Color_White]		.SetTextColour(wxColor( 160, 160, 160 ));

	m_table[Color_StrongBlack]	.SetTextColour(wxColor( 255, 255, 255 ));
	m_table[Color_StrongRed]	.SetTextColour(wxColor( 180,   0,   0 ));
	m_table[Color_StrongGreen]	.SetTextColour(wxColor(   0, 160,   0 ));
	m_table[Color_StrongBlue]	.SetTextColour(wxColor(  32,  32, 204 ));
	m_table[Color_StrongMagenta].SetTextColour(wxColor( 160,   0, 160 ));
	m_table[Color_StrongOrange]	.SetTextColour(wxColor( 160, 120,   0 ));
	m_table[Color_StrongGray]	.SetTextColour(wxColor( 128, 128, 128 ));

	m_table[Color_StrongCyan]	.SetTextColour(wxColor( 128, 180, 180 ));
	m_table[Color_StrongYellow]	.SetTextColour(wxColor( 180, 180, 128 ));
	m_table[Color_StrongWhite]	.SetTextColour(wxColor( 160, 160, 160 ));
}

void ConsoleLogFrame::ColorArray::SetColorScheme_Light()
{
	m_table[Color_Default]		.SetTextColour(wxColor(   0,   0,   0 ));
	m_table[Color_Black]		.SetTextColour(wxColor(   0,   0,   0 ));
	m_table[Color_Red]			.SetTextColour(wxColor( 128,   0,   0 ));
	m_table[Color_Green]		.SetTextColour(wxColor(   0, 128,   0 ));
	m_table[Color_Blue]			.SetTextColour(wxColor(   0,   0, 128 ));
	m_table[Color_Magenta]		.SetTextColour(wxColor( 160,   0, 160 ));
	m_table[Color_Orange]		.SetTextColour(wxColor( 160, 120,   0 ));
	m_table[Color_Gray]			.SetTextColour(wxColor( 108, 108, 108 ));

	m_table[Color_Cyan]			.SetTextColour(wxColor( 128, 180, 180 ));
	m_table[Color_Yellow]		.SetTextColour(wxColor( 180, 180, 128 ));
	m_table[Color_White]		.SetTextColour(wxColor( 160, 160, 160 ));

	m_table[Color_StrongBlack]	.SetTextColour(wxColor(   0,   0,   0 ));
	m_table[Color_StrongRed]	.SetTextColour(wxColor( 128,   0,   0 ));
	m_table[Color_StrongGreen]	.SetTextColour(wxColor(   0, 128,   0 ));
	m_table[Color_StrongBlue]	.SetTextColour(wxColor(   0,   0, 128 ));
	m_table[Color_StrongMagenta].SetTextColour(wxColor( 160,   0, 160 ));
	m_table[Color_StrongOrange]	.SetTextColour(wxColor( 160, 120,   0 ));
	m_table[Color_StrongGray]	.SetTextColour(wxColor( 108, 108, 108 ));

	m_table[Color_StrongCyan]	.SetTextColour(wxColor( 128, 180, 180 ));
	m_table[Color_StrongYellow]	.SetTextColour(wxColor( 180, 180, 128 ));
	m_table[Color_StrongWhite]	.SetTextColour(wxColor( 160, 160, 160 ));
}

// fixme - not implemented yet.
void ConsoleLogFrame::ColorArray::SetFont( const wxFont& font )
{
	//for( int i=0; i<ConsoleColors_Count; ++i )
	//	m_table[i].SetFont( font );
}

u32 ConsoleLogFrame::ColorArray::GetRGBA( const ConsoleColors color )
{
	return m_table[color].GetTextColour().GetRGBA();
}

enum MenuIDs_t
{
	MenuId_FontSize_Small = 0x10,
	MenuId_FontSize_Normal,
	MenuId_FontSize_Large,
	MenuId_FontSize_Huge,

	MenuId_ColorScheme_Light = 0x20,
	MenuId_ColorScheme_Dark,

	MenuId_AutoDock,

	MenuId_LogSource_EnableAll = 0x30,
	MenuId_LogSource_DisableAll,
	MenuId_LogSource_Devel,
	MenuId_LogSource_SetDefault,
	MenuId_LogSource_CDVD_Info,

	MenuId_LogSource_Start = 0x100
};

#define pxTheApp ((Pcsx2App&)*wxTheApp)

// --------------------------------------------------------------------------------------
//  ScopedLogLock  (implementations)
// --------------------------------------------------------------------------------------
class ScopedLogLock : public ScopedLock
{
public:
	ConsoleLogFrame*	WindowPtr;

public:
	ScopedLogLock()
		: ScopedLock( wxThread::IsMain() ? NULL : &pxTheApp.GetProgramLogLock() )
	{
		WindowPtr = pxTheApp.m_ptr_ProgramLog;
	}

	virtual ~ScopedLogLock() = default;

	bool HasWindow() const
	{
		return WindowPtr != NULL;
	}

	ConsoleLogFrame& GetWindow() const
	{
		return *WindowPtr;
	}
};

enum MenuId_LogSources_Offset
{
	MenuId_LogSources_Offset_eeConsole = 0,
	MenuId_LogSources_Offset_iopConsole,
	MenuId_LogSources_Offset_eeRecPerf,

	MenuId_LogSources_Offset_ELF = 4,

	MenuId_LogSources_Offset_Event = 6,
	MenuId_LogSources_Offset_Thread,
	MenuId_LogSources_Offset_sysoutConsole,

	MenuId_LogSources_Offset_recordingConsole = 10,
	MenuId_LogSources_Offset_controlInfo
};

// WARNING ConsoleLogSources & ConLogDefaults must have the same size
static ConsoleLogSource* const ConLogSources[] =
{
	(ConsoleLogSource*)&SysConsole.eeConsole,
	(ConsoleLogSource*)&SysConsole.iopConsole,
	(ConsoleLogSource*)&SysConsole.eeRecPerf,
	NULL,
	(ConsoleLogSource*)&SysConsole.ELF,
	NULL,
	(ConsoleLogSource*)&pxConLog_Event,
	(ConsoleLogSource*)&pxConLog_Thread,
	(ConsoleLogSource*)&SysConsole.sysoutConsole,
	NULL,
#ifndef DISABLE_RECORDING
	(ConsoleLogSource*)&SysConsole.recordingConsole,
	(ConsoleLogSource*)&SysConsole.controlInfo,
#endif
};

// WARNING ConsoleLogSources & ConLogDefaults must have the same size
static const bool ConLogDefaults[] =
{
	true,
	true,
	false,
	true,
	false,
	false,
	false,
	false,
	false,
#ifndef DISABLE_RECORDING
	false,
	false,
#endif
	false
};

// Typically on startup (or during first time wizard when choosing "import"), the
// settings are loaded from ini and if the ini doesn't exist then from ConLogDefaults,
// but during first time wizard when choosing "overwrite", the first action is "save",
// which ends up saving before applying ConLogDefaults, therefore all conlog sources
// are saved as disabled. ConLogInitialized is used to detect and avoid this issue.
static bool ConLogInitialized = false;

void ConLog_LoadSaveSettings( IniInterface& ini )
{
	ScopedIniGroup path(ini, L"ConsoleLogSources");

	ini.Entry( L"Devel", DevConWriterEnabled, false );

	uint srcnt = ArraySize(ConLogSources);
	for (uint i=0; i<srcnt; ++i)
	{
		if (ConsoleLogSource* log = ConLogSources[i])
		{
			// IsSaving() is for clarity only, since log->Enabled initial value is ignored when loading.
			if (ini.IsSaving() && !ConLogInitialized)
				log->Enabled = ConLogDefaults[i];
			ini.Entry( log->GetCategory() + L"." + log->GetShortName(), log->Enabled, ConLogDefaults[i] );
		}
	}

	ConLogInitialized = true;
}

// --------------------------------------------------------------------------------------
//  ConsoleLogFrame  (implementations)
// --------------------------------------------------------------------------------------
ConsoleLogFrame::ConsoleLogFrame( MainEmuFrame *parent, const wxString& title, AppConfig::ConsoleLogOptions& options )
	: wxFrame(parent, wxID_ANY, title)
	, m_conf( options )
	, m_TextCtrl( *new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
		wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL) )
	, m_timer_FlushUnlocker( this )
	, m_ColorTable( options.FontSize )

	, m_QueueColorSection( L"ConsoleLog::QueueColorSection" )
	, m_QueueBuffer( L"ConsoleLog::QueueBuffer" )
	, m_threadlogger( EnableThreadedLoggingTest ? new ConsoleTestThread() : NULL )
{
	m_CurQueuePos				= 0;
	m_WaitingThreadsForFlush	= 0;
	m_pendingFlushMsg			= false;
	m_FlushRefreshLocked		= false;

	// create Log menu (contains most options)
	wxMenuBar *pMenuBar = new wxMenuBar();
	SetMenuBar( pMenuBar );
	SetIcons( wxGetApp().GetIconBundle() );

	if (0==m_conf.Theme.CmpNoCase(L"Dark"))
	{
		m_ColorTable.SetColorScheme_Dark();
		m_TextCtrl.SetBackgroundColour( wxColor( 0, 0, 0 ) );
	}
	else //if ((0==m_conf.Theme.CmpNoCase("Default")) || (0==m_conf.Theme.CmpNoCase("Light")))
	{
		m_ColorTable.SetColorScheme_Light();
		m_TextCtrl.SetBackgroundColour( wxColor( 230, 235, 242 ) );
	}

	m_TextCtrl.SetDefaultStyle( m_ColorTable[DefaultConsoleColor] );

	// SetDefaultStyle only sets the style of text in the control.  We need to
	// also set the font of the control, so that sizing logic knows what font we use:
	m_TextCtrl.SetFont( m_ColorTable[DefaultConsoleColor].GetFont() );

	wxMenu& menuLog		(*new wxMenu());
	wxMenu& menuAppear	(*new wxMenu());
	wxMenu& menuSources	(*new wxMenu());
	wxMenu& menuFontSizes( menuAppear );

	// create Appearance menu and submenus

	menuFontSizes.Append( MenuId_FontSize_Small,	_("&Small"),	_t("Fits a lot of log in a microcosmically small area."),
		wxITEM_RADIO )->Check( options.FontSize == 7 );
	menuFontSizes.Append( MenuId_FontSize_Normal,	_("&Normal font"),_t("It's what I use (the programmer guy)."),
		wxITEM_RADIO )->Check( options.FontSize == 8 );
	menuFontSizes.Append( MenuId_FontSize_Large,	_("&Large"),	_t("Its nice and readable."),
		wxITEM_RADIO )->Check( options.FontSize == 10 );
	menuFontSizes.Append( MenuId_FontSize_Huge,		_("&Huge"),	_t("In case you have a really high res display."),
		wxITEM_RADIO )->Check( options.FontSize == 12 );

	menuFontSizes.AppendSeparator();
	menuFontSizes.Append( MenuId_ColorScheme_Light,	_("&Light theme"),	_t("Default soft-tone color scheme."), wxITEM_RADIO );
	menuFontSizes.Append( MenuId_ColorScheme_Dark,	_("&Dark theme"),	_t("Classic black color scheme for people who enjoy having text seared into their optic nerves."), wxITEM_RADIO );

	// The "Always on Top" option currently doesn't work
	//menuAppear.AppendSeparator();
	//menuAppear.Append( wxID_ANY, _("Always on Top"),
	//	_t("When checked the log window will be visible over other foreground windows."), wxITEM_CHECK );

	menuLog.Append(wxID_SAVE,	_("&Save..."),		_("Save log contents to file"));
	menuLog.Append(wxID_CLEAR,	_("C&lear"),		_("Clear the log window contents"));
	menuLog.AppendSeparator();
	menuLog.AppendCheckItem(MenuId_AutoDock, _("Auto&dock"), _("Dock log window to main PCSX2 window"))->Check(m_conf.AutoDock);
	menuLog.AppendSubMenu( &menuAppear, _("&Appearance") );
	menuLog.AppendSeparator();
	menuLog.Append(wxID_CLOSE,	_("&Close"),		_("Close this log window; contents are preserved"));

	// Source Selection/Toggle menu

	menuSources.Append( MenuId_LogSource_Devel, _("Dev/&Verbose"), _("Shows PCSX2 developer logs"), wxITEM_CHECK );
	menuSources.Append( MenuId_LogSource_CDVD_Info, _("&CDVD reads"), _("Shows disk read activity"), wxITEM_CHECK );
	
	menuSources.AppendSeparator();

	uint srcnt = ArraySize(ConLogSources);
	for (uint i=0; i<srcnt; ++i)
	{
		if (const BaseTraceLogSource* log = ConLogSources[i])
		{
			menuSources.Append( MenuId_LogSource_Start+i, log->GetName(), log->GetDescription(), wxITEM_CHECK );
			Bind(wxEVT_MENU, &ConsoleLogFrame::OnToggleSource, this, MenuId_LogSource_Start + i);
		}
		else
			menuSources.AppendSeparator();
	}

	menuSources.AppendSeparator();
	menuSources.Append( MenuId_LogSource_EnableAll,		_("&Enable all"),	_("Enables all log source filters.") );
	menuSources.Append( MenuId_LogSource_DisableAll,	_("&Disable all"),	_("Disables all log source filters.") );
	menuSources.Append( MenuId_LogSource_SetDefault,	_("&Restore Default"), _("Restore default source filters.") );

	pMenuBar->Append(&menuLog,		_("&Log"));
	pMenuBar->Append(&menuSources,	_("&Sources"));

	// status bar for menu prompts
	CreateStatusBar();

	SetSize( wxRect( options.DisplayPosition, options.DisplaySize ) );
	Show( options.Visible );

	// Bind Events:

	Bind(wxEVT_MENU, &ConsoleLogFrame::OnOpen, this, wxID_OPEN);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnClose, this, wxID_CLOSE);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnSave, this, wxID_SAVE);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnClear, this, wxID_CLEAR);

	Bind(wxEVT_MENU, &ConsoleLogFrame::OnFontSize, this, MenuId_FontSize_Small, MenuId_FontSize_Huge);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnToggleTheme, this, MenuId_ColorScheme_Light, MenuId_ColorScheme_Dark);

	Bind(wxEVT_MENU, &ConsoleLogFrame::OnAutoDock, this, MenuId_AutoDock);

	Bind(wxEVT_MENU, &ConsoleLogFrame::OnToggleSource, this, MenuId_LogSource_Devel);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnToggleCDVDInfo, this, MenuId_LogSource_CDVD_Info);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnEnableAllLogging, this, MenuId_LogSource_EnableAll);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnDisableAllLogging, this, MenuId_LogSource_DisableAll);
	Bind(wxEVT_MENU, &ConsoleLogFrame::OnSetDefaultLogging, this, MenuId_LogSource_SetDefault);

	Bind(wxEVT_CLOSE_WINDOW, &ConsoleLogFrame::OnCloseWindow, this);
	Bind(wxEVT_MOVE, &ConsoleLogFrame::OnMoveAround, this);
	Bind(wxEVT_SIZE, &ConsoleLogFrame::OnResize, this);
	Bind(wxEVT_ACTIVATE, &ConsoleLogFrame::OnActivate, this);

	Bind(pxEvt_SetTitleText, &ConsoleLogFrame::OnSetTitle, this);
	Bind(pxEvt_FlushQueue, &ConsoleLogFrame::OnFlushEvent, this);

	Bind(wxEVT_TIMER, &ConsoleLogFrame::OnFlushUnlockerTimer, this, m_timer_FlushUnlocker.GetId());

	if( m_threadlogger != NULL )
		m_threadlogger->Start();

	OnLoggingChanged();

	if (0==m_conf.Theme.CmpNoCase(L"Dark"))
	{
		pMenuBar->Check(MenuId_ColorScheme_Dark, true);
	}
	else //if ((0==m_conf.Theme.CmpNoCase("Default")) || (0==m_conf.Theme.CmpNoCase("Light")))
	{
		pMenuBar->Check(MenuId_ColorScheme_Light, true);
	}
}

ConsoleLogFrame::~ConsoleLogFrame()
{
	ScopedLogLock locker;
	wxGetApp().OnProgramLogClosed( GetId() );
}

void ConsoleLogFrame::OnEnableAllLogging(wxCommandEvent& evt)
{
	uint srcnt = ArraySize(ConLogSources);
	for (uint i=0; i<srcnt; ++i)
	{
		if (ConsoleLogSource* log = ConLogSources[i])
			log->Enabled = true;
	}
	DevConWriterEnabled = true;
	// Safe to change - it's read only in the core thread and only affects log output.
	const_cast<Pcsx2Config&>(EmuConfig).CdvdVerboseReads = g_Conf->EmuOptions.CdvdVerboseReads = true;

	OnLoggingChanged();
	evt.Skip();
}

void ConsoleLogFrame::OnDisableAllLogging(wxCommandEvent& evt)
{
	uint srcnt = ArraySize(ConLogSources);
	for (uint i=0; i<srcnt; ++i)
	{
		if (ConsoleLogSource* log = ConLogSources[i])
			log->Enabled = false;
	}
	DevConWriterEnabled = false;
	// Safe to change - it's read only in the core thread and only affects log output.
	const_cast<Pcsx2Config&>(EmuConfig).CdvdVerboseReads = g_Conf->EmuOptions.CdvdVerboseReads = false;

	OnLoggingChanged();
	evt.Skip();
}

void ConsoleLogFrame::OnSetDefaultLogging(wxCommandEvent& evt)
{
	uint srcnt = ArraySize(ConLogSources);
	for (uint i = 0; i<srcnt; ++i)
	{
		if (ConsoleLogSource* log = ConLogSources[i])
			log->Enabled = ConLogDefaults[i];
	}
	DevConWriterEnabled = false;
	// Safe to change - it's read only in the core thread and only affects log output.
	const_cast<Pcsx2Config&>(EmuConfig).CdvdVerboseReads = g_Conf->EmuOptions.CdvdVerboseReads = false;

	OnLoggingChanged();
	evt.Skip();
}

void ConsoleLogFrame::OnLoggingChanged()
{
	if (!GetMenuBar()) return;

	if( wxMenuItem* item = GetMenuBar()->FindItem(MenuId_LogSource_Devel) )
		item->Check( DevConWriterEnabled );

	if( wxMenuItem* item = GetMenuBar()->FindItem(MenuId_LogSource_CDVD_Info) )
		item->Check( g_Conf->EmuOptions.CdvdVerboseReads );

	uint srcnt = ArraySize(ConLogSources);
	for (uint i=0; i<srcnt; ++i)
	{
		if (const ConsoleLogSource* log = ConLogSources[i])
		{
			GetMenuBar()->Check( MenuId_LogSource_Start+i, log->IsActive() );
		}
#ifndef DISABLE_RECORDING
		GetMenuBar()->Enable( MenuId_LogSource_Start + MenuId_LogSources_Offset_recordingConsole, g_Conf->EmuOptions.EnableRecordingTools);
		GetMenuBar()->Enable( MenuId_LogSource_Start + MenuId_LogSources_Offset_controlInfo, g_Conf->EmuOptions.EnableRecordingTools);
#endif
	}
}

void ConsoleLogFrame::UpdateLogList()
{
	OnLoggingChanged();
}

// Implementation note:  Calls SetColor and Write( text ).  Override those virtuals
// and this one will magically follow suite. :)
bool ConsoleLogFrame::Write( ConsoleColors color, const wxString& text )
{
	pthread_testcancel();

	ScopedLock lock( m_mtx_Queue );

	if( m_QueueColorSection.GetLength() == 0 )
	{
		pxAssertMsg( m_CurQueuePos == 0, "Queue's character position didn't get reset in sync with it's ColorSection table." );
	}

	if( (m_QueueColorSection.GetLength() == 0) || ((color != Color_Current) && (m_QueueColorSection.GetLast().color != color)) )
	{
		++m_CurQueuePos;		// Don't overwrite the NULL;
		m_QueueColorSection.Add( ColorSection(color, m_CurQueuePos) );
	}

	int endpos = m_CurQueuePos + text.Length();
	m_QueueBuffer.MakeRoomFor( endpos + 1 );		// and the null!!
	memcpy( &m_QueueBuffer[m_CurQueuePos], text.wc_str(), sizeof(wxChar) * text.Length() );
	m_CurQueuePos = endpos;

	// this NULL may be overwritten if the next message sent doesn't perform a color change.
	m_QueueBuffer[m_CurQueuePos] = 0;

	// Idle events don't always pass (wx blocks them when moving windows or using menus, for
	// example).  So let's hackfix it so that an alternate message is posted if the queue is
	// "piling up."

	if( !m_pendingFlushMsg )
	{
		m_pendingFlushMsg = true;

		// wxWidgets may have aggressive locks on event processing, so best to release
		// our own mutex lest wx get hung for an extended period of time and cause all
		// of our own stuff to get sluggish.
		lock.Release();

		wxCommandEvent evt( pxEvt_FlushQueue );
		evt.SetInt( 0 );
		if( wxThread::IsMain() )
		{
			OnFlushEvent( evt );
			return false;
		}
		else
			GetEventHandler()->AddPendingEvent( evt );

		lock.Acquire();
	}

	if( !wxThread::IsMain() )
	{
		// Too many color changes causes huge slowdowns when decorating the rich textview, so
		// include a secondary check to avoid having a colorful log spam killing gui responsiveness.

		if( m_CurQueuePos > 0x100000 || m_QueueColorSection.GetLength() > 256 )
		{
			++m_WaitingThreadsForFlush;
			lock.Release();

			// Note: if the queue flushes, we need to return TRUE, so that our thread sleeps
			// until the main thread has had a chance to repaint the console window contents.
			// [TODO] : It'd be a lot better if the console window repaint released the lock
			//  once its task were complete, but thats been problematic, so for now this hack is
			//  what we get.
			if( m_sem_QueueFlushed.Wait( wxTimeSpan( 0,0,0,250 ) ) ) return true;

			// If we're here it means QueueFlush wait timed out, so remove us from the waiting
			// threads count. This way no thread permanently deadlocks against the console
			// logger.  They just run quite slow, but should remain responsive to user input.
			lock.Acquire();
			if( m_WaitingThreadsForFlush != 0 ) --m_WaitingThreadsForFlush;
		}
	}

	return false;
}

bool ConsoleLogFrame::Newline()
{
	return Write( Color_Current, L"\n" );
}

// =================================================================================
//  Section : Event Handlers
//    * Misc Window Events (Move, Resize,etc)
//    * Menu Events
//    * Logging Events
// =================================================================================

void ConsoleLogFrame::OnMoveAround( wxMoveEvent& evt )
{
	if( IsBeingDeleted() || !IsVisible() || IsIconized() ) return;

	// Docking check!  If the window position is within some amount
	// of the main window, enable docking.

	if( wxWindow* main = GetParent() )
	{
		wxPoint topright( main->GetRect().GetTopRight() );
		wxRect snapzone( topright - wxSize( 8,8 ), wxSize( 16,16 ) );

		m_conf.AutoDock = snapzone.Contains( GetPosition() );
		//Console.WriteLn( "DockCheck: %d", g_Conf->ConLogBox.AutoDock );
		if( m_conf.AutoDock )
		{
			SetPosition( topright + wxSize( 1,0 ) );
			m_conf.AutoDock = true;
		}
	}
	if (!IsMaximized())
		m_conf.DisplayPosition = GetPosition();
	evt.Skip();
}

void ConsoleLogFrame::OnResize( wxSizeEvent& evt )
{
	if (!IsMaximized())
		m_conf.DisplaySize = GetSize();
	evt.Skip();
}

// ----------------------------------------------------------------------------
// OnFocus / OnActivate : Special implementation to "connect" the console log window
// with the main frame window.  When one is clicked, the other is assured to be brought
// to the foreground with it.  (Currently only MSW only, as wxWidgets appears to have no
// equivalent to this). We don't bother with OnFocus here because it doesn't propagate
// up the window hierarchy anyway, so it always gets swallowed by the text control.
// But no matter: the console doesn't have the same problem as the Main Window of missing
// the initial activation event.

/*void ConsoleLogFrame::OnFocus( wxFocusEvent& evt )
{
	if( MainEmuFrame* mainframe = GetMainFramePtr() )
		MSW_SetWindowAfter( mainframe->GetHandle(), GetHandle() );

	evt.Skip();
}*/

void ConsoleLogFrame::OnActivate( wxActivateEvent& evt )
{
	if( MainEmuFrame* mainframe = GetMainFramePtr() )
		MSW_SetWindowAfter( mainframe->GetHandle(), GetHandle() );

	evt.Skip();
}
// ----------------------------------------------------------------------------

void ConsoleLogFrame::OnCloseWindow(wxCloseEvent& event)
{
	if( event.CanVeto() )
	{
		// instead of closing just hide the window to be able to Show() it later
		Show( false );

		// In the nogui case there might not be a Main frame window.
		if (MainEmuFrame* mainframe = GetMainFramePtr())
			mainframe->OnLogBoxHidden();
	}
	else
	{
		// This is sent when the app is exiting typically, so do a full close.
		m_threadlogger = NULL;
		wxGetApp().OnProgramLogClosed( GetId() );
		event.Skip();
	}
}

void ConsoleLogFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
{
	Show(true);
}

void ConsoleLogFrame::OnClose( wxCommandEvent& event )
{
	Close( false );
}

void ConsoleLogFrame::OnSave(wxCommandEvent& WXUNUSED(event))
{
	wxString filename;
	wxFile file;
	bool rc = OpenLogFile( file, filename, this );
	if ( !rc )
	{
		// canceled
		return;
	}

	// retrieve text and save it
	// -------------------------
	int nLines = m_TextCtrl.GetNumberOfLines();
	for ( int nLine = 0; nLine < nLines; nLine++ )
	{
		if( !file.Write(m_TextCtrl.GetLineText(nLine) + wxTextFile::GetEOL()) )
		{
			wxLogError( L"Can't save log contents to file." );
			return;
		}
	}
}

void ConsoleLogFrame::OnClear(wxCommandEvent& WXUNUSED(event))
{
	m_TextCtrl.Clear();
}

void ConsoleLogFrame::OnToggleCDVDInfo( wxCommandEvent& evt )
{
	evt.Skip();

	if (!GetMenuBar()) return;

	if (evt.GetId() == MenuId_LogSource_CDVD_Info)
	{
		if( wxMenuItem* item = GetMenuBar()->FindItem(evt.GetId()) )
		{
			g_Conf->EmuOptions.CdvdVerboseReads = item->IsChecked();
			const_cast<Pcsx2Config&>(EmuConfig).CdvdVerboseReads = g_Conf->EmuOptions.CdvdVerboseReads;		// read-only in core thread, so it's safe to modify.
		}
	}
}

void ConsoleLogFrame::OnToggleSource( wxCommandEvent& evt )
{
	evt.Skip();

	if (!GetMenuBar()) return;

	if (evt.GetId() == MenuId_LogSource_Devel)
	{
		if( wxMenuItem* item = GetMenuBar()->FindItem(evt.GetId()) )
			DevConWriterEnabled = item->IsChecked();

		return;
	}

	uint srcid = evt.GetId() - MenuId_LogSource_Start;

	if (!pxAssertDev( ArraySize(ConLogSources) > srcid, "Invalid source log index (out of bounds)" )) return;
	if (!pxAssertDev( ConLogSources[srcid] != NULL, "Invalid source log index (NULL pointer [separator])" )) return;

	if( wxMenuItem* item = GetMenuBar()->FindItem(evt.GetId()) )
	{
		pxAssertDev( item->IsCheckable(), "Uncheckable log source menu item?  Seems fishy!" );
		ConLogSources[srcid]->Enabled = item->IsChecked();
	}
}

void ConsoleLogFrame::OnToggleTheme( wxCommandEvent& evt )
{
	evt.Skip();

	const wxChar* newTheme = L"Default";

	switch( evt.GetId() )
	{
		case MenuId_ColorScheme_Light:
			newTheme = L"Default";
			m_ColorTable.SetColorScheme_Light();
			m_TextCtrl.SetBackgroundColour( wxColor( 230, 235, 242 ) );
		break;

		case MenuId_ColorScheme_Dark:
			newTheme = L"Dark";
			m_ColorTable.SetColorScheme_Dark();
			m_TextCtrl.SetBackgroundColour( wxColor( 24, 24, 24 ) );
		break;
	}

	if (0 == m_conf.Theme.CmpNoCase(newTheme)) return;
	m_conf.Theme = newTheme;

	m_ColorTable.SetFont( m_conf.FontSize );
	m_TextCtrl.SetDefaultStyle( m_ColorTable[Color_White] );
}

void ConsoleLogFrame::OnFontSize( wxCommandEvent& evt )
{
	evt.Skip();

	int ptsize = 8;
	switch( evt.GetId() )
	{
		case MenuId_FontSize_Small:		ptsize = 7; break;
		case MenuId_FontSize_Normal:	ptsize = 8; break;
		case MenuId_FontSize_Large:		ptsize = 10; break;
		case MenuId_FontSize_Huge:		ptsize = 12; break;
	}

	if( ptsize == m_conf.FontSize ) return;

	m_conf.FontSize = ptsize;
	m_ColorTable.SetFont( ptsize );
	m_TextCtrl.SetDefaultStyle( m_ColorTable[Color_White] );

	// TODO: Process the attributes of each character and upgrade the font size,
	// while still retaining color and bold settings...  (might be slow but then
	// it hardly matters being a once-in-a-bluemoon action).
}

void ConsoleLogFrame::OnAutoDock(wxCommandEvent& evt)
{
	if (auto menuBar = GetMenuBar())
		m_conf.AutoDock = menuBar->IsChecked(MenuId_AutoDock);
	evt.Skip();
}

// ----------------------------------------------------------------------------
//  Logging Events (typically received from Console class interfaces)
// ----------------------------------------------------------------------------

void ConsoleLogFrame::OnSetTitle( wxCommandEvent& event )
{
	SetTitle( event.GetString() );
}

void ConsoleLogFrame::DoFlushEvent( bool isPending )
{
	// recursion guard needed due to Mutex lock/acquire code below, which can end up yielding
	// to the gui and attempting to process more messages (which in turn would result in re-
	// entering this handler).

	static int recursion_counter = 0;
	RecursionGuard recguard( recursion_counter );
	if( recguard.IsReentrant() ) return;

	ScopedLock locker( m_mtx_Queue );

	if( m_CurQueuePos != 0 )
	{
		DoFlushQueue();
	}

	// Implementation note: I tried desperately to move this into wxEVT_IDLE, on the theory that
	// we don't actually want to wake up pending threads until after the GUI's finished all its
	// paperwork.  But wxEVT_IDLE doesn't work when you click menus or the title bar of a window,
	// making it pretty well annoyingly useless for just about anything. >_<

	if( m_WaitingThreadsForFlush > 0 )
	{
		do {
			m_sem_QueueFlushed.Post();
		} while( --m_WaitingThreadsForFlush > 0 );

		int count = m_sem_QueueFlushed.Count();
		while( count < 0 ) {
			m_sem_QueueFlushed.Post();
			count = m_sem_QueueFlushed.Count();
		}
	}

	m_pendingFlushMsg = isPending;
}

void ConsoleLogFrame::OnFlushUnlockerTimer( wxTimerEvent& )
{
	m_FlushRefreshLocked = false;
	DoFlushEvent( false );
}

void ConsoleLogFrame::OnFlushEvent( wxCommandEvent& )
{
	if( m_FlushRefreshLocked ) return;

	DoFlushEvent( true );

	m_FlushRefreshLocked = true;
	m_timer_FlushUnlocker.Start( 100, true );
}

void ConsoleLogFrame::DoFlushQueue()
{
	int len = m_QueueColorSection.GetLength();
	pxAssert( len != 0 );

	// Note, freezing/thawing actually seems to cause more overhead than it solves.
	// It might be useful if we're posting like dozens of messages, but in our case
	// we only post 1-4 typically, so better to leave things enabled.
	//m_TextCtrl.Freeze();

	// Manual InsertionPoint tracking avoids a lot of overhead in SetInsertionPointEnd()
	wxTextPos insertPoint = m_TextCtrl.GetLastPosition();

	// cap at 512k for now...
	// fixme - 512k runs well on win32 but appears to be very sluggish on linux (but that could
	// be a result of my using Xming + CoLinux).  Might need platform dependent defaults here. --air

	static const int BufferSize = 0x80000;
	if( (insertPoint + m_CurQueuePos) > BufferSize )
	{
		int toKeep = BufferSize - m_CurQueuePos;
		if( toKeep <= 10 )
		{
			m_TextCtrl.Clear();
			insertPoint = 0;
		}
		else
		{
			int toRemove = BufferSize - toKeep;
			if( toRemove < BufferSize / 4 ) toRemove = BufferSize;
			m_TextCtrl.Remove( 0, toRemove );
			insertPoint -= toRemove;
		}
	}

	m_TextCtrl.SetInsertionPoint( insertPoint );

	// fixme : Writing a lot of colored logs to the console can be quite slow when "spamming"
	// is happening, due to the overhead of SetDefaultStyle and WriteText calls.  I'm not sure
	// if there's a better way to go about this?  Using Freeze/Thaw helps a little bit, but it's
	// still magnitudes slower than dumping a straight run. --air

	if( len > 64 ) m_TextCtrl.Freeze();

	for( int i=0; i<len; ++i )
	{
		if( m_QueueColorSection[i].color != Color_Current )
			m_TextCtrl.SetDefaultStyle( m_ColorTable[m_QueueColorSection[i].color] );

		m_TextCtrl.WriteText( &m_QueueBuffer[m_QueueColorSection[i].startpoint] );
	}

	if( len > 64 ) m_TextCtrl.Thaw();

	m_QueueColorSection.Clear();
	m_CurQueuePos		= 0;
}

ConsoleLogFrame* Pcsx2App::GetProgramLog()
{
	return (ConsoleLogFrame*) wxWindow::FindWindowById( m_id_ProgramLogBox );
}

const ConsoleLogFrame* Pcsx2App::GetProgramLog() const
{
	return (const ConsoleLogFrame*) wxWindow::FindWindowById( m_id_ProgramLogBox );
}

void Pcsx2App::ProgramLog_PostEvent( wxEvent& evt )
{
	if( ConsoleLogFrame* proglog = GetProgramLog() )
		proglog->GetEventHandler()->AddPendingEvent( evt );
}

// --------------------------------------------------------------------------------------
//  ConsoleImpl_ToFile
// --------------------------------------------------------------------------------------

static void __concall ConsoleToFile_Newline()
{
#if defined(__unix__)
	if ((g_Conf) && (g_Conf->EmuOptions.ConsoleToStdio)) ConsoleWriter_Stdout.Newline();
#endif

#if defined(__unix__)
	fputc( '\n', emuLog );
#else
	fputs( "\r\n", emuLog );
#endif
}

static void __concall ConsoleToFile_DoWrite( const wxString& fmt )
{
#if defined(__unix__)
	if ((g_Conf) && (g_Conf->EmuOptions.ConsoleToStdio)) ConsoleWriter_Stdout.WriteRaw(fmt);
#endif

	px_fputs( emuLog, fmt.ToUTF8() );
}

static void __concall ConsoleToFile_DoWriteLn( const wxString& fmt )
{
	ConsoleToFile_DoWrite( fmt );
	ConsoleToFile_Newline();

	if (emuLog != NULL) fflush( emuLog );
}

static void __concall ConsoleToFile_SetTitle( const wxString& title )
{
	ConsoleWriter_Stdout.SetTitle(title);
}

static void __concall ConsoleToFile_DoSetColor( ConsoleColors color )
{
	ConsoleWriter_Stdout.DoSetColor(color);
}

extern const IConsoleWriter	ConsoleWriter_File;
const IConsoleWriter	ConsoleWriter_File =
{
	ConsoleToFile_DoWrite,
	ConsoleToFile_DoWriteLn,
	ConsoleToFile_DoSetColor,

	ConsoleToFile_DoWrite,
	ConsoleToFile_Newline,
	ConsoleToFile_SetTitle,

	0
};

Mutex& Pcsx2App::GetProgramLogLock()
{
	return m_mtx_ProgramLog;
}

// --------------------------------------------------------------------------------------
//  ConsoleToWindow Implementations
// --------------------------------------------------------------------------------------
template< const IConsoleWriter& secondary >
static void __concall ConsoleToWindow_SetTitle( const wxString& title )
{
	secondary.SetTitle(title);
	wxCommandEvent evt( pxEvt_SetTitleText );
	evt.SetString( title );
	wxGetApp().ProgramLog_PostEvent( evt );
}

template< const IConsoleWriter& secondary >
static void __concall ConsoleToWindow_DoSetColor( ConsoleColors color )
{
	secondary.DoSetColor(color);
}

template< const IConsoleWriter& secondary >
static void __concall ConsoleToWindow_Newline()
{
	secondary.Newline();

	ScopedLogLock locker;
	bool needsSleep = locker.WindowPtr && locker.WindowPtr->Newline();
	locker.Release();
	if( needsSleep ) wxGetApp().Ping();
}

template< const IConsoleWriter& secondary >
static void __concall ConsoleToWindow_DoWrite( const wxString& fmt )
{
	if( secondary.WriteRaw != NULL )
		secondary.WriteRaw( fmt );

	ScopedLogLock locker;
	bool needsSleep = locker.WindowPtr && locker.WindowPtr->Write( Console.GetColor(), fmt );
	locker.Release();
	if( needsSleep ) wxGetApp().Ping();
}

template< const IConsoleWriter& secondary >
static void __concall ConsoleToWindow_DoWriteLn( const wxString& fmt )
{
	if( secondary.DoWriteLn != NULL )
		secondary.DoWriteLn( fmt );

	ScopedLogLock locker;
	bool needsSleep = locker.WindowPtr && locker.WindowPtr->Write( Console.GetColor(), fmt + L'\n' );
	locker.Release();
	if( needsSleep ) wxGetApp().Ping();
}

typedef void __concall DoWriteFn(const wxString&);

static const IConsoleWriter	ConsoleWriter_Window =
{
	ConsoleToWindow_DoWrite<ConsoleWriter_Stdout>,
	ConsoleToWindow_DoWriteLn<ConsoleWriter_Stdout>,
	ConsoleToWindow_DoSetColor<ConsoleWriter_Stdout>,

	ConsoleToWindow_DoWrite<ConsoleWriter_Stdout>,
	ConsoleToWindow_Newline<ConsoleWriter_Stdout>,
	ConsoleToWindow_SetTitle<ConsoleWriter_Stdout>,

	0
};

static const IConsoleWriter	ConsoleWriter_WindowAndFile =
{
	ConsoleToWindow_DoWrite<ConsoleWriter_File>,
	ConsoleToWindow_DoWriteLn<ConsoleWriter_File>,
	ConsoleToWindow_DoSetColor<ConsoleWriter_File>,

	ConsoleToWindow_DoWrite<ConsoleWriter_File>,
	ConsoleToWindow_Newline<ConsoleWriter_File>,
	ConsoleToWindow_SetTitle<ConsoleWriter_File>,

	0
};

void Pcsx2App::EnableAllLogging()
{
	AffinityAssert_AllowFrom_MainUI();

	const bool logBoxOpen = (m_ptr_ProgramLog != NULL);
	const IConsoleWriter* newHandler = NULL;

	if( emuLog )
	{
		if( !m_StdoutRedirHandle ) m_StdoutRedirHandle = std::unique_ptr<PipeRedirectionBase>(NewPipeRedir(stdout));
		if( !m_StderrRedirHandle ) m_StderrRedirHandle = std::unique_ptr<PipeRedirectionBase>(NewPipeRedir(stderr));
		newHandler = logBoxOpen ? (IConsoleWriter*)&ConsoleWriter_WindowAndFile : (IConsoleWriter*)&ConsoleWriter_File;
	}
	else
	{
		if( logBoxOpen )
		{
			if (!m_StdoutRedirHandle) m_StdoutRedirHandle = std::unique_ptr<PipeRedirectionBase>(NewPipeRedir(stdout));
			if (!m_StderrRedirHandle) m_StderrRedirHandle = std::unique_ptr<PipeRedirectionBase>(NewPipeRedir(stderr));
			newHandler = &ConsoleWriter_Window;
		}
		else
			newHandler = &ConsoleWriter_Stdout;
	}
	Console_SetActiveHandler( *newHandler );
}

// Used to disable the emuLog disk logger, typically used when disabling or re-initializing the
// emuLog file handle.  Call SetConsoleLogging to re-enable the disk logger when finished.
void Pcsx2App::DisableDiskLogging() const
{
	AffinityAssert_AllowFrom_MainUI();

	const bool logBoxOpen = (GetProgramLog() != NULL);
	Console_SetActiveHandler( logBoxOpen ? (IConsoleWriter&)ConsoleWriter_Window : (IConsoleWriter&)ConsoleWriter_Stdout );

	// Semi-hack: It's possible, however very unlikely, that a secondary thread could attempt
	// to write to the logfile just before we disable logging, and would thus have a pending write
	// operation to emuLog file handle at the same time we're trying to re-initialize it.  The CRT
	// has some guards of its own, and PCSX2 itself typically suspends the "log happy" threads
	// when changing settings, so the chance for problems is low.  We minimize it further here
	// by sleeping off 5ms, which should allow any pending log-to-disk events to finish up.
	//
	// (the most correct solution would be a mutex lock in the Disk logger itself, but for now I
	//  am going to try and keep the logger lock-free and use this semi-hack instead).

	Threading::Sleep( 5 );
}

void Pcsx2App::DisableWindowLogging() const
{
	AffinityAssert_AllowFrom_MainUI();
	Console_SetActiveHandler( (emuLog!=NULL) ? (IConsoleWriter&)ConsoleWriter_File : (IConsoleWriter&)ConsoleWriter_Stdout );
}

void OSDlog(ConsoleColors color, bool console, const std::string& str)
{
	if (GSosdLog)
		GSosdLog(str.c_str(), wxGetApp().GetProgramLog()->GetRGBA(color));

	if (console)
		Console.WriteLn(color, str.c_str());
}

void OSDmonitor(ConsoleColors color, const std::string key, const std::string value) {
	if(!GSosdMonitor) return;

	GSosdMonitor(wxString(key).utf8_str(), wxString(value).utf8_str(), wxGetApp().GetProgramLog()->GetRGBA(color));
}

