#include "FishIDE.h"

#include "Settings.h"
#include "SettingsDialogs.h"
#include "ExampleManager.h"
#include "LibraryManager.h"
#include "BoardManager.h"
#include "FileTools.h"

#include "FishIDEEditor.h"
#include "FileFinder.h"

#define KEYGROUPNAME "Ide"
#define KEYNAMESPACE IdeKeys
#define KEYFILE      <FishIDE/FishIDE.key>
#include             <CtrlLib/key_source.h>

using namespace IdeKeys;

void FishIDE::DoEditKeys()
{
	EditKeys();
	AKEditor();
}

void FishIDE::AKEditor()
{
	CodeEditor::find_next_key = AK_FINDNEXT().key[0];
	CodeEditor::find_prev_key = AK_FINDPREV().key[0];
	CodeEditor::replace_key = AK_DOREPLACE().key[0];
}


bool FishIDE::ProjectsAvail(void)
{
	return projectsCtrl.GetCurrentProject();
}

void FishIDE::ProjectNewCb()
{
	FileSel fs;
	fs.Type("FishIDE files", "*.fish *.ino");
	fs.ActiveDir(Settings.GetSketchFolder());
	fs.NoAsking();
	while(true)
	{
		if(fs.ExecuteOpen(t_("New project:")))
		{
			// get file path
			String path = fs.Get();
			
			// get project title
			String title = GetFileTitle(path);
			
			// get project folder
			String folder = GetFileFolder(path);
			String folderTitle = GetFileTitle(folder);
			
			// if folder title is same as project name
			// we're already inside correct path
			// otherwise we shall create a new folder
			if(folderTitle != title)
				folder = AppendFileName(folder, title);
			if(
				DirectoryExists(folder) &&
				(
					FileTools::ScanFolder(folder, "*", false).GetCount() ||
					FileTools::ScanFolder(folder, "*", true).GetCount()
				)
			)
			{
				Exclamation(Format(t_("Project '%s' already exists&Please choose another name"), title));
				continue;
			}
			
			// folder is ok, create it and create default project content
			path = ForceExt(AppendFileName(folder, title), ".fish");
			projectsCtrl.NewProject(path, true);
			
			break;
		}
		else
			break;
	}
}

void FishIDE::ProjectOpenCb()
{
	FileSel fs;
	fs.Type("FishIDE files", "*.fish *.ino");
	fs.ActiveDir(Settings.GetSketchFolder());
	if(fs.ExecuteOpen(t_("Open project:")))
	{
		String p = fs.Get();
		String typ = ToLower(GetFileExt(p));
		if(typ != ".fish" && typ != ".ino")
		{
			Exclamation(t_("Not a FishIDE project"));
			return;
		}
		if(!projectsCtrl.OpenProject(p))
			Exclamation(t_("Error opening project"));
	}
}

void FishIDE::ProjectOpenManagedExampleCb(Bar &bar, bool inPlace)
{
	VectorMap<String, Vector<String>> cats = GatherManagedExamples();
	for(int iCat = 0; iCat < cats.GetCount(); iCat++)
	{
		String cat = cats.GetKey(iCat);
		Vector<String> &examples = cats[iCat];
		if(examples.GetCount())
		{
			bar.Sub(cat, [=,ex = pick(examples)](Bar &bar)
				{
					for(int j = 0; j < ex.GetCount(); j++)
					{
						String examplePath = ex[j];
						String exampleName = GetFileTitle(examplePath);
						bar.Add(exampleName, [=](){ OpenExample(examplePath, inPlace); });
					}
				}
			);
		}
	}
}

void FishIDE::ProjectOpenLibExampleCb(Bar &bar, String const &libsRoot, bool inPlace)
{
	VectorMap<String, Vector<String>> libs = GatherLibrariesExamples(libsRoot);
	for(int iLib = 0; iLib < libs.GetCount(); iLib++)
	{
		String lib = libs.GetKey(iLib);
		Vector<String> &examples = libs[iLib];
		if(examples.GetCount())
		{
			bar.Sub(lib, [=,ex = pick(examples)](Bar &bar)
				{
					for(int j = 0; j < ex.GetCount(); j++)
					{
						String examplePath = ex[j];
						String exampleName = GetFileTitle(examplePath);
						bar.Add(exampleName, [=](){ OpenExample(examplePath, inPlace); });
					}
				}
			);
		}
	}
}

void FishIDE::ProjectOpenPlatformLibExampleCb(Bar &bar, bool inPlace)
{
	ProjectOpenLibExampleCb(bar, Settings.GetPlatformLibsRoot(), inPlace);
}

void FishIDE::ProjectOpenSystemLibExampleCb(Bar &bar, bool inPlace)
{
	ProjectOpenLibExampleCb(bar, Settings.GetSystemLibsRoot(), inPlace);
}

void FishIDE::ProjectOpenUserLibExampleCb(Bar &bar, bool inPlace)
{
	ProjectOpenLibExampleCb(bar, Settings.GetUserLibsRoot(), inPlace);
}

void FishIDE::ProjectOpenExampleMenuCb(Bar &bar, bool inPlace)
{
	bar.Add(t_("Downloaded examples"), THISBACK1(ProjectOpenManagedExampleCb, inPlace));
	bar.Separator();
	bar.Add(t_("Platform libraries examples"), THISBACK1(ProjectOpenPlatformLibExampleCb, inPlace));
	bar.Add(t_("System libraries examples"), THISBACK1(ProjectOpenSystemLibExampleCb, inPlace));
	bar.Add(t_("User libraries examples"), THISBACK1(ProjectOpenUserLibExampleCb, inPlace));
}

void FishIDE::ProjectOpenExampleToolbarCb(Bar &bar)
{
	bar.Add(t_("Import example"), FishIDEImg::ImportSample(), [=,&bar]()
		{
			Point p = bar.GetScreenRect().BottomLeft();
			p.x = GetMousePos().x;
	        MenuBar::Execute([=](Bar& bar) { ProjectOpenExampleMenuCb(bar, false); }, p);
        }
    );
	bar.Add(t_("Open example in place"), FishIDEImg::OpenSample(), [=,&bar]()
		{
			Point p = bar.GetScreenRect().BottomLeft();
			p.x = GetMousePos().x;
	        MenuBar::Execute([=](Bar& bar) { ProjectOpenExampleMenuCb(bar, true); }, p);
        }
    );
}

void FishIDE::ProjectSaveCb()
{
	Project *proj = GetCurrentProject();
	if(!proj)
		return;
	if(!proj->Save())
		Exclamation(t_("Error saving project"));
}

void FishIDE::ProjectSaveAsCb()
{
	Project *proj = GetCurrentProject();
	if(!proj)
		return;
	
	FileSel fs;
	fs.DefaultExt("fish");
	fs.ActiveDir(Settings.GetSketchFolder());
	if(fs.ExecuteSaveAs(t_("Save project as:")))
	{
		String p = fs.Get();
		String projName = GetFileTitle(p);
		String projFolder = GetFileFolder(p);
		
		// check if project is saved into a folder of same name
		// otherwise create it
		String projFolderName = GetFileTitle(projFolder);
		if(projFolderName != projName)
			projFolder = AppendFileName(projFolder, projName);
		RealizeDirectory(projFolder);
		String projPath = ForceExt(AppendFileName(projFolder, projName), ".tai");
		if(!projectsCtrl.SaveProjectAs(proj, projPath))
			Exclamation(t_("Error saving project"));
	}
}

void FishIDE::ProjectPropertiesCb()
{
}

void FishIDE::ProjectExitCb()
{
	Close();
}

void FishIDE::ProjectMenu(Bar &bar)
{
	bar.Add(t_("New"), FishIDEImg::New(), THISBACK(ProjectNewCb));
	bar.Separator();
	bar.Add(t_("Open"), FishIDEImg::Open(), THISBACK(ProjectOpenCb));
	if(bar.IsMenuBar())
	{
		bar.Add(t_("Import example"), FishIDEImg::OpenSample(), THISBACK1(ProjectOpenExampleMenuCb, false));
		bar.Add(t_("Open example in place"), FishIDEImg::OpenSample(), THISBACK1(ProjectOpenExampleMenuCb, true));
	}
	else
		ProjectOpenExampleToolbarCb(bar);
	bar.Separator();
	bar.Add(ProjectsAvail(), t_("Save"), FishIDEImg::Save(), THISBACK(ProjectSaveCb)).Key(AK_SAVEFILE);
	bar.Add(ProjectsAvail(), t_("Save as"), FishIDEImg::SaveAs(), THISBACK(ProjectSaveAsCb));
	if(bar.IsMenuBar())
		bar.Separator();
//	bar.Add(t_("Recent files"), callback(&lastFiles, &LastFiles::MenuInsert));
//	bar.Separator();
	if(bar.IsMenuBar())
		bar.Add(t_("Quit"), FishIDEImg::Exit(), THISBACK(ProjectExitCb));
}

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

void FishIDE::EditFind()
{
	FishIDEEditor.FindReplace(find_pick_sel, find_pick_text, false);
}

void FishIDE::EditReplace()
{
	FishIDEEditor.FindReplace(find_pick_sel, find_pick_text, true);
}

void FishIDE::EditFindReplacePickText()
{
	FishIDEEditor.FindReplace(true, true, false);
}

void FishIDE::EditFindNext()
{
	FishIDEEditor.FindNext();
}

void FishIDE::EditFindPrevious()
{
	FishIDEEditor.FindPrev();
}

void  FishIDE::EditFindInFiles()
{
	Project *proj = GetCurrentProject();
	if(!proj)
		return;
	FileFinder.FindInFiles(proj, false);
}

void  FishIDE::EditReplaceInFiles()
{
	Project *proj = GetCurrentProject();
	if(!proj)
		return;
	FileFinder.FindInFiles(proj, true);
}

void FishIDE::EditPaste()
{
	FishIDEEditor.Paste();
}

void  FishIDE::EditFormatCode(void)
{
	Project *proj = GetCurrentProject();
	if(proj)
		proj->FormatCode();
}

void FishIDE::SearchMenu(Bar& bar)
{
}

void FishIDE::EditMenu(Bar &bar)
{
	auto &e = FishIDEEditor;
	
	bar.Add(t_("Undo"), FishIDEImg::Undo(), callback(&e, &FishIDEEditorClass::Undo))
		.Key(K_CTRL_Z)
		.Enable(e.IsUndo())
		.Help(t_("Undo changes to text"));
	bar.Add(t_("Redo"), FishIDEImg::Redo(), callback(&e, &FishIDEEditorClass::Redo))
		.Key(K_SHIFT|K_CTRL_Z)
		.Enable(e.IsRedo())
		.Help(t_("Redo undone changes"));

	if(bar.IsMenuBar())
		bar.Separator();
	
	bar.Add(t_("Cut"), FishIDEImg::Cut(), callback(&e, &FishIDEEditorClass::Cut))
		.Key(K_CTRL_X)
		.Enable(e.IsSelection())
		.Help(t_("Cut selection and place it on the system clipboard"));
	bar.Add(t_("Copy"), FishIDEImg::Copy(), callback(&e, &FishIDEEditorClass::Copy))
		.Key(K_CTRL_C)
		.Enable(e.IsSelection())
		.Help(t_("Copy current selection on the system clipboard"));
	bar.Add(t_("Paste"), FishIDEImg::Paste(), callback(&e, &FishIDEEditorClass::Paste))
		.Key(K_CTRL_V)
		.Help(t_("Insert text from clipboard at cursor location"));

	if(bar.IsMenuBar())
	{
		bar.Separator();
		bar.Add(t_("Select all"), callback(&e, &FishIDEEditorClass::SelectAll))
			.Key(K_CTRL_A)
			.Enable(&e);
	}

	bar.Add(AK_FIND, FishIDEImg::Search(), THISBACK(EditFind))
		.Help(t_("Search for text or text pattern"));
	bar.Add(AK_REPLACE, FishIDEImg::Replace(), THISBACK(EditReplace))
		.Help(t_("Search for text or text pattern, with replace option"));

	bar.Add(AK_FINDNEXT, THISBACK(EditFindNext))
		.Help(t_("Find next occurrence"));
	bar.Add(AK_FINDPREV, THISBACK(EditFindPrevious))
		.Help(t_("Find previous occurrence"));
	bar.Separator();
	bar.Add(t_("Find in files"), FishIDEImg::SearchInFiles(), THISBACK(EditFindInFiles))
		.Help(t_("Find text in files"));
	bar.Add(t_("Replace in files"), FishIDEImg::ReplaceInFiles(), THISBACK(EditReplaceInFiles))
		.Help(t_("Replace text in files"));

	if(bar.IsMenuBar())
	{
		bar.Separator();
		bar.Add(AK_FORMATCODE, THISBACK(EditFormatCode))
			.Help(t_("Format code in editor"));
	}
	bar.MenuSeparator();

/*
	bar.Add(AK_FINDSTRING, THISBACK1(FindString, false))
		.Help("Find any ordinary string constant (\"\" - delimited)");
	bar.Add(AK_FINDSTRINGBACK, THISBACK1(FindString, true))
		.Help("Find any ordinary string constant (\"\" - delimited) backwards");	
	bar.MenuSeparator();
*/
}

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

void FishIDE::BuildBuildCb()
{
	Project *project = GetCurrentProject();
	if(!project)
		return;
	project->Build();
}
		
void FishIDE::BuildUploadCb()
{
	Project *project = GetCurrentProject();
	if(!project)
		return;
	project->Upload();
}

void FishIDE::BuildShowDisassemblyCb()
{
	Project *project = GetCurrentProject();
	if(!project)
		return;
	project->ShowDisasm();
}

void FishIDE::BuildRemoveSketchOutputCb()
{
	Board *board = Settings.GetSelectedBoard();
	if(!board)
		return;
	String boardName = board->GetName();
	String architecture = board->GetPlatform().GetArchitecture();
	String sketch = GetCurrentProject()->GetTitle();
	String path = AppendFileName(GetConfigPath(), "BUILD");
	path = AppendFileName(path, architecture);
	path = AppendFileName(path, boardName);
	path = AppendFileName(path, sketch);
	FileTools::DeleteFolderDeep(path);
}

void FishIDE::BuildRemoveBoardOutputCb()
{
	Board *board = Settings.GetSelectedBoard();
	if(!board)
		return;
	String boardName = board->GetName();
	String architecture = board->GetPlatform().GetArchitecture();
	String path = AppendFileName(GetConfigPath(), "BUILD");
	path = AppendFileName(path, architecture);
	path = AppendFileName(path, boardName);
	FileTools::DeleteFolderDeep(path);
}

void FishIDE::BuildRemoveAllOutputCb()
{
	String path = AppendFileName(GetConfigPath(), "BUILD");
	FileTools::DeleteFolderDeep(path);
}

void FishIDE::BuildMenu(Bar &bar)
{
	bar.Add(t_("Build"), FishIDEImg::Build(), THISBACK(BuildBuildCb));
	bar.Add(t_("Upload"), FishIDEImg::Up(), THISBACK(BuildUploadCb));
	bar.Add(t_("Show disassembly"), FishIDEImg::Disasm(), THISBACK(BuildShowDisassemblyCb));
	if(bar.IsMenuBar())
	{
		bar.Separator();
		Board const *board = Settings.GetSelectedBoard();
		if(board)
		{
			Project const *proj = GetCurrentProject();
			if(proj)
				bar.Add(Format(t_("Clean output for sketch '%s'"), proj->GetTitle()), THISBACK(BuildRemoveSketchOutputCb));
			bar.Add(Format(t_("Clean output for board '%s'"), board->GetDescription()), THISBACK(BuildRemoveBoardOutputCb));
		}
		bar.Add(t_("Clean output folder"), THISBACK(BuildRemoveAllOutputCb));
	}
}


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

void FishIDE::ToolsBoardManagerCb()
{
	BoardManager().Run();
}

void FishIDE::ToolsExampleManagerCb()
{
	ExampleManager manager;
//	manager.WhenChange = THISBACK(librariesChangedCb);
	manager.Run();
}

void FishIDE::ToolsLibraryManagerCb()
{
	LibraryManager manager;
	manager.WhenChange = THISBACK(librariesChangedCb);
	manager.Run();
}

void FishIDE::ToolsLibraryZipInstallCb()
{
	FileSel fs;
	fs.Type(t_("Zipped library files"), "*.zip");
	if(fs.ExecuteOpen(t_("Select a zip library file")))
	{
		// get selected path
		String archivePath = fs.Get();

		// unpack it on a temporary folder
		String tempPath = AppendFileName(GetTempPath(), "FishideTmpLib");
		DeleteFolderDeep(tempPath);
		if(!FileTools::UnpackArduinoArchive(archivePath, tempPath))
		{
			Exclamation(t_("Error unpacking library archive"));
			return;
		}
		String propPath = AppendFileName(tempPath, "library.properties");
		if(!FileExists(propPath))
		{
			Exclamation(t_("Bad library archive file"));
			return;
		}
		
		// read property file to gather lib info
		// (mostly the name...)
		LibraryInfo info(tempPath);
		String destPath = AppendFileName(Settings.GetUserLibsRoot(), info.GetName());
		if(DirectoryExists(destPath))
		{
			if(!PromptYesNo(Format(t_("Library '%s' already exists&Do you want to replace it?"), info.GetName())))
				return;
		}
		DeleteFolderDeep(destPath);
		FileTools::DirectoryCopy(tempPath, destPath);
	}
}

void FishIDE::ToolsSettingsCb()
{
	if(SettingsDialog().Run() == IDOK)
		settingsChangedCb();
}

void FishIDE::ToolsMenu(Bar &bar)
{
	bar.Add(t_("Board manager"), FishIDEImg::Board(), THISBACK(ToolsBoardManagerCb));
	bar.Add(t_("Examples manager"), FishIDEImg::ExampleManager(), THISBACK(ToolsExampleManagerCb));
	bar.Add(t_("Library manager"), FishIDEImg::Book(), THISBACK(ToolsLibraryManagerCb));
	if(bar.IsMenuBar())
	{
		bar.Separator();
		bar.Add(t_("Install library from zip file"), THISBACK(ToolsLibraryZipInstallCb));
	}
	bar.Separator();
	bar.Add(t_("Settings"), FishIDEImg::Preferences(), THISBACK(ToolsSettingsCb));
	bar.Add(t_("Edit keys"), THISBACK(DoEditKeys));
}

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

void FishIDE::HelpAboutCb(void)
{
/*
	String message = t_(
		"[*5a@6 "
		"Integrated&"
	    "    Development&"
	    "        Environment&"
		"For Arduino-like boards&"
		"Copyright(C) 2015&"
	    "    by Massimo Del Fedele"
	    "]"
    );
*/
	String message = "";
    splash().AboutBox(message, 15, 15);
}

void FishIDE::HelpMenu(Bar &bar)
{
	bar.Add(t_("About"), FishIDEImg::Help(), THISBACK(HelpAboutCb));
}

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

void FishIDE::MainMenu(Bar &bar)
{
	bar.Add(t_("Project"), THISBACK(ProjectMenu));
	bar.Add(t_("Edit"), THISBACK(EditMenu));
	bar.Add(t_("Sketch"), THISBACK(BuildMenu));
	bar.Add(t_("Tools"), THISBACK(ToolsMenu));
	bar.Add(t_("Help"), THISBACK(HelpMenu));
}

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

void FishIDE::ToolBarMenu(Bar &bar)
{
	MainMenu(bar);
	bar.Separator();
	bar.Add(t_("Quit"), FishIDEImg::Exit(), THISBACK(ProjectExitCb));

	bar.Separator();
	bar.Add(boardSelector);
	bar.Separator();
	bar.Add(deviceSelector);
	bar.Separator();

}

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

// board selection callback
void FishIDE::selectBoardCb(String board)
{
	Settings.SetActiveBoard(board);
}
	
// device selection callback
void FishIDE::selectDeviceCb(String device)
{
	Settings.SetDevice(device);
}	

// update menus functions -- triggered on projects changes
void FishIDE::updateMenuCb(void)
{
	KillTimeCallback(TIMEID_MENU);
//@@	SetTimeCallback(500, THISBACK(updateMenuCb), TIMEID_MENU);
	if(!menu.IsOpen())
		menu.Set(THISBACK(MainMenu));
	toolBar.Set(THISBACK(ToolBarMenu));
}
