//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// copyright            : (C) 2008 by Eran Ifrah
// file name            : unittestpp.cpp
//
// -------------------------------------------------------------------------
// A
//              _____           _      _     _ _
//             /  __ \         | |    | |   (_) |
//             | /  \/ ___   __| | ___| |    _| |_ ___
//             | |    / _ \ / _  |/ _ \ |   | | __/ _ )
//             | \__/\ (_) | (_| |  __/ |___| | ||  __/
//              \____/\___/ \__,_|\___\_____/_|\__\___|
//
//                                                  F i l e
//
//    This program 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.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
#include <wx/app.h>
#include "bitmap_loader.h"
#include "unittestspage.h"
#include "macros.h"
#include "unittestdata.h"
#include "ctags_manager.h"
#include "unittestcppoutputparser.h"
#include <wx/tokenzr.h>
#include "environmentconfig.h"
#include "pipedprocess.h"
#include "dirsaver.h"
#include "procutils.h"
#include "workspace.h"
#include "project.h"
#include "testclassdlg.h"
#include "newunittestdlg.h"
#include <wx/ffile.h>
#include <wx/msgdlg.h>
#include "unittestpp.h"
#include <wx/xrc/xmlres.h>
#include <wx/menuitem.h>
#include <wx/menu.h>

#ifdef __WXMSW__
#include <wx/msw/registry.h>
#include "evnvarlist.h"
#endif

static UnitTestPP* thePlugin = NULL;

//Define the plugin entry point
extern "C" EXPORT IPlugin *CreatePlugin(IManager *manager)
{
	if (thePlugin == 0) {
		thePlugin = new UnitTestPP(manager);
	}
	return thePlugin;
}

extern "C" EXPORT PluginInfo GetPluginInfo()
{
	PluginInfo info;
	info.SetAuthor(wxT("Eran Ifrah"));
	info.SetName(wxT("UnitTestPP"));
	info.SetDescription(wxT("A Unit test plugin based on the UnitTest++ framework"));
	info.SetVersion(wxT("v1.0"));
	return info;
}

extern "C" EXPORT int GetPluginInterfaceVersion()
{
	return PLUGIN_INTERFACE_VERSION;
}

UnitTestPP::UnitTestPP(IManager *manager)
		: IPlugin(manager)
		, m_proc(NULL)
{
	m_longName = wxT("A Unit test plugin based on the UnitTest++ framework");
	m_shortName = wxT("UnitTestPP");
	m_topWindow = m_mgr->GetTheApp();
}

UnitTestPP::~UnitTestPP()
{
}

clToolBar *UnitTestPP::CreateToolBar(wxWindow *parent)
{
	//support both toolbars icon size
	clToolBar *tb(NULL);

	if (m_mgr->AllowToolbar()) {
		int size = m_mgr->GetToolbarIconSize();

		tb = new clToolBar(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, clTB_DEFAULT_STYLE);
		tb->SetToolBitmapSize(wxSize(size, size));

		BitmapLoader &bitmapLoader = *m_mgr->GetStdIcons();

		if (size == 24) {
			tb->AddTool(XRCID("run_unit_tests"), wxT("Run Unit tests..."), bitmapLoader.LoadBitmap(wxT("toolbars/24/unittest++/run_as_unittest")), wxT("Run project as unit test project..."));

		} else {
			tb->AddTool(XRCID("run_unit_tests"), wxT("Run Unit tests..."), bitmapLoader.LoadBitmap(wxT("toolbars/16/unittest++/run_as_unittest")), wxT("Run project as unit test project..."));
		}
		tb->Realize();
	}

	//Connect the events to us
	parent->Connect(XRCID("run_unit_tests"), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(UnitTestPP::OnRunUnitTests), NULL, (wxEvtHandler*)this);
	parent->Connect(XRCID("run_unit_tests"), wxEVT_UPDATE_UI, wxUpdateUIEventHandler(UnitTestPP::OnRunUnitTestsUI), NULL, (wxEvtHandler*)this);
	return tb;
}

void UnitTestPP::CreatePluginMenu(wxMenu *pluginsMenu)
{
	//Create the popup menu for the file explorer
	//The only menu that we are interseted is the file explorer menu
	wxMenu* menu = new wxMenu();
	wxMenuItem *item(NULL);

	item = new wxMenuItem(menu, XRCID("unittestpp_new_simple_test"), wxT("Create new &test..."), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	item = new wxMenuItem(menu, XRCID("unittestpp_new_class_test"), wxT("Create tests for &class..."), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	menu->AppendSeparator();

	item = new wxMenuItem(menu, XRCID("run_unit_tests"), wxT("Run Project as UnitTest++ and report"), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	pluginsMenu->Append(wxID_ANY, wxT("UnitTest++"), menu);

	//connect the events
	m_topWindow->Connect(XRCID("unittestpp_new_simple_test"), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(UnitTestPP::OnNewSimpleTest), NULL, (wxEvtHandler*)this);
	m_topWindow->Connect(XRCID("unittestpp_new_class_test"), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(UnitTestPP::OnNewClassTest), NULL, (wxEvtHandler*)this);
}

void UnitTestPP::HookPopupMenu(wxMenu *menu, MenuType type)
{
	if (type == MenuTypeEditor) {
		menu->Append(XRCID("UNITTESTPP_EDITOR_POPUP"), wxT("UnitTest++"), CreateEditorPopMenu());
	}
}

void UnitTestPP::UnHookPopupMenu(wxMenu *menu, MenuType type)
{
	if (type == MenuTypeEditor) {
		wxMenuItem *item = menu->FindItem(XRCID("UNITTESTPP_EDITOR_POPUP"));
		if (item) {
			menu->Destroy(item);
		}
	}
}

void UnitTestPP::UnPlug()
{
}


wxMenu *UnitTestPP::CreateEditorPopMenu()
{
	//Create the popup menu for the file explorer
	//The only menu that we are interseted is the file explorer menu
	wxMenu* menu = new wxMenu();
	wxMenuItem *item(NULL);

	item = new wxMenuItem(menu, XRCID("unittestpp_new_simple_test"), wxT("Create new &test..."), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	item = new wxMenuItem(menu, XRCID("unittestpp_new_class_test"), wxT("Create tests for &class..."), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	return menu;
}

void UnitTestPP::OnNewClassTest(wxCommandEvent& e)
{
	wxUnusedVar(e);

	if (GetUnitTestProjects().empty()) {
		if (wxMessageBox(wxString::Format(wxT("There are currently no UnitTest project in your workspace\nWould you like to create one now?")), wxT("CodeLite"), wxYES_NO|wxCANCEL) == wxYES) {
			// add new UnitTest project
			wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, XRCID("new_project"));
			m_mgr->GetTheApp()->GetTopWindow()->GetEventHandler()->AddPendingEvent(event);
		}
		return;
	}

	//position has changed, compare line numbers
	wxString clsName;
	IEditor *editor = m_mgr->GetActiveEditor();
	if (editor) {
		int line = editor->GetCurrentLine();
		TagEntryPtr tag = m_mgr->GetTagsManager()->FunctionFromFileLine(editor->GetFileName(), line+1);
		if ( tag && tag->GetScope().IsEmpty() == false && tag->GetScope() != wxT("<global>") ) {
			clsName = tag->GetScope();
		}
	}

	TestClassDlg dlg(m_mgr->GetTheApp()->GetTopWindow(), m_mgr, this);
	dlg.SetClassName(clsName);

	if (dlg.ShowModal() == wxID_OK) {
		wxArrayString arr         = dlg.GetTestsList();
		wxString      fixture     = dlg.GetFixtureName();
		wxString      filename    = dlg.GetFileName();
		wxString      projectName = dlg.GetProjectName();

		wxFileName fn(filename);
		wxString err_msg;

		fixture.Trim().Trim(false);
		ProjectPtr p = m_mgr->GetWorkspace()->FindProjectByName(projectName, err_msg);
		if (p) {
			// incase a relative path was given, use the selected project path
			fn = FindBestSourceFile(p, fn);
			for (size_t i=0; i<arr.GetCount(); i++) {

				// Construct the test name in the format of:
				// Test<FuncName>
				wxString name = arr.Item(i);
				wxString prefix = name.Mid(0, 1);

				name = name.Mid(1);
				prefix.MakeUpper();
				prefix << name;

				wxString testName;
				testName << wxT("Test") << prefix;

				if (!fixture.IsEmpty()) {
					DoCreateFixtureTest(testName, fixture, projectName, fn.GetFullPath());
				} else {
					DoCreateSimpleTest(testName, projectName, fn.GetFullPath());
				}

			}
		}
	}
}

void UnitTestPP::OnNewSimpleTest(wxCommandEvent& e)
{
	wxUnusedVar(e);

	if (GetUnitTestProjects().empty()) {
		if (wxMessageBox(wxString::Format(wxT("There are currently no UnitTest project in your workspace\nWould you like to create one now?")), wxT("CodeLite"), wxYES_NO|wxCANCEL) == wxYES) {
			// add new UnitTest project
			wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, XRCID("new_project"));
			m_mgr->GetTheApp()->GetTopWindow()->GetEventHandler()->AddPendingEvent(event);
		}
		return;
	}

	NewUnitTestDlg dlg(m_mgr->GetTheApp()->GetTopWindow(), this, m_mgr->GetConfigTool());
	if (dlg.ShowModal() == wxID_OK) {
		// create the unit test
		wxString testName    = dlg.GetTestName();
		wxString fixture     = dlg.GetFixtureName();
		wxString projectName = dlg.GetProjectName();
		wxString filename    = dlg.GetFilename();

		// incase a relative path was given, use the selected project path
		wxFileName fn(filename);
		wxString err_msg;
		ProjectPtr p = m_mgr->GetWorkspace()->FindProjectByName(projectName, err_msg);
		if (p) {
			fn = FindBestSourceFile(p, fn);
			fixture.Trim().Trim(false);
			if (!fixture.IsEmpty()) {
				DoCreateFixtureTest(testName, fixture, projectName, fn.GetFullPath());
			} else {
				DoCreateSimpleTest(testName, projectName, fn.GetFullPath());
			}
		}
	}
}

void UnitTestPP::DoCreateFixtureTest(const wxString& name, const wxString& fixture, const wxString &projectName, const wxString &filename)
{
	wxString text;

	text << wxT("\nTEST_FIXTURE(") << fixture << wxT(", ") << name << wxT(")\n");
	text << wxT("{\n");
	text << wxT("}\n");

	IEditor *editor = DoAddTestFile(filename, projectName);
	if (editor) {
		editor->AppendText(text);
	}
}

void UnitTestPP::DoCreateSimpleTest(const wxString& name, const wxString &projectName, const wxString &filename)
{
	// try to locate the file
	wxString errMsg;
	ProjectPtr proj = m_mgr->GetWorkspace()->FindProjectByName(projectName, errMsg);
	if (!proj) {
		// no such project!
		wxMessageBox(wxT("Could not find the target project"), wxT("CodeLite"), wxOK|wxICON_ERROR);
		return;
	}

	IEditor *editor = DoAddTestFile(filename, projectName);
	wxString text;

	text << wxT("\nTEST(") << name << wxT(")\n");
	text << wxT("{\n");
	text << wxT("}\n");

	if (editor) {
		editor->AppendText(text);
	}
}

void UnitTestPP::OnRunUnitTests(wxCommandEvent& e)
{
	wxString projectName = m_mgr->GetWorkspace()->GetActiveProjectName();
	wxString wd, err_msg;
	wxString cmd = m_mgr->GetProjectExecutionCommand(projectName, wd);

	DirSaver ds;

	// first we need to CD to the project directory
	ProjectPtr p = m_mgr->GetWorkspace()->FindProjectByName(projectName, err_msg);
	if (p) {
		::wxSetWorkingDirectory(p->GetFileName().GetPath());
	}

	// now change the directory
	wxSetWorkingDirectory(wd);

	//m_proc will be deleted upon termination
	m_proc = new PipedProcess(wxNewId(), cmd);
	if (m_proc) {

		//set the environment variables
		EnvSetter env(m_mgr->GetEnv());

		if (m_proc->Start() == 0) {

			//failed to start the process
			delete m_proc;
			m_proc = NULL;
			return;
		}
		m_proc->Connect(wxEVT_END_PROCESS, wxProcessEventHandler(UnitTestPP::OnProcessTerminated), NULL, this);
	}
}

void UnitTestPP::OnRunUnitTestsUI(wxUpdateUIEvent& e)
{
	CHECK_CL_SHUTDOWN();
	bool activeProjIsUT(false);
	if(m_mgr->GetWorkspace()) {
		wxString errMsg;
		wxString projectName = m_mgr->GetWorkspace()->GetActiveProjectName();
		ProjectPtr p = m_mgr->GetWorkspace()->FindProjectByName(projectName, errMsg);
		if(p){
			activeProjIsUT = p->GetProjectInternalType() == wxT("UnitTest++");
		}
	}
	e.Enable(m_mgr->IsWorkspaceOpen() && !m_proc && activeProjIsUT);
}

void UnitTestPP::OnProcessTerminated(wxProcessEvent& e)
{
	wxString output;
	m_proc->ReadAll(output);
	delete m_proc;
	m_proc = NULL;

	wxArrayString arr = wxStringTokenize(output, wxT("\r\n"));
	UnitTestCppOutputParser parser(arr);

	// parse the results
	TestSummary summary;
	parser.Parse( &summary );

	// create new report page, and add it to the editor
	static int counter(1);

	if (summary.totalTests == 0) {
		return;
	}

	UnitTestsPage *page = new UnitTestsPage(m_mgr->GetDockingManager()->GetManagedWindow(), &summary, m_mgr);
	m_mgr->AddPage(page, wxString::Format(wxT("UnitTest++ Report <%d>"), counter), wxNullBitmap, true);
	counter++;

	wxString msg;
	double errCount = summary.errorCount;
	double totalTests = summary.totalTests;

	double err_percent = (errCount / totalTests)*100;
	double pass_percent = ((totalTests - errCount) / totalTests)*100;
	msg << err_percent << wxT("%");
	page->UpdateFailedBar((size_t)summary.errorCount, msg);

	msg.clear();
	msg << pass_percent << wxT("%");
	page->UpdatePassedBar((size_t)(summary.totalTests - summary.errorCount), msg);

}

std::vector<ProjectPtr> UnitTestPP::GetUnitTestProjects()
{
	std::vector<ProjectPtr> ut_projects;
	wxArrayString projects;
	m_mgr->GetWorkspace()->GetProjectList(projects);
	for (size_t i=0; i<projects.GetCount(); i++) {
		wxString err_msg;
		ProjectPtr proj = m_mgr->GetWorkspace()->FindProjectByName(projects.Item(i), err_msg);
		if (proj && IsUnitTestProject(proj)) {
			ut_projects.push_back(proj);
		}
	}
	return ut_projects;
}

bool UnitTestPP::IsUnitTestProject(ProjectPtr p)
{
	if (!p) {
		return false;
	}
	return p->GetProjectInternalType() == wxT("UnitTest++");
}

IEditor *UnitTestPP::DoAddTestFile(const wxString& filename, const wxString &projectName)
{
	// first open / create the target file
	wxFileName fn(filename);
	if ( wxFileName::FileExists(filename) == false ) {
		// the file does not exist!
		wxFFile file(filename, wxT("wb"));
		if ( !file.IsOpened() ) {
			wxMessageBox(wxString::Format(wxT("Could not create target file '%s'"), filename.c_str()), wxT("CodeLite"), wxICON_WARNING|wxOK);
			return NULL;
		}

		// since this is a new file, it will most probably will need the include file
		file.Write(wxT("#include <UnitTest++.h>\n"));
		file.Close();

	}

	// locate the project
	wxString errMsg;
	IEditor *editor(NULL);

	ProjectPtr proj = m_mgr->GetWorkspace()->FindProjectByName(projectName, errMsg);
	if (proj) {
		std::vector<wxFileName> files;
		proj->GetFiles(files, true);

		// Search the target file, if it is already exist in the project, open the file
		// and return
		for (size_t i=0; i<files.size(); i++) {
			if (files.at(i) == fn) {
				m_mgr->OpenFile(fn.GetFullPath());
				editor = m_mgr->GetActiveEditor();
				if (editor && editor->GetFileName() == fn) {
					return editor;
				} else {
					return NULL;
				}
			}
		}

		// add it to the project
		wxArrayString paths;
		paths.Add(filename);
		m_mgr->CreateVirtualDirectory(proj->GetName(), wxT("tests"));
		m_mgr->AddFilesToVirtualFolder(proj->GetName() + wxT(":tests"), paths);

		// open the file
		m_mgr->OpenFile(fn.GetFullPath());
		editor = m_mgr->GetActiveEditor();
		if (editor && editor->GetFileName() == fn) {
			return editor;
		}
	}
	return NULL;
}

wxFileName UnitTestPP::FindBestSourceFile(ProjectPtr proj, const wxFileName &filename)
{
	if (filename.IsOk() == false) {
		// no such file
		std::vector<wxFileName> files;
		proj->GetFiles(files, true);

		// Search the target file, if it is already exist in the project, open the file
		// and return
		for (size_t i=0; i<files.size(); i++) {
			wxFileName fn = files.at(i);
			if (IsSourceFile(fn.GetExt())) {
				return fn;
			}
		}
		// no source file were found in the project
		// create a path name of the file which will be located
		// under the selected project path (we dont create it here)
		wxFileName fn(proj->GetFileName());
		fn.SetFullName(wxT("unit_tests.cpp"));
		return fn;
	} else if (filename.IsAbsolute() == false) {
		// relative path was given, set the path to the project path
		wxFileName fn(filename);
		fn.SetPath(proj->GetFileName().GetPath());
		return fn;
	} else {
		return filename;
	}
}
