#include "ide.h"

bool Ide::FindLineError(const String& ln, FindLineErrorCache& cache, ListLineInfo& f)
{
	if(!cache.init) {
		VectorMap<String, String> bm = GetMethodVars(method);
		cache.is_java = (bm.Get("BUILDER", Null) == "JDK");
		cache.upp = GetUppDir();
		cache.init = true;
	}
	const char *s0 = ln;
	while(*s0 == ' ' || *s0 == '\t')
		s0++;
	for(;;) {
		const char *s = s0;
		f.file.Clear();
		for(; s < ln.End(); s++) {
			if(*s != '\"' && (byte)*s >= 32 && *s != '(' && (f.file.GetLength() < 3 || *s != ':'))
				f.file.Cat(*s);
			else {
				if(*s == '\"') {
					f.file = Null;
					s++;
					while(*s && *s != '\"')
						f.file.Cat(*s++);
					if(*s)
						s++;
				}
				String file = f.file;
				int e = file.GetLength();
				while(e > 0 && file[e - 1] == ' ')
					e--;
				file.Trim(e);
				file = TrimLeft(file);
	
				int q = cache.file.Find(file);
				if(q < 0) {
					String file0 = file;
				#ifdef PLATFORM_WIN32
					if(file[0] == '\\' || file[0] == '/')
						file = String(cache.upp[0], 1) + ':' + file;
				#endif
					bool exists = false;
					if(!IsFullPath(file) && *file != '\\' && *file != '/') {
						if(cache.wspc_paths.IsEmpty()) {
							::Workspace  wspc;
							wspc.Scan(main);
							for(int i = 0; i < wspc.GetCount(); i++)
								cache.wspc_paths.Add(PackageDirectory(wspc[i]));
						}
						for(int i = 0; i < cache.wspc_paths.GetCount(); i++) {
							String path = AppendFileName(cache.wspc_paths[i], file);
							String ext = ToLower(GetFileExt(path));
							if(findarg(ext, ".obj", ".lib", ".o", ".so", ".a", ".", "") < 0) {
								FindFile ff;
								if(ff.Search(path) && ff.IsFile()) {
									file = path;
									exists = true;
									break;
								}
							}
						}
					}
					if(!IsFullPath(file) || !exists && !FileExists(file) || !IsTextFile(file))
						file = Null;
					cache.file.Add(file0, file);
				}
				else
					file = cache.file[q];
				if(file.GetCount()) {
					f.file = file;
					while(*s && !IsDigit(*s)) {
						if(*s == '/' || IsAlpha(*s))
							return false;
						s++;
					}
					f.lineno = f.linepos = 0;
					CParser p(s);
					try {
						if(p.IsInt())
							f.lineno = p.ReadInt();
						if(p.Char(':') && p.IsInt())
							f.linepos = p.ReadInt();
					}
					catch(CParser::Error) {}
					const char *ms = p.GetPtr();
					if(ln.Find(": warning") >= 0)
						f.kind = 2;
					else if(ln.Find(": error") >= 0 || ln.Find(": fatal error") >= 0)
						f.kind = 1;
					else
						f.kind = 3;
					const char *hs = ms;
					while(!IsLetter(*hs) && *hs)
						hs++;
					f.message = *hs ? hs : ms;
					f.message = TrimLeft(f.message);
					if(IsNull(f.message))
						f.message = ln;
					Vector<String> conf = SplitFlags(mainconfigparam, true);
					String uppout = GetOutputDir();
					int upplen = uppout.GetLength();
					if(cache.is_java && f.file.GetLength() > upplen
					&& !MemICmp(f.file, uppout, upplen) && f.file[upplen] == DIR_SEP) { // check for preprocessed file
						FileIn fi(f.file);
						if(fi.IsOpen()) {
							String fake_file = f.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++;
										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(f.lineno <= file_line) {
									f.file = fake_file;
									f.lineno = fake_line;
									f.linepos = 0;
									break;
								}
								file_line++;
								fake_line++;
							}
						}
					}
					return f.lineno > 0;
				}
				else
				if(*s == ':' || !strchr(s, '/') && !strchr(s, '\\')) // safe to say this is final
					break;
				else
					f.file.Cat(*s); // File is not complete, e.g.: C:\Program Files (x86)\Microsoft Visual Studio 10.0\Vc\Include\string.h(186)
			}
		}
		while(*s0 != ' ' && *s0 != '\t') { // skip to next whitespace to try again
			if(s0 >= ln.End())
				return false;
			s0++;
		}
		while(*s0 == ' ' || *s0 == '\t')
			s0++;
	}
}

void Ide::FindError()
{
	FindLineError(console.GetLine(console.GetCursor()));
}

bool Ide::Next(ArrayCtrl& a, int d)
{
	if(a.IsVisible()) {
		int c = a.GetCursor();
		for(;;) {
			c += d;
			if(c >= 0 && c < a.GetCount()) {
				Value v = a.Get(c, "INFO");
				if(v.Is<ListLineInfo>() && !IsNull(v.To<ListLineInfo>().file)) {
					a.SetCursor(c);
					return true;
				}
			}
			else {
				if(d > 0)
					a.GoBegin();
				else
					a.GoEnd();
				return true;
			}
		}
	}
	return false;
}

void Ide::FindNextError()
{
	if(Next(error, 1) || Next(FFound(), 1))
		return;
	int ln = console.GetLine(console.GetCursor());
	int l = ln;
	for(l = ln; l < console.GetLineCount(); l++)
		if(FindLineError(l)) return;
	for(l = 0; l < ln; l++)
		if(FindLineError(l)) return;
}

void Ide::FindPrevError() {
	if(Next(error, -1) || Next(FFound(), -1))
		return;
	int ln = console.GetLine(console.GetCursor());
	int l = ln;
	for(l = ln - 2; l >= 0; l--)
		if(FindLineError(l)) return;
	for(l = console.GetLineCount() - 1; l > ln; l--)
		if(FindLineError(l)) return;
}

void Ide::SetErrorEditor()
{
	if(error.GetCount()) {
		SetBottom(BERRORS);
//		if(!error.IsCursor())
//			error.GoBegin();
	}

	bool refresh = false;
	Vector<String> errorfiles;
	FindLineErrorCache cache;
	for(int i = 0; i < console.GetLineCount(); i++) {
		ListLineInfo f;
		if(FindLineError(console.GetUtf8Line(i), cache, f)) {
			String file = NormalizePath(f.file);
		#ifdef PLATFORM_WIN32
			errorfiles.Add(ToLower(file));
		#else
			errorfiles.Add(file);
		#endif
		}
	}
	if(refresh)
		editor.RefreshFrame();
	SetErrorFiles(errorfiles);
}

void Ide::GoToError(const ListLineInfo& f, bool error)
{
	if(IsNull(f.file))
		return;
	String file = NormalizePath(f.file);
	DoEditAsText(file);
	if(designer)
		FlushFile();
	EditFile(file);
	int lp = max(f.linepos - 1, 0);
	int l = f.lineno - 1;
	int pos;
	if(error)
		l = editor.GetLineNo(l);
	else {
		String ln = TrimLeft(f.line);
		if(ln.GetCount() && l >= 0 && l < editor.GetLineCount())
			for(int i = 0; i < 200; i++) {
				if(l - i >= 0 && TrimLeft(editor.GetUtf8Line(l - i)) == ln) {
					l = l - i;
					break;
				}
				if(l + i < editor.GetLineCount() && TrimLeft(editor.GetUtf8Line(l + i)) == ln) {
					l = l + i;
					break;
				}
			}
	}
	pos = editor.GetPos(l, lp);
	editor.SetCursor(pos);
	if(*f.message == '\1') {
		Vector<String> h = Split(~f.message + 1, '\1', false);
		if(h.GetCount() >= 4)
			editor.Illuminate(h[3].Mid(atoi(h[1]), atoi(h[2])).ToWString());
	}
	editor.CenterCursor();
	editor.SetFocus();
	Sync();
}

void Ide::GoToError(ArrayCtrl& a, bool error)
{
	Value v = a.Get("INFO");
	if(v.Is<ListLineInfo>())
		GoToError(ValueTo<ListLineInfo>(v), error);
}

bool Ide::FindLineError(int l) {
	ListLineInfo f;
	FindLineErrorCache cache;
	if(FindLineError(console.GetUtf8Line(l), cache, f)) {
		GoToError(f, true);
		console.SetSelection(console.GetPos64(l), console.GetPos64(l + 1));
		if(btabs.GetCursor() != BCONSOLE && !BottomIsFindInFiles())
			ShowConsole();
		return true;
	}
	return false;
}

void Ide::ClearErrorsPane()
{
	linking = false;
	linking_line.Clear();
	error_cache.Clear();
	error.Clear();
	SyncErrorsMessage();
	error_count = 0;
	warning_count = 0;
}

void Ide::PutLinking()
{
	linking = true;
	linking_line.Clear();
}

void Ide::PutLinkingEnd(bool ok)
{
	if(!ok && linking) {
		addnotes = true;
		error_count++;
		error.Add(Null, Null, AttrText("Linking has failed").Bold()
			                  .NormalPaper(HighlightSetup::GetHlStyle(HighlightSetup::PAPER_ERROR).color));
		for(int i = 0; i < linking_line.GetCount(); i++) {
			ListLineInfo f;
			if(!FindLineError(linking_line[i], error_cache, f)) {
				f.file = Null;
				f.lineno = Null;
				f.message = TrimLeft(linking_line[i]);
			}
			int linecy;
			error.Add(f.file, f.lineno,
			          AttrText(FormatErrorLine(f.message, linecy))
			          .NormalPaper(HighlightSetup::GetHlStyle(HighlightSetup::PAPER_ERROR).color),
			          RawToValue(f));
			error.SetLineCy(error.GetCount() - 1, linecy);
		}
	}
	SyncErrorsMessage();
	linking = false;
}

bool IsDarkMismatch()
{
	return IsDark(SColorPaper()) != IsDark(HighlightSetup::GetHlStyle(HighlightSetup::PAPER_NORMAL).color);
}

void Ide::TopAlignedDisplay::Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
{
	w.DrawRect(r, paper);
	w.DrawText(r.left, r.top, AsString(q));
}

WString Ide::FormatErrorLine(const String& text, int& linecy)
{
	WString txt;
	int cx = max(GetStdFontCy() * 30, error.HeaderObject().GetTabWidth(2) - error.HeaderTab(2).GetMargin() * 2);
	int x = 0;
	Font fnt = StdFont();
	WString h = text.ToWString();
	linecy = fnt.GetCy();
	const wchar *s = h;
	while(findarg(*s, ' ', '\t') >= 0)
		s++;
	while(*s) {
		int chcx = fnt[*s];
		if(x + chcx > cx) {
			txt.Cat('\n');
			x = 0;
			linecy += fnt.GetCy();
		}
		txt.Cat(*s);
		x += chcx;
		s++;
	}
	return txt;
}

WString Ide::FormatErrorLineEP(const String& text, const char *ep, int& linecy)
{
	WString txt;
	int cx = error.HeaderObject().GetTabWidth(2) - error.HeaderTab(2).GetMargin() * 2;
	int x = 0;
	Font fnt = StdFont();
	WString h = text.ToWString();
	linecy = fnt.GetCy();
	const wchar *s = h;
	while(findarg(*s, ' ', '\t') >= 0) {
		s++;
		if(*ep)
			ep++;
	}
	int lep = ' ';
	while(*s) {
		int chcx = fnt[*s];
		if(x + chcx > cx) {
			txt.Cat('\n');
			x = 0;
			linecy += fnt.GetCy();
		}
		if(lep != *ep) {
			txt.Cat(decode(*ep, '~', 2, '^', 3, 1));
			lep = *ep;
		}
		txt.Cat(*s);
		x += chcx;
		s++;
		if(*ep)
			ep++;
	}
	return txt;
}

struct ElepDisplay : public Display {
	Size    DoPaint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const;

	virtual Size GetStdSize(const Value& q) const;
	virtual void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const;
};

Size ElepDisplay::DoPaint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
{
	w.DrawRect(r, paper);
	WString txt = q;
	int st = 1;
	const wchar *s = txt;
	const wchar *b = s;
	int x = 0;
	int y = 0;
	int cx = 0;
	int linecy = StdFont().GetLineHeight();
	for(;;) {
		if((byte)*s < ' ') {
			int tcx = GetTextSize(b, StdFont(), int(s - b)).cx;
			if(st != 1 && (style & CURSOR) == 0)
				w.DrawRect(x + r.left, y + r.top, tcx, linecy,
				           HighlightSetup::GetHlStyle(st == 2 ? HighlightSetup::PAPER_WARNING
				                                              : HighlightSetup::PAPER_ERROR).color);
			w.DrawText(x + r.left, y + r.top, b, StdFont(), ink, int(s - b));
			x += tcx;
			cx = max(cx, tcx + x);
			if(*s == '\0')
				break;
			if(*s == '\n') {
				x = 0;
				y += linecy;
			}
			else
				st = *s;
			b = ++s;
		}
		else
			s++;
	}
	return Size(cx, y + linecy);
}

Size ElepDisplay::GetStdSize(const Value& q) const
{
	NilDraw w;
	return DoPaint(w, Rect(0, 0, INT_MAX, INT_MAX), q, Null, Null, 0);
}

void ElepDisplay::Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
{
	DoPaint(w, r, q, ink, paper, style);
}

void Ide::ConsoleLine(const String& line, bool assist)
{
	if(linking) {
		linking_line.Add(line);
		return;
	}
	ListLineInfo f;
	if(FindLineError(line, error_cache, f)) {
		if(assist)
			f.kind = 1;
		if(findarg(f.kind, 1, 2) >= 0 || error.GetCount() == 0) {
			static SColor serror([] { return HighlightSetup::GetHlStyle(HighlightSetup::PAPER_ERROR).color; });
			static SColor swarning([] { return HighlightSetup::GetHlStyle(HighlightSetup::PAPER_WARNING).color; });
			Color paper = f.kind == 1 ? serror : swarning;
			if(f.kind == 1)
				error_count++;
			else
				warning_count++;
			if(IsDarkMismatch())
				paper = SColorPaper();
			int linecy;
			error.Add(f.file, f.lineno,
			          AttrText(FormatErrorLine(f.message, linecy)).NormalPaper(paper),
			          RawToValue(f));
			if(prenotes.GetCount()) {
				error.Set(error.GetCount() - 1, "NOTES", prenotes);
				prenotes.Clear();
			}
			error.SetLineCy(error.GetCount() - 1, linecy);
			SyncErrorsMessage();
			addnotes = true;
			return;
		}
	}
	else {
		int q = line.FindAfter(" from "); // GCC style "included from"
		ListLineInfo fi;
		if(q >= 0 && FindLineError(line.Mid(q), error_cache, fi)) {
			fi.message = line;
			prenotes.Add(RawToValue(fi));
			return;
		}
		f.lineno = Null;
		f.file = Null;
		f.message = line;
	}
	if(addnotes) {
		int cnt = error.GetCount();
		if(cnt == 0)
			return;
		ValueArray n = error.Get(cnt - 1, "NOTES");
		bool iserrorpos = true;
		for(const char *s = f.message; *s; s++)
			if(*s != ' ' && *s != '~' && *s != '^' && *s != '|')
				iserrorpos = false;
		int i = n.GetCount() - 1;
		if(iserrorpos && i >= 0) {
			ListLineInfo f0 = ValueTo<ListLineInfo>(n[i]);
			f0.error_pos = f.message;
			n.Set(i, RawToValue(f0));
		}
		else
			n.Add(RawToValue(f));
		error.Set(cnt - 1, "NOTES", n);
	}
}

void Ide::SyncErrorsMessage()
{
	String h;
	String cnt;
	if(IsDarkMismatch()) {
		h = "Message";
		if(error_count)
			cnt << error_count << " error(s)";
		if(warning_count) {
			if(error_count)
				cnt << ", ";
			cnt << warning_count << " warning(s)";
		}
	}
	else {
		h = "\1[g Message";
		if(error_count)
			cnt << "[*@r " << error_count << " error" << (error_count > 1 ? "s]" : "]");
		if(warning_count) {
			if(error_count)
				cnt << ", ";
			cnt << "[@o " << warning_count << " warning" << (warning_count > 1 ? "s]" : "]");
		}
	}
	if(cnt.GetCount())
		h << " (" << cnt << ")";
	error.HeaderTab(2).SetText(h);
	if(error_count)
		btabs.Set(error_tab_i, "Errors (" + AsString(error_count) + ")", SRed(), StdFont().Bold());
	else
	if(warning_count)
		btabs.Set(error_tab_i, "Warnings (" + AsString(warning_count) + ")", SColorText());
	else
		btabs.Set(error_tab_i, "Errors", SColorText());
}

void Ide::ConsoleRunEnd()
{
	addnotes = false;
	prenotes.Clear();
}

void Ide::ShowFound(ArrayCtrl& list)
{
	if(list.IsCursor())
		GoToError(list, false);
}

String Ide::GetErrorsText(bool all, bool src)
{
	int c = error.GetCursor();
	if(!all && c < 0)
		return Null;
	String txt;
	int h = all ? error.GetCount() : error.GetCursor() + 1;
	for(int i = all ? 0 : c; i < h; i++) {
		if(src)
			txt << error.Get(i, 0) << " (" << error.Get(i, 1) << "): ";
		txt << error.Get(i, 2) << "\r\n";
	}
	return txt;
}

void Ide::CopyError(bool all)
{
	String s = GetErrorsText(all, true);
	if(s.GetCount())
		WriteClipboardText(s);
}

void Ide::RemoveError()
{
	if(!error.IsCursor())
		return;
	RemoveErrorNotes();
	String err = ~error.Get(2);
	int c = error.GetCursor();
	for(int i = error.GetCount() - 1; i >= 0; i--)
		if(~error.Get(i, 2) == err)
			error.Remove(i);
	error.SetCursor(min(error.GetCount() - 1, c));
	SelError();
	SyncErrorsMessage();
}

void Ide::ErrorMenu(Bar& bar)
{
	bar.Add(error.IsCursor(), "Copy", [this] { CopyError(false); });
	bar.Add("Copy all", [this] { CopyError(false); });
	bar.Separator();
	bar.Add(error.IsCursor() && error.Get("NOTES") != "0",
	        "Remove all instances of this error", [this] { RemoveError(); });
	bar.Separator();
	bar.Add(error.IsCursor(), "Search the web..", IdeImg::Google(), [this] {
		LaunchWebBrowser("https://www.google.com/search?q=" + GetErrorsText(false, false));
	});
}

void Ide::RemoveErrorNotes()
{
	int sc = error.GetScroll();
	removing_notes = true;
	for(int i = error.GetCount() - 1; i >= 0; i--)
		if(error.Get(i, "NOTES") == "0")
			error.Remove(i);
	removing_notes = false;
	error.ScrollTo(sc);
	error.ScrollIntoCursor();
}

void Ide::SelError()
{
	if(removing_notes)
		return;
	if(error.IsCursor()) {
		Value v = error.Get("NOTES");
		if(v != "0") { // "0" - expanded note
			RemoveErrorNotes();
			ValueArray n = v;
			int ii = error.GetCursor();
			for(int i = 0; i < n.GetCount(); i++) {
				const ListLineInfo& f = ValueTo<ListLineInfo>(n[i]);
				bool ok = true;
				for(const char *s = f.message; *s; s++)
					if((byte)*s >= 128) { // clang UTF-8 is messy, just ignore those notes
						ok = false;
						break;
					}
				if(ok) {
					error.Insert(++ii);
					error.Set(ii, 0, f.file);
					error.Set(ii, 1, f.lineno);
					int linecy;
					if(f.error_pos.GetCount()) {
						error.Set(ii, 2, FormatErrorLineEP(f.message, f.error_pos, linecy));
						error.SetDisplay(ii, 2, Single<ElepDisplay>());
					}
					else
						error.Set(ii, 2, FormatErrorLine(f.message, linecy));
					error.Set(ii, "INFO", n[i]);
					error.Set(ii, "NOTES", "0");
					error.SetLineCy(ii, linecy);
				}
			}
		}
		GoToError(error, true);
	}
}

void Ide::ShowError()
{
	if(error.IsCursor())
		GoToError(error, true);
}

Size Ide::FoundDisplay::DrawHl(Draw& w, const char *s, const Rect& r, Color ink, Color paper, dword style) const
{
	Vector<String> h = Split(s + 1, '\1', false);
	if(h.GetCount() < 4)
		return Size(1, 1);
	One<EditorSyntax> es = EditorSyntax::Create(h[0]);
	es->IgnoreErrors();
	String txt = h[3];
	if(txt.GetCount() > 500)
		txt.Trim(500);
	WString ln = txt.ToWString();
	Vector<LineEdit::Highlight> hln;
	hln.SetCount(ln.GetCount() + 1);
	for(int i = 0; i < ln.GetCount(); i++) {
		LineEdit::Highlight& h = hln[i];
		h.paper = paper;
		h.ink = SColorText();
		h.chr = ln[i];
		h.font = StdFont();
	}
	HighlightOutput hl(hln);
	es->Highlight(ln.Begin(), ln.End(), hl, NULL, 0, 0);
	int fcy = GetStdFontCy();
	int y = r.top + (r.GetHeight() - fcy) / 2;
	w.DrawRect(r, paper);
	int sl = Utf32Len(txt, atoi(h[1]));
	int sh = Utf32Len(~txt + sl, atoi(h[2])) + sl;
	int x;
	for(int text = 0; text < 2; text++) { // first pass draws background
		x = r.left;
		for(int i = 0; i < hln.GetCount(); i++) {
			Font fnt = StdFont();
			int a = fnt.GetAscent();
			LineEdit::Highlight& h = hln[i];
			fnt.Bold(h.font.IsBold());
			fnt.Italic(h.font.IsItalic());
			fnt.Underline(h.font.IsUnderline());
			a -= fnt.GetAscent();
			int cw = fnt[h.chr];
			if(h.chr == '\t')
				cw = 4 * fnt[' '];
			Color hpaper = HighlightSetup::GetHlStyle(HighlightSetup::PAPER_SELWORD).color;
			Color hink = h.ink;
			if(IsDarkMismatch()) {
				hpaper = paper;
				hink = ink;
			}
			if(i >= sl && i < sh && !(style & (CURSOR|SELECT|READONLY)) && !text)
				w.DrawRect(x, y, cw, fcy, hpaper);
			if(h.chr != '\t' && text)
				w.DrawText(x, y + a, &h.chr, fnt, hink, 1);
			x += cw;
		}
	}
	return Size(x, fcy);
}

Size Ide::FoundDisplay::GetStdSize(const Value& q) const
{
	String s = q;
	if(*s == '\1') {
		NilDraw nil;
		return DrawHl(nil, s, RectC(0, 0, INT_MAX, INT_MAX), Black(), White(), 0);
	}
	return StdDisplay().GetStdSize(q);
}

void Ide::FoundDisplay::Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
{
	String s = q;
	if(*s == '\1')
		DrawHl(w, s, r, ink, paper, style);
	else
		StdDisplay().Paint(w, r, q, ink, paper, style);
}

void Ide::FoundFileDisplay::Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const
{
	w.DrawRect(r, paper);
	DrawFileName(w, r, q, ink);
}

Size Ide::FoundFileDisplay::GetStdSize(const Value& q) const
{
	return GetDrawFileNameSize(q);
}
