#include "AScriptShell.h"
#include "Config.h"

AScript_IncludeModule(scribble)

//-----------------------------------------------------------------------------
// AScriptShell
//-----------------------------------------------------------------------------
AScriptShell::AScriptShell(wxWindow *pParent, wxWindowID id, long style) :
		wxTextCtrl(pParent, id, wxT(""), wxDefaultPosition, wxDefaultSize,
								style | wxWANTS_CHARS | wxTE_MULTILINE),
		_console(_sig, this), _env(0, NULL),
		_threadRunningFlag(false), _scrollViewFlag(false)
{
	do { // import(scribble)
		AScript::AScript_Module(scribble)::Import(_env, _sig);
		if (_sig.IsSignalled()) return;
	} while (0);
	_env.SetEchoFlag(true);
	_env.SetConsole(true, &_console);
	_env.SetConsole(false, &_console);
	_console.Println(_sig, AScript::GetOpening());
	_console.Print(_sig, _env.GetPrompt(_parser.IsContinued()));
	_pLineHistory = _lineHistory.rend();
	MarkLineTop();
	UpdateFont();
	UpdateColor();
}

AScriptShell::~AScriptShell()
{
}

void AScriptShell::UpdateFont()
{
	wxFont font(wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT));
	font.SetPointSize(Config::AScriptShell::ReadFontSize());
	SetFont(font);
}

void AScriptShell::UpdateColor()
{
	SetForegroundColour(wxColour(Config::AScriptShell::ReadFontColor()));
	SetBackgroundColour(wxColour(Config::AScriptShell::ReadBackgroundColor()));
}

void AScriptShell::EvalFile(const wxString &fileName)
{
	RunThread(new EvalFileThread(this, fileName));
}

BEGIN_EVENT_TABLE(AScriptShell, wxTextCtrl)
	EVT_KEY_DOWN(AScriptShell::OnKeyDown)
END_EVENT_TABLE()

void AScriptShell::OnKeyDown(wxKeyEvent &event)
{
	int keyCode = event.GetKeyCode();
	if (keyCode == 'D' && event.ControlDown()) {
		if (IsThreadRunning()) {
			_sig.SetSignal(AScript::SIGTYPE_Terminate, AScript::Value::Null);
		}
		return;
	}
	if (event.AltDown()) {
		if (keyCode == WXK_RETURN) {
			if (IsScrollView()) {
				LeaveScrollView();
			} else {
				*this << "\n";
			}
		} else {
			event.Skip();
		}
	} else if (IsThreadRunning()) {
		// nothing to do
	} else if (keyCode == WXK_RETURN) {
		if (IsScrollView()) {
			LeaveScrollView();
		} else {
			wxString str = GetRange(_posLineTop, GetLastPosition());
			*this << "\n";
			if (!str.empty() && (_lineHistory.empty() || _lineHistory.back() != str)) {
				_lineHistory.push_back(str);
			}
			str << "\n";
			RunThread(new EvalLineThread(this, str));
		}
	} else if (keyCode == WXK_ESCAPE) {
		if (IsScrollView()) {
			LeaveScrollView();
		} else {
			Replace(GetLineTopPoint(), GetLastPosition(), wxT(""));
		}
	} else if (keyCode == WXK_HOME) {
		if (IsScrollView()) {
			event.Skip();
		} else {
			SetInsertionPoint(GetLineTopPoint());
		}
	} else if (keyCode == WXK_END) {
		if (IsScrollView()) {
			event.Skip();
		} else {
			SetInsertionPoint(GetLastPosition());
		}
	} else if (keyCode == WXK_BACK) {
		if (IsScrollView()) {
			event.Skip();
		} else {
			if (GetInsertionPoint() > GetLineTopPoint()) event.Skip();
		}
	} else if (keyCode == WXK_LEFT) {
		if (IsScrollView()) {
			event.Skip();
		} else {
			if (GetInsertionPoint() > GetLineTopPoint()) event.Skip();
		}
	} else if (keyCode == WXK_RIGHT) {
		event.Skip();
	} else if (keyCode == WXK_UP) {
		if (IsScrollView()) {
			event.Skip();
		} else if (event.ControlDown()) {
			EnterScrollView();
		} else if (_lineHistory.empty()) {
			// nothing to do
		} else if (_pLineHistory == _lineHistory.rend()) {
			_pLineHistory = _lineHistory.rbegin();
			Replace(GetLineTopPoint(), GetLastPosition(), *_pLineHistory);
		} else if (_pLineHistory + 1 != _lineHistory.rend()) {
			_pLineHistory++;
			Replace(GetLineTopPoint(), GetLastPosition(), *_pLineHistory);
		}
	} else if (keyCode == WXK_DOWN) {
		if (IsScrollView()) {
			event.Skip();
		} else if (_pLineHistory == _lineHistory.rend()) {
			// nothing to do
		} else if (_pLineHistory == _lineHistory.rbegin()) {
			_pLineHistory = _lineHistory.rend();
			Replace(GetLineTopPoint(), GetLastPosition(), wxT(""));
		} else {
			_pLineHistory--;
			Replace(GetLineTopPoint(), GetLastPosition(), *_pLineHistory);
		}
	} else {
		event.Skip();
	}
}

void AScriptShell::EnterScrollView()
{
	SetForegroundColour(wxColour(128, 128, 128));
	Refresh();
	Update();
	_scrollViewFlag = true;
}

void AScriptShell::LeaveScrollView()
{
	SetForegroundColour(*wxBLACK);
	Refresh();
	Update();
	_scrollViewFlag = false;
	SetInsertionPoint(GetLastPosition());
}

void AScriptShell::RunThread(wxThread *pThread)
{
	_threadRunningFlag = true;
	pThread->Create();
	pThread->Run();
}

void AScriptShell::NotifyEndOfThread()
{
	_pLineHistory = _lineHistory.rend();
	_console.Print(_sig, _env.GetPrompt(_parser.IsContinued()));
	ShowPosition(GetLastPosition());
	MarkLineTop();
	_threadRunningFlag = false;
}

//-----------------------------------------------------------------------------
// AScriptShell::StreamEx
//-----------------------------------------------------------------------------
AScriptShell::StreamEx::StreamEx(AScript::Signal sig, AScriptShell *pAScriptShell) :
	AScript::Stream(sig, AScript::Stream::ATTR_Writable), _pAScriptShell(pAScriptShell)
{
	InstallCodec("cp932", true);
}

const char *AScriptShell::StreamEx::GetName() const
{
	return "<ascript-shell>";
}

void AScriptShell::StreamEx::DoPutChar(AScript::Signal sig, char ch)
{
	_str << ch;
}

void AScriptShell::StreamEx::FlushConsole()
{
	*_pAScriptShell << _str;
	_str.clear();
}

//-----------------------------------------------------------------------------
// AScriptShell::EvalLineThread
//-----------------------------------------------------------------------------
AScriptShell::EvalLineThread::EvalLineThread(AScriptShell *pAScriptShell, const wxString &str) :
	wxThread(wxTHREAD_DETACHED), _pAScriptShell(pAScriptShell), _str(str)
{
}

wxThread::ExitCode AScriptShell::EvalLineThread::Entry()
{
	AScript::Environment &env = _pAScriptShell->GetEnvironment();
	AScript::Signal &sig = _pAScriptShell->GetSignal();
	AScript::Parser &parser = _pAScriptShell->GetParser();
	foreach_const (wxString, p, _str) {
		parser.EvalConsoleChar(env, sig, *p);
		if (sig.IsSignalled()) break;
	}
	_pAScriptShell->NotifyEndOfThread();
	return 0;
}

//-----------------------------------------------------------------------------
// AScriptShell::EvalFileThread
//-----------------------------------------------------------------------------
AScriptShell::EvalFileThread::EvalFileThread(AScriptShell *pAScriptShell, const wxString &fileName) :
	wxThread(wxTHREAD_DETACHED), _pAScriptShell(pAScriptShell), _fileName(fileName)
{
}

wxThread::ExitCode AScriptShell::EvalFileThread::Entry()
{
	AScript::Environment &env = _pAScriptShell->GetEnvironment();
	AScript::Signal &sig = _pAScriptShell->GetSignal();
	AScript::Stream &console = *env.GetConsole(false);
	console.Println(sig, "");
	AScript::Expr *pExpr = AScript::Parser().ParseStream(env, sig, _fileName.c_str());
	if (sig.IsSignalled()) {
		console.PrintSignal(sig, sig);
		sig.ClearSignal();
	} else {
		AScript::String dirOrg = AScript::OAL::GetCurDir();
		AScript::OAL::ChangeCurDir(AScript::Directory::ExtractDirName(_fileName.c_str()).c_str());
		AScript::Environment envLocal(&env, AScript::ENVTYPE_Local);
		envLocal.SetEchoFlag(false);
		pExpr->Exec(envLocal, sig);
		AScript::OAL::ChangeCurDir(dirOrg.c_str());
		envLocal.SetEchoFlag(true);
		if (sig.IsSignalled()) {
			console.PrintSignal(sig, sig);
			sig.ClearSignal();
		}
	}
	_pAScriptShell->NotifyEndOfThread();
	return 0;
}
