#include "ide.h"
#pragma hdrstop

void Ide::MakeTitle()
{
	String title;
	if(!main.IsEmpty())
		title << main;
	if(mainconfigname == mainconfigparam)
		title << " - " << mainconfigname;
	else
	if(!mainconfigname.IsEmpty()) {
		title << " - " << mainconfigname;
		title << " ( " << mainconfigparam << " )";
	}
	title << " - TheIDE";
	if(designer) {
		title << " - [" << designer->GetFileName();
		int cs = designer->GetCharset();
		if(cs >= 0)
			title << " " << CharsetName(cs);
		title << "]";
	}
	else
	if(!editfile.IsEmpty()) {
		title << " - [" << editfile;
		title << " " << CharsetName(editor.GetCharset());
		if(editor.IsReadOnly())
			title << " (Read Only)";
		if(editor.IsDirty())
			title << " *";
		title << "]";
	}
	if(!IsNull(editfile))
		for(int i = 0; i < 10; i++)
			if(NormalizePath(editfile) == NormalizePath(bookmark[i].file))
				title << Format(" <%d>", i);
	title << " { " << GetVarsName() << " }";
	Title(title.ToWString());
}

void Ide::MakeIcon() {
#ifdef PLATFORM_WIN32
	String mp = main;
	Image li = IdeImg::PackageLarge();
	if(!IsNull(mp))
	{
		Size isz = li.GetSize();
		ImageDraw idraw(isz);
		Draw& mdraw = idraw.Alpha();
		idraw.DrawImage(0, 0, li);
		mdraw.DrawImage(0, 0, li, White);
		Font font = StdFont(9);
		Size sz = GetTextSize(mp, font);
		sz.cx = min(sz.cx + 4, isz.cx);
		sz.cy += 2;
		int x = (isz.cx - sz.cx) / 2;
		int y = isz.cy - sz.cy;
		idraw.DrawRect(x, y, sz.cx, sz.cy, White);
		mdraw.DrawRect(x, y, sz.cx, sz.cy, White);
		idraw.DrawText(x + 2, y + 1, mp, font, Black);
		DrawFrame(idraw, x, y, sz.cx, sz.cy, LtBlue);
		li = idraw;
	}
	LargeIcon(li);
#endif
}

bool Ide::CanToggleReadOnly()
{
	FindFile ff(editfile);
	if(ff && ff.IsReadOnly()) return false;
	return NormalizePath(SourcePath(GetActivePackage(), GetActiveFileName())) ==
		   NormalizePath(editfile);
}

void Ide::ToggleReadOnly()
{
	if(CanToggleReadOnly() && IsActiveFile()) {
		editor.SetEditable(editor.IsReadOnly());
		ActiveFile().readonly = editor.IsReadOnly();
		SavePackage();
		MakeTitle();
		SetBar();
	}
}

void Ide::AdjustMainConfig()
{
	const Workspace& wspc = IdeWorkspace();
	if(!wspc.GetCount())
		return;
	const Array<Package::Config>& f = wspc.GetPackage(0).config;
	for(int i = 0; i < f.GetCount(); i++)
		if(f[i].param == mainconfigparam)
			return;
	if(f.GetCount()) {
		mainconfigparam = f[0].param;
		mainconfigname = f[0].name;
		SetMainConfigList();
	}
}

String Ide::GetFirstFile()
{
	const Workspace& wspc = IdeWorkspace();
	for(int i = 0; i < wspc.GetCount(); i++) {
		const Package& p = wspc.GetPackage(i);
		for(int j = 0; j < p.GetCount(); j++)
			if(!p[j].separator)
				return SourcePath(wspc[i], p[j]);
	}
	return Null;
}

void Ide::SetMain(const String& package)
{
	FlushFile();
	SaveWorkspace();
	transferfilecache.Clear();
	main = package;
	mainconfigname.Clear();
	mainconfigparam.Clear();
	ScanWorkspace();
	SyncWorkspace();
	LoadFromFile(THISBACK(SerializeWorkspace), WorkspaceFile());
	editorsplit.Zoom(0);
	UpdateFormat();
	String e = editfile;
	editfile.Clear();
	MakeTitle();
	MakeIcon();
	SyncMainConfigList();
	AdjustMainConfig();
	SyncBuildMode();
	SetHdependDirs();
	SetBar();
	HideBottom();
	if(IsNull(e))
		e = GetFirstFile();
	EditFile(e);
	if(!IsNull(lasttopicpath))
		topic.Open(lasttopicpath);
}

void Ide::Exit()
{
	if(debugger)
		debugger->Stop();
	SaveFile();
	SaveWorkspace();
	FlushFile();
	console.Kill();
	Break(IDOK);
}

bool Ide::OpenMainPackage()
{
	String tt = "Select main package";
//	tt << " (IDE " << IDE_VER_MAJOR << "." << IDE_VER_MINOR << ".r" << IDE_VER_BUILD
//	<< ", " << ::AsString(IDE_VER_DATE) << ")";
#ifdef bmYEAR
	tt << " (TheIDE " << IDE_VERSION
	   << Format(" %d-%02d-%02d %d:%02d)", bmYEAR , bmMONTH, bmDAY, bmHOUR, bmMINUTE);
#else
	tt << " (TheIDE " << IDE_VERSION << ')';
#endif
	String p = SelectPackage(tt, main, true, true);
	if(p.IsEmpty()) return false;
	main.Clear();
	SetMain(p);
	if(editor.auto_assist)
		StartBrowserBase();
	return true;
}

void Ide::NewMainPackage()
{
	SaveBrowserBase();
	OpenMainPackage();
}

void Ide::PackageCursor()
{
	WorkspaceWork::PackageCursor();
	String p = GetActivePackage();
	if(p.IsEmpty()) return;
	String ef = ToLower(NormalizePath(editfile));
	for(int i = 0; i < filelist.GetCount(); i++)
		if(ToLower(NormalizePath(SourcePath(p, filelist[i]))) == ef) {
			filelist.SetCursor(i);
			break;
		}
	SetBar();
}

void Ide::EditWorkspace()
{
	EditPackages(main, GetActivePackage(), pocfg);
	ScanWorkspace();
	SyncWorkspace();
}

String Ide::WorkspaceFile()
{
	String nm;
	for(const char *s = main; *s; s++)
		nm.Cat(*s == '\\' || *s == '/' ? '$' : *s);
	String cfg = ConfigFile("cfg");
	RealizeDirectory(cfg);
	return AppendFileName(cfg, ForceExt(nm + '@' + GetVarsName(), ".cfg"));
}

void Ide::SaveWorkspace()
{
	if(console.console) return;
	if(main.IsEmpty()) return;
	lasttopicpath = topic.GetFilePath();
	StoreToFile(THISBACK(SerializeWorkspace), WorkspaceFile());
}

void Ide::SyncMainConfigList()
{
	mainconfiglist.Clear();
	const Workspace& wspc = IdeWorkspace();
	if(wspc.GetCount() <= 0) return;
	const Array<Package::Config>& f = wspc.GetPackage(0).config;
	for(int i = 0; i < f.GetCount(); i++)
		mainconfiglist.Add(f[i].param, Nvl(f[i].name, f[i].param));
	SetMainConfigList();
}

void Ide::SetMainConfigList()
{
	mainconfiglist <<= mainconfigparam;
	mainconfigname = mainconfiglist.GetValue();
	mainconfiglist.Tip("Main configuration: " + mainconfigparam);
}

void Ide::OnMainConfigList()
{
	mainconfigparam = ~mainconfiglist;
	SetMainConfigList();
	MakeTitle();
}

void Ide::UscFile(const String& file)
{
	try {
		ParseUscFile(file);
	}
	catch(CParser::Error& e) {
		ShowConsole();
		console << e << "\n";
	}
}

void Ide::UscProcessDir(const String& dir)
{
	for(FindFile ff(AppendFileName(dir, "*.usc")); ff; ff.Next())
		UscFile(AppendFileName(dir, ff.GetName()));
}

void Ide::SyncUsc()
{
	CleanUsc();
	::Workspace wspc;
	wspc.Scan(main);
	int i;
	for(i = 0; i < wspc.GetCount(); i++) {
		const Package& p = wspc.GetPackage(i);
		for(int j = 0; j < p.file.GetCount(); j++) {
			String file = SourcePath(wspc[i], p.file[j]);
			if(ToLower(GetFileExt(file)) == ".usc")
				UscFile(file);
		}
	}
	UscProcessDir(GetCommonDir());
	UscProcessDir(GetLocalDir());
	UscProcessDir(GetFileFolder(ConfigFile("x")));
}

void Ide::SyncWorkspace()
{
	SyncUsc();
	TopicPackages();
	SyncBrowserBase();
}

bool IsTextFile(const String& file) {
	byte buffer[16384];
	FileIn fi(file);
	if(!fi.IsOpen())
		return false;
	int count = fi.Get(buffer, sizeof(buffer) - 1);
	buffer[count] = 0;
	const byte *end = buffer + count;
	const byte *q = buffer;
	const byte *x = q;
	while(q < end) {
		if(*q < 32) {
			int c = *q;
			if(c == '\n') {
				if(q - x > 2000) return false;
				x = q;
			}
			else
			if(c != '\r' && c != '\t' && c != '\v' && c != '\f' && c != 0x1a)
				return false;
		}
		q++;
	}
	return true;
}

bool Ide::FindLineError(int l, Host& host) {
	Vector<String> wspc_paths;
	VectorMap<String, String> bm = GetMethodVars(method);
	bool is_java = (bm.Get("BUILDER", Null) == "JDK");
	String ln = console.GetUtf8Line(l);
	String file;
	const char *s = ln;
	while(*s == ' ' || *s == '\t')
		s++;
	for(; *s; s++) {
		if(*s != '\"' && (byte)*s >= 32 && *s != '(' && (file.GetLength() < 3 || *s != ':'))
			file.Cat(*s);
		else {
			if(*s == '\"') {
				file = Null;
				s++;
				while(*s && *s != '\"')
					file.Cat(*s++);
				if(*s)
					s++;
			}
			int e = file.GetLength();
			while(e > 0 && file[e - 1] == ' ')
				e--;
			file.Trim(e);
			file = TrimLeft(file);
			String upp = GetUppDir();
			file = host.GetLocalPath(file);
		#ifdef PLATFORM_WIN32
			if(file[0] == '\\' || file[0] == '/')
				file = String(upp[0], 1) + ':' + file;
		#endif
			if(!IsFullPath(file) && *file != '\\' && *file != '/') {
				if(wspc_paths.IsEmpty()) {
					::Workspace  wspc;
					wspc.Scan(main);
					for(int i = 0; i < wspc.GetCount(); i++)
						wspc_paths.Add(GetFileDirectory(PackagePath(wspc[i])));
				}
				for(int i = 0; i < wspc_paths.GetCount(); i++) {
					String path = AppendFileName(wspc_paths[i], file);
					FindFile ff;
					if(ff.Search(path) && ff.IsFile()) {
						file = path;
						break;
					}
				}
			}
			file = FollowCygwinSymlink(file);
			if(IsFullPath(file) && FileExists(file) && IsTextFile(file)) {
				while(*s && !IsDigit(*s))
					s++;
				int lineno = 0;
				if(IsDigit(*s))
					lineno = stou(s);
				Vector<String> conf = SplitFlags(mainconfigparam, true);
				String uppout = GetVar("OUTPUT");
				int upplen = uppout.GetLength();
				if(is_java && file.GetLength() > upplen
				&& !MemICmp(file, uppout, upplen) && file[upplen] == DIR_SEP) { // check for preprocessed file
					FileIn fi(file);
					if(fi.IsOpen())
					{
						String fake_file = file;
						int fake_line = 1;
						int file_line = 1;
						while(!fi.IsEof())
						{
							String line = fi.GetLine();
							const char *p = line;
							if(p[0] == '/' && p[1] == '/' && p[2] == '#')
							{
								p += 3;
								if(p[0] == 'l' && p[1] == 'i' && p[2] == 'n' && p[3] == 'e')
									p += 4;
								while(*p == ' ' || *p == '\t')
									p++;
								if(IsDigit(*p))
								{
									fake_line = stou(p, &p);
									while(*p == ' ' || *p == '\t')
										p++;
									if(*p == '\"')
										p++;
									const char *b = p;
									fake_file.Clear();
									while(*p && *p != '\"')
										if(*p == '/')
										{
											fake_file.Cat('/');
											if(p[1] == '/')
												p++;
											p++;
										}
										else
											fake_file.Cat(*p++);
								}
								file_line++;
								continue;
							}
							if(lineno <= file_line) {
								file = fake_file;
								lineno = fake_line;
								break;
							}
							file_line++;
							fake_line++;
						}
					}
				}
				if(lineno > 0) {
					file = NormalizePath(file);
					editastext.FindAdd(file);
					EditFile(file);
					editor.SetCursor(editor.GetPos(editor.GetLineNo(lineno - 1)));
					editor.CenterCursor();
					editor.SetFocus();
					Sync();
					console.SetSelection(console.GetPos(l), console.GetPos(l + 1));
					ShowConsole();
					return true;
				}
			}
			file.Clear();
		}
	}
	return false;
}

void Ide::FindError() {
	int l = console.GetLine(console.GetCursor());
	One<Host> host = CreateHost(false);
	FindLineError(l, *host);
}

void Ide::FindNextError() {
	int ln = console.GetLine(console.GetCursor());
	int l = ln;
	One<Host> host = CreateHost(false);
	for(l = ln; l < console.GetLineCount(); l++)
		if(FindLineError(l, *host)) return;
	for(l = 0; l < ln; l++)
		if(FindLineError(l, *host)) return;
}

void Ide::FindPrevError() {
	int ln = console.GetLine(console.GetCursor());
	int l = ln;
	One<Host> host = CreateHost(false);
	for(l = ln - 2; l >= 0; l--)
		if(FindLineError(l, *host)) return;
	for(l = console.GetLineCount() - 1; l > ln; l--)
		if(FindLineError(l, *host)) return;
}

void Ide::Renumber() {
	for(int i = 0; i < filedata.GetCount(); i++)
		::Renumber(filedata[i].lineinfo);
	editor.Renumber();
}

bool Ide::Key(dword key, int count) {
	dword *k = IdeKeys::AK_DELLINE().key;
	if(key == k[0] || key == k[1]) {
		editor.DeleteLine();
		return true;
	}
	k = IdeKeys::AK_CUTLINE().key;
	if(key == k[0] || key == k[1]) {
		editor.CutLine();
		return true;
	}
	switch(key) {
	case K_SHIFT_CTRL_UP:
	case K_SHIFT_CTRL_DOWN:
	case K_ALT_DELETE:
		return filelist.Key(key, count);
	case K_ALT_UP:
		return filelist.Key(K_UP, 0);
	case K_ALT_DOWN:
		return filelist.Key(K_DOWN, 0);
	case K_ALT_PAGEUP:
		return package.Key(K_UP, 0);
	case K_ALT_PAGEDOWN:
		return package.Key(K_DOWN, 0);
	case K_CTRL|K_ALT_LEFT:
		TabsLR(-1);
		return true;
	case K_CTRL|K_ALT_RIGHT:
		TabsLR(1);
		return true;
	case K_CTRL_I:
		AddFile(WorkspaceWork::ANY_FILE);
		return true;
	case K_CTRL_KEY|K_KEYUP:
		if(tabi) {
			tabi = 0;
			AddLru();
		}
		return true;
	case K_CTRL_TAB:
		if(++tabi >= tablru.GetCount())
			tabi = 0;
		if(tabi < tablru.GetCount()) {
			blocktabs = true;
			EditFile(tablru[tabi]);
		}
		return true;
	case K_ALT_RIGHT:
	default:
		if(key >= K_SHIFT_CTRL_0 && key <= K_SHIFT_CTRL_9) {
			Bookmark& b = bookmark[key - K_SHIFT_CTRL_0];
			b.file = editfile;
			b.pos = editor.GetEditPos();
			MakeTitle();
			return true;
		}
		if(key >= K_CTRL_0 && key <= K_CTRL_9) {
			GotoBookmark(bookmark[key - K_CTRL_0]);
			return true;
		}
	}
	return TopWindow::Key(key, count);
}

void Ide::GotoBookmark(const Bookmark& b)
{
	if(b.file.IsEmpty()) return;
	EditFile(b.file);
	if(bookmark_pos)
		editor.SetEditPos(b.pos);
}

bool Ide::IsHistDiff(int i)
{
	Bookmark& b = history[i];
	return b.file != editfile || abs(editor.GetCursor() - b.pos.cursor) > 200;
}

void Ide::AddHistory()
{
	if(history.GetCount() && histi - 1 >= 0 && histi <= history.GetCount()) {
		if(IsHistDiff(histi - 1))
			++histi;
	}
	else
		histi = 1;
	history.SetCount(histi);
	Bookmark& b = history.Top();
	b.file = editfile;
	b.pos = editor.GetEditPos();
}

void Ide::HistoryBk()
{
	while(histi > 0 && --histi < history.GetCount())
		if(IsHistDiff(histi)) {
			GotoBookmark(history[histi]);
			break;
		}
}

void Ide::HistoryFw()
{
	if(histi < history.GetCount() - 1 && ++histi >= 0)
		GotoBookmark(history[histi]);
}

void Ide::BookKey(int key) {
	Key(key, 1);
}

void Ide::Display() {
	Point p = editor.GetColumnLine(editor.GetCursor());
	display.SetLabel(Format("Ln %d, Col %d", p.y + 1, p.x + 1));
}

void Ide::ExportProject() {
	FlushFile();
	FileSel fsel;
	if(!fsel.ExecuteSelectDir("Export folder"))
		return;
	String outpath = ~fsel;
	::Workspace wspc;
	wspc.Scan(main);
	console.Clear();
	int p;
	int total = wspc.GetCount();
	for(p = 0; p < wspc.GetCount(); p++)
		total += wspc.GetPackage(p).file.GetCount();
	Progress progress("Exporting file %d", total);
	for(p = 0; p < wspc.GetCount(); p++)
	{
		if(progress.StepCanceled())
		{
			console << "(canceled)\n";
			break;
		}
		String packname = NativePath(wspc[p]);
		String packupp = AppendFileName(packname, AppendExt(GetFileNamePos(packname), ".upp"));
		const Package& pkg = wspc.GetPackage(p);
		String packout = AppendFileName(outpath, packupp);
		console << NFormat("%s, %d file(s)\n", packname, pkg.file.GetCount());
		RealizePath(packout);
		if(!pkg.Save(packout))
		{
			console << NFormat("Error saving UPP file '%s' into '%s'\n", packname, packout);
			return;
		}
		for(int f = 0; f < pkg.file.GetCount(); f++)
		{
			if(progress.StepCanceled())
			{
				console << "(canceled)\n";
				return;
			}
			if(!pkg.file[f].separator)
			{
				String pkgfile = AppendFileName(packname, pkg.file[f]);
				String srcfile = SourcePath(packname, pkg.file[f]);
				String dstfile = AppendFileName(outpath, pkgfile);
				RealizePath(dstfile);
				if(!FileExists(srcfile))
					console << NFormat("%s: file not found\n", srcfile);
				else if(!FileCopy(srcfile, dstfile))
				{
					console << NFormat("%s: error saving '%s'\n", srcfile, dstfile);
					return;
				}
			}
		}
	}
	console << "Done\n";
}

void Ide::SerializeWorkspace(Stream& s) {
	int i;
	int version = 7;
	int n = 0;
	s / version;
	s.Magic(0x12345);
	if(s.IsStoring()) {
		for(i = 0; i < filedata.GetCount(); i++) {
			String fn = filedata.GetKey(i);
			if(!fn.IsEmpty() && FileExists(fn)) {
				s % fn;
				s % filedata[i].editpos;
				if(version >= 1)
					s % filedata[i].columnline;
			}
		}
		String h;
		s % h;
	}
	else {
		String fn;
		filedata.Clear();
		for(;;) {
			s % fn;
			if(fn.IsEmpty()) break;
			FileData& fd = filedata.GetAdd(fn);
			fd.Clear();
			s % fd.editpos;
			if(version >= 1)
				s % fd.columnline;
		}
	}
	String pk = GetActivePackage();
	s % pk;
	package.FindSetCursor(pk);
	s % tablru;
	s % mainconfigname;
	s % mainconfigparam;
	s % console.verbosebuild;
	s % stoponerrors;
	byte dummy;
	s % dummy;
	s % runarg;
	s % recent_runarg;
	s % rundir;
	s % stdout_file % recent_stdout_file / runmode;
	if(version >= 1)
		s % runexternal;
	s % editfile;
	for(i = 0; i < 10; i++)
		s % bookmark[i];
	editor.Serialize(s);
	if(version >= 5)
		s % editorsplit;
	if(version == 6) {
		String n;
		int v;
		s / v;
		for(int i = 0; i < 10; i++) {
			s % n;
			s / v;
		}
	}
	SerializeFindInFiles(s);
	String om;
	s % om;
	s % recentoutput;
	s % recentflags;
	s / editortabsize / indent_amount % indent_spaces;
	for(int j = 0; j < GetIdeModuleCount(); j++)
		GetIdeModule(j).Serialize(s); // -> Workspace
	SerializeWorkspaceConfigs(s);
	SerializeOutputMode(s);
	SerializeClosed(s);
	topic.SerializeWspc(s);
	browser.SerializeWspc(s);
	if(version >= 3)
		s % lasttopicpath;
}

void Ide::SetIdeState(int newstate) {
	if(newstate != idestate)
	{
		if(newstate == BUILDING)
			build_start_time = GetSysTime();
		else
		{
			if(idestate == BUILDING && !IsNull(build_start_time))
				stat_build_time += int(GetSysTime() - build_start_time);
			build_start_time = Null;
		}
	}
	idestate = newstate;
	MakeTitle();
	SetBar();
}

void Ide::SetIcon()
{
	int new_state_icon = 0;
	if((bool)debugger && !IdeIsDebugLock()) {
		new_state_icon = 1;

		return;
	}
	else
	if((GetTimeClick() / 800) & 1)
		if(debugger)
			new_state_icon = 2;
		else
		if(idestate == BUILDING)
			new_state_icon = 3;
	if(state_icon == new_state_icon)
		return;
	state_icon = new_state_icon;
	switch(state_icon) {
	case 1:  Icon(IdeImg::IconDebugging()); break;
	case 2:  Icon(IdeImg::IconRunning()); break;
	case 3:  Icon(IdeImg::IconBuilding()); break;
	default: Icon(IdeImg::Package());
	}
}

void Ide::Periodic()
{
	CheckFileUpdate();
	SetIcon();
	if(debugger && debugger->IsFinished() && !IdeIsDebugLock())
		IdeEndDebug();
}

const Workspace& Ide::IdeWorkspace() const
{
	static Workspace wspc;
	static String _main;
	if(main != _main || wspc.GetCount() == 0) {
		wspc.Scan(main);
		_main = main;
	}
	else {
		for(int i = 0; i < wspc.GetCount(); i++)
			if(wspc.GetPackage(i).time != FileGetTime(PackagePath(wspc[i]))) {
				wspc.Scan(main);
				break;
			}
	}
	return wspc;
}
