text: #include "ide.h"
  
  struct FileLine : Moveable<FileLine> {
  	String file;
  	int    line;
  };
  
  struct LngEntry : Moveable<LngEntry, DeepCopyOption<LngEntry> > {
  	bool                   added;
  	VectorMap<int, String> text;
  	Vector<FileLine>       fileline;
  
  	LngEntry(const LngEntry& e, int) { added = e.added; text <<= e.text; fileline <<= e.fileline; }
  	LngEntry() {}
  
  	void AddFileLine(CParser& p) {
  		FileLine& fl = fileline.Add();
  		fl.file = p.GetFileName();
  		fl.line = p.GetLine();
  	}
  };
  
  struct TFile : Moveable<TFile> {
  	bool                        dirty;
  	bool                        java;
  	String                      package, file;
  	VectorMap<String, LngEntry> map;
  	Vector<int>                 ls;
  
  	void MakeLS() {
  		Index<int> lngset;
  		lngset.Add(LNG_enUS);
  		for(int i = 0; i < map.GetCount(); i++)
  			for(int j = 0; j < map[i].text.GetCount(); j++)
  				lngset.FindAdd(map[i].text.GetKey(j));
  		ls = lngset.PickKeys();
  		Sort(ls);
  	}
  
  	rval_default(TFile);
  	TFile() { dirty = false; }
  };
  
  void t_error(CParser& p)
  {
  	PutConsole(p.GetFileName() + Format("(%d): t_/tt_ works only with plain text literals", p.GetLine()));
  }
  
  void LngParseCFile(const String& fn, VectorMap<String, LngEntry>& lng)
  {
  	String data = LoadFile(fn);
  	CParser p(data, fn);
  	try {
  		while(!p.IsEof()) {
  			if((p.Id("t_") || p.Id("tt_")) && p.Char('('))
  				if(p.IsString()) {
  					String tid = p.ReadString();
  					LngEntry& le = lng.GetAdd(tid);
  					le.text.GetAdd(LNG_enUS) = GetENUS(tid);
  					le.AddFileLine(p);
  					le.added = true;
  					if(!p.Char(')'))
  						t_error(p);
  				}
  				else
  					t_error(p);
  			else
  				p.SkipTerm();
  		}
  	}
  	catch(CParser::Error e) {
  		PutConsole(e);
  		ShowConsole();
  	}
  }
  
  const Index<int>& LngIndex()
  {
  	static Index<int> l;
  	if(l.GetCount() == 0) {
  		const int *x = GetAllLanguages();
  		while(*x)
  			l.Add(*x++);
  	}
  	return l;
  }
  
  bool LngParseTFile(const String& fn, VectorMap<String, LngEntry>& lng)
  {
  	String data = LoadFile(fn);
  	CParser p(data, fn);
  	try {
  		if(p.Char('#'))
  			while(!p.IsEof()) {
  				if(p.IsChar2('T', '_'))
  					break;
  				else
  					p.SkipTerm();
  			}
  		String id;
  		while(!p.IsEof()) {
  			if(p.Id("T_")) {
  				p.PassChar('(');
  				id = Null;
  				do
  					id.Cat(p.ReadString());
  				while(p.Char('+'));
  				p.PassChar(')');
  				lng.GetAdd(id).added = false;
  			}
  			else {
  				if(IsNull(id))
  					p.ThrowError("missing T_");
  				String lngs = p.ReadId();
  				p.PassChar('(');
  				if(lngs.GetLength() == 4) {
  					int lang = LNG_(ToUpper(lngs[0]), ToUpper(lngs[1]), ToUpper(lngs[2]), ToUpper(lngs[3]));
  					if(LngIndex().Find(lang) >= 0) {
  						String lt;
  						do
  							lt.Cat(p.ReadString());
  						while(p.Char('+'));
  						lng.GetAdd(id).text.GetAdd(lang) = lt;
  						p.PassChar(')');
  						continue;
  					}
  				}
  				p.ThrowError("invalid language");
  			}
  		}
  	}
  	catch(CParser::Error e) {
  		PutConsole(e);
  		ShowConsole();
  		return false;
  	}
  	return true;
  }
  
  String CreateTFile(const VectorMap<String, LngEntry>& map, const Vector<int>& lngset, bool rep, bool obsolete, bool java)
  {
  	const char *linepfx = (java ? "   + " : "     ");
  	int ascflags = (java ? 0 : ASCSTRING_OCTALHI) | ASCSTRING_SMART;
  
  	String out;
  	String cfile;
  	out << "#ifdef _MSC_VER\r\n#pragma setlocale(\"C\")\r\n#endif";
  
  	for(int i = 0; i < map.GetCount(); i++) {
  		if(i) out << "\r\n";
  		const LngEntry& e = map[i];
  		String nc;
  		if(e.fileline.GetCount())
  			nc = e.fileline[0].file;
  		if(nc != cfile) {
  			cfile = nc;
  			if(!IsNull(cfile) && !rep)
  				out << "\r\n// " << GetFileName(cfile) << "\r\n\r\n";
  			if(IsNull(cfile) && obsolete)
  				out << "\r\n// Obsolete\r\n\r\n";
  		}
  		if(!IsNull(cfile) || rep || obsolete) {
  			String id = map.GetKey(i);
  			out << "T_(" << AsCString(id, 70, linepfx, ascflags) << ")\r\n";
  			for(int j = 0; j < lngset.GetCount(); j++) {
  				int lang = lngset[j];
  				if(rep || lang != LNG_enUS) {
  					int q = e.text.Find(lang);
  					if(!rep || q >= 0 && !IsNull(e.text[q])) {
  						int c = (lang >> 15) & 31;
  						if(c) {
  							out.Cat(c + 'a' - 1);
  							c = (lang >> 10) & 31;
  							if(c) {
  								out.Cat(c + 'a' - 1);
  								c = (lang >> 5) & 31;
  								if(c) {
  									out.Cat(c + 'A' - 1);
  									c = lang & 31;
  									if(c) out.Cat(c + 'A' - 1);
  								}
  							}
  						}
  						out << '(' << AsCString(q >= 0 ? e.text[q] : String(), 70,
  							linepfx, ascflags) << ")\r\n";
  					}
  				}
  			}
  		}
  	}
  	return out;
  }
  
  struct LangDlg : WithLangLayout<TopWindow> {
  	void Serialize(Stream& s);
  
  	Vector<TFile>& tfile;
  
  	void AddLang();
  	void RemoveLang();
  
  	void AddLangAll();
  	void RemoveLangAll();
  
  	void EnterFile();
  	void EnterText();
  	void ToggleWork();
  
  	void LangMenu(Bar& bar);
  
  	bool ShouldWrite(int i)        { return file.Get(i, 1); }
  
  	typedef LangDlg CLASSNAME;
  
  	LangDlg(Vector<TFile>& map);
  };
  
  void LangDlg::Serialize(Stream& s)
  {
  	SerializePlacement(s);
  	String f = file.GetKey();
  	int l = lang.GetKey();
  	String t = text.GetKey();
  	s % f % l % t;
  	file.FindSetCursor(f);
  	lang.FindSetCursor(l);
  	text.FindSetCursor(t);
  }
  
  void LangDlg::AddLang()
  {
  	if(!file.IsCursor())
  		return;
  	WithAddLangLayout<TopWindow> dlg;
  	CtrlLayoutOKCancel(dlg, "Add language");
  	dlg.lang <<= dlg.Breaker(999);
  	TFile& tf = tfile[file.GetCursor()];
  	Vector<int>& ls = tf.ls;
  	for(;;) {
  		int l = ~dlg.lang;
  		dlg.ok.Enable((l & 31) && FindIndex(ls, l) < 0);
  		switch(dlg.Run()) {
  		case IDOK:
  			l = ~dlg.lang;
  			if(FindIndex(ls, l) < 0) {
  				ls.Add(l);
  				tf.dirty = true;
  				Sort(ls);
  				EnterFile();
  				lang.FindSetCursor(l);
  				return;
  			}
  			break;
  		case IDCANCEL:
  			return;
  		}
  	}
  }
  
  void LangDlg::AddLangAll()
  {
  	WithAddLangLayout<TopWindow> dlg;
  	CtrlLayoutOKCancel(dlg, "Add to all");
  	if(dlg.Run() != IDOK)
  		return;
  	int l = ~dlg.lang;
  	for(int i = 0; i < tfile.GetCount(); i++) {
  		TFile& tf = tfile[i];
  		Vector<int>& ls = tf.ls;
  		if(FindIndex(ls, l) < 0) {
  			ls.Add(l);
  			tf.dirty = true;
  			Sort(ls);
  		}
  	}
  	EnterFile();
  	lang.FindSetCursor(l);
  }
  
  void LangDlg::RemoveLang()
  {
  	if(file.IsCursor() && lang.IsCursor() && (int)lang.GetKey() != LNG_enUS
  	   && PromptOKCancel("Remove selected language version?")) {
  		TFile& tf = tfile[file.GetCursor()];
  		tf.ls.Remove(lang.GetCursor());
  		tf.dirty = true;
  		EnterFile();
  	}
  }
  
  void LangDlg::RemoveLangAll()
  {
  	WithAddLangLayout<TopWindow> dlg;
  	CtrlLayoutOKCancel(dlg, "Remove from all");
  	if(file.IsCursor() && lang.IsCursor())
  		dlg.lang <<= tfile[file.GetCursor()].ls[lang.GetCursor()];
  	if(dlg.Run() != IDOK)
  		return;
  	int l = ~dlg.lang;
  	for(int i = 0; i < tfile.GetCount(); i++) {
  		TFile& tf = tfile[i];
  		Vector<int>& ls = tf.ls;
  		int q = FindIndex(ls, l);
  		if(q >= 0) {
  			ls.Remove(q);
  			tf.dirty = true;
  			Sort(ls);
  		}
  	}
  	EnterFile();
  	lang.FindSetCursor(l);
  }
  
  void LangDlg::LangMenu(Bar& bar)
  {
  	bar.Add(file.IsCursor(), "Add..", THISBACK(AddLang));
  	bar.Add(lang.IsCursor(), "Remove", THISBACK(RemoveLang));
  	bar.Separator();
  	bar.Add(file.IsCursor(), "Add to all..", THISBACK(AddLangAll));
  	bar.Add(lang.IsCursor(), "Remove from all..", THISBACK(RemoveLangAll));
  }
  
  struct FontAndColorDisplay : Display {
  	Font  font;
  	Color color;
  
  	void Paint(Draw& w, const Rect& r, const Value& q,
  						Color ink, Color paper, dword s) const {
  		w.DrawRect(r, paper);
  		String vt = q;
  		const char *txt = GetENUS(vt);
  		int x = r.left;
  		if(txt != ~vt) {
  			String id(~vt, txt - 1);
  			w.DrawText(x, r.top, id, font, txt[-1] == '\a' ? LtRed : Magenta);
  			x += GetTextSize(id, font).cx + 6;
  		}
  		w.DrawText(x, r.top, AsCString(txt), font, ink);
  	}
  
  	FontAndColorDisplay(Font f, Color c) : font(f), color(c) {}
  };
  
  void LangDlg::EnterFile()
  {
  	TFile& tf = tfile[file.GetCursor()];
  	Sort(tf.ls);
  
  	lang.Clear();
  	for(int i = 0; i < tf.ls.GetCount(); i++)
  		lang.Add(tf.ls[i], LNGAsText(tf.ls[i]));
  	lang.GoBegin();
  
  	static FontAndColorDisplay normal(Arial(11)(), Null);
  	static FontAndColorDisplay added(Arial(11)().Bold(), Null);
  	static FontAndColorDisplay obsolete(Arial(11)().Italic(), Null);
  
  	text.Clear();
  	for(int i = 0; i < tf.map.GetCount(); i++) {
  		text.Add(tf.map.GetKey(i));
  		LngEntry& e = tf.map[i];
  		Display *d;
  		if(e.fileline.GetCount() == 0)
  			d = &obsolete;
  		else
  		if(e.added)
  			d = &added;
  		else
  			d = &normal;
  		text.SetDisplay(text.GetCount() - 1, 0, *d);
  	}
  	text.GoBegin();
  }
  
  void LangDlg::EnterText()
  {
  	source.Clear();
  	VectorMap<String, LngEntry>& map = tfile[file.GetCursor()].map;
  	int q = text.GetCursor();
  	if(q < 0 || q >= map.GetCount())
  		return;
  	LngEntry& e = map[q];
  	for(int i = 0; i < e.fileline.GetCount(); i++) {
  		FileLine& f = e.fileline[i];
  		source.Add(f.file, f.line, GetFileName(f.file) + " (" + AsString(f.line) + ")");
  	}
  }
  
  void LangDlg::ToggleWork()
  {
  	if(file.GetCount()) {
  		bool q = !(int)file.Get(0, 1);
  		for(int i = 0; i < file.GetCount(); i++)
  			file.Set(i, 1, q);
  	}
  }
  
  LangDlg::LangDlg(Vector<TFile>& tfile)
  :	tfile(tfile)
  {
  	CtrlLayoutOKCancel(*this, "Translation files");
  	file.AddColumn("File");
  	HeaderCtrl::Column& m = file.AddColumn().Ctrls<Option>().HeaderTab();
  	m.SetImage(IdeImg::work());
  	m.WhenAction = THISBACK(ToggleWork);
  	file.WhenEnterRow = THISBACK(EnterFile);
  	file.ColumnWidths("144 27");
  
  	lang.AddIndex();
  	lang.AddColumn("Versions");
  	lang.WhenBar = THISBACK(LangMenu);
  
  	text.AddColumn("Text");
  	text.WhenEnterRow = THISBACK(EnterText);
  
  	source.AddIndex();
  	source.AddIndex();
  	source.AddColumn("Source");
  	source.WhenLeftDouble = Breaker(IDYES);
  
  	for(int i = 0; i < tfile.GetCount(); i++)
  		file.Add(tfile[i].package + '/' + tfile[i].file, 1);
  	file.GoBegin();
  
  	Icon(IdeImg::Language());
  	Sizeable().Zoomable();
  }
  
  class ExportTrDlg : public WithExportTrLayout<TopWindow> {
  	SelectDirButton of;
  	typedef ExportTrDlg CLASSNAME;
  
  public:
  	ExportTrDlg();
  };
  
  ExportTrDlg::ExportTrDlg()
  {
  	CtrlLayoutOKCancel(*this, "Export .tr file");
  
  	DlCharsetD(charset);
  	of.Attach(folder);
  
  	charset <<= CHARSET_UTF8;
  	lang <<= LNG_ENGLISH;
  }
  
  String ExportTr(const Vector<TFile>& tfile, int& cs)
  {
  	ExportTrDlg dlg;
  	LoadFromGlobal(dlg, "tr-export");
  	int c = dlg.Run();
  	StoreToGlobal(dlg, "tr-export");
  	if(c != IDOK)
  		return Null;
  	int lng = ~dlg.lang;
  	cs = ~dlg.charset;
  	String fn = AppendFileName(~dlg.folder, LNGAsText(lng) + "." + CharsetName(cs) + ".tr");
  	FileOut out(fn);
  	if(!out) {
  		Exclamation("Unable to open the output file:&[* \1" + fn);
  		return Null;
  	}
  	out << "LANGUAGE " << AsCString(LNGAsText(SetLNGCharset(lng, cs))) << ";\r\n";
  	for(int i = 0; i < tfile.GetCount(); i++) {
  		const TFile& m = tfile[i];
  		out << "// " << m.package << "/" << m.file << "\r\n";
  		for(int j = 0; j < m.map.GetCount(); j++)
  			out << AsCString(m.map.GetKey(j), 70) << ",\r\n"
  			    << "\t" << AsCString(ToCharset(cs, m.map[j].text.Get(lng, ""), CHARSET_UTF8),
  			                         60, "\t", ASCSTRING_SMART)
  			    << ";\r\n\r\n";
  	}
  	return fn;
  }
  
  void Ide::SyncT(int kind)
  {
  	console.Clear();
  	String filepath = GetActiveFilePath();
  	SaveFile();
  
  	TFile repository;
  	LngParseTFile(ConfigFile("repository.t"), repository.map);
  
  	if(kind == 1) {
  		FileSel fs;
  		fs.ActiveDir(AnySourceFs().GetActiveDir());
  		fs.Type("Runtime translation file (*.tr)", "*.tr");
  		fs.AllFilesType();
  		LoadFromGlobal(fs, "fs-tr");
  		bool b = fs.ExecuteOpen("Import translation file");
  		StoreToGlobal(fs, "fs-tr");
  		if(!b) return;
  		String tr = LoadFile(~fs);
  		CParser p(tr);
  		try {
  			while(!p.IsEof()) {
  				p.PassId("LANGUAGE");
  				int lang = LNGFromText(p.ReadString());
  				if(!lang)
  					p.ThrowError("Invalid language");
  				p.PassChar(';');
  				byte cs = GetLNGCharset(lang);
  				lang &= LNGC_(255, 255, 255, 255, 0);
  				while(p.IsString()) {
  					String id = p.ReadString();
  					p.PassChar(',');
  					repository.map.GetAdd(id).text.GetAdd(lang) = ToCharset(CHARSET_UTF8, p.ReadString(), cs);
  					p.PassChar(';');
  				}
  			}
  		}
  		catch(CParser::Error& e) {
  			PutConsole(e);
  			Exclamation("Failed: \1" + e);
  			return;
  		}
  	}
  
  	Vector<TFile> tfile;
  	Vector<int> mainsT;
  
  	Progress pi;
  	const Workspace& wspc = IdeWorkspace();
  	pi.SetTotal(wspc.GetCount());
  	for(int iPackage = 0; iPackage < wspc.GetCount(); iPackage++) {
  		const Package& pk = wspc.GetPackage(iPackage);
  		String n = wspc[iPackage];
  		pi.SetText("Scanning " + n);
  		if(pi.StepCanceled()) return;
  		VectorMap<String, LngEntry> pmap;
  		for(int iFile = 0; iFile < pk.file.GetCount(); iFile++) {
  			String file = SourcePath(n, pk.file[iFile]);
  			LngParseCFile(SourcePath(GetActivePackage(), file), pmap);
  		}
  		bool localT = false;
  		for(int iFile = 0; iFile < pk.file.GetCount(); iFile++) {
  			String file = SourcePath(n, pk.file[iFile]);
  			String ext = GetFileExt(file);
  			if(ext == ".t" || ext == ".jt") {
  				VectorMap<String, LngEntry> tmap(pmap, 0);
  				if(LngParseTFile(file, tmap)) {
  					TFile& tf = tfile.Add();
  					tf.java = (ext == ".jt");
  					tf.package = n;
  					tf.file = pk.file[iFile];
  					tf.map = pick(tmap);
  					tf.MakeLS();
  					// mark that we've found a local translation file
  					localT = true;
  					
  					// store index of main package translation(s) file(s)
  					if(iPackage == 0)
  						mainsT.Add(tfile.GetCount() - 1);
  				}
  			}
  		}
  		// if no local translation file(s), we append translation to
  		// main package one(s), if any
  		if(!localT && iPackage > 0)
  		{
  			for(int i = 0; i < mainsT.GetCount(); i++)
  			{
  				VectorMap<String, LngEntry> &map = tfile[mainsT[i]].map;
  				for(int iEntry = 0; iEntry < pmap.GetCount(); iEntry++)
  				{
  					int idx = map.Find(pmap.GetKey(iEntry));
  					if(idx < 0)
  					{
  						map.Add(pmap.GetKey(iEntry), pmap[iEntry]);
  						idx = map.GetCount() - 1;
  					}
  					LngEntry &entry = map[idx];
  					if(!entry.fileline.GetCount())
  					{
  						LngEntry &pEntry = pmap[iEntry];
  						for(int iLc = 0; iLc < pEntry.fileline.GetCount(); iLc++)
  						{
  							FileLine &fl = entry.fileline.Add();
  							String file = GetFileName(pEntry.fileline[iLc].file);
  							file = "PACKAGE '" + GetFileName(n) + "' FILE '" + file + "'";
  							fl.file = file;
  							fl.line = pEntry.fileline[iLc].line;
  						}
  					}
  				}
  			}
  		}
  	}
  
  	if(kind == 2) {
  		for(int i = 0; i < tfile.GetCount(); i++) {
  			TFile& tf = tfile[i];
  			for(int ii = 0; ii < tf.map.GetCount(); ii++) {
  				LngEntry& tle = tf.map[ii];
  				LngEntry& rle = repository.map.GetAdd(tf.map.GetKey(ii));
  				for(int l = 0; l < tle.text.GetCount(); l++)
  					if(!IsNull(tle.text[l]))
  						rle.text.GetAdd(tle.text.GetKey(l)) = tle.text[l];
  			}
  		}
  	
  		int cs;
  		String fn = ExportTr(tfile, cs);
  		if(!IsNull(fn)) {
  			EditFile(fn);
  			int c = filelist.GetCursor();
  			FlushFile();
  			ActiveFile().charset = (byte)cs;
  			SavePackage();
  			PackageCursor();
  			if(c >= 0)
  				filelist.SetCursor(c);
  			editor.ClearUndo();
  		}
  		return;
  	}
  
  	LangDlg dlg(tfile);
  	LoadFromWorkspace(dlg, "Tdlg");
  	switch(dlg.Execute()) {
  	case IDCANCEL:
  		return;
  	case IDYES:
  		if(dlg.source.IsCursor()) {
  			EditFile(dlg.source.Get(0));
  			editor.SetCursor(editor.GetPos(editor.GetLineNo((int)dlg.source.Get(1) - 1)));
  			editor.CenterCursor();
  			editor.SetFocus();
  			StoreToWorkspace(dlg, "Tdlg");
  			return;
  		}
  	}
  	StoreToWorkspace(dlg, "Tdlg");
  
  	FlushFile();
  
  	for(int i = 0; i < tfile.GetCount(); i++) {
  		TFile& tf = tfile[i];
  		for(int ii = 0; ii < tf.map.GetCount(); ii++) {
  			int q = repository.map.Find(tf.map.GetKey(ii));
  			if(q >= 0) {
  				LngEntry& tle = tf.map[ii];
  				LngEntry& rle = repository.map[q];
  				for(int l = 0; l < tf.ls.GetCount(); l++) {
  					q = rle.text.Find(tf.ls[l]);
  					if(q >= 0)
  						tle.text.GetAdd(tf.ls[l]) = rle.text[q];
  				}
  			}
  		}
  	}
  
  	pi.SetTotal(tfile.GetCount());
  	for(int i = 0; i < tfile.GetCount(); i++) {
  		TFile& tf = tfile[i];
  		String td = CreateTFile(tf.map, tf.ls, false, !dlg.remove, tf.java);
  		String fp = SourcePath(tf.package, tf.file);
  		pi.SetText("Storing " + tf.package + '/' + tf.file);
  		if(pi.StepCanceled())
  			break;
  		if(dlg.ShouldWrite(i) && LoadFile(fp) != td)
  			::SaveFile(fp, td);
  	}
  
  	repository.MakeLS();
  	::SaveFile(ConfigFile("repository.t"), CreateTFile(repository.map, repository.ls, true, false, false));
  	FileCursor();
  }
  
  void Ide::ConvertST()
  {
  	if(!PromptYesNo("Convert project to t_ translations?"))
  		return;
  
  	FlushFile();
  
  	TFile repository;
  	LngParseTFile(ConfigFile("repository.t"), repository.map);
  
  	Progress pi;
  	const Workspace& wspc = IdeWorkspace();
  	pi.SetTotal(wspc.GetCount());
  	for(int i = 0; i < wspc.GetCount(); i++) {
  		const Package& pk = wspc.GetPackage(i);
  		String n = wspc[i];
  		pi.SetText("Converting " + n);
  		if(pi.StepCanceled()) return;
  		VectorMap<String, LngEntry> pmap;
  		for(int i = 0; i < pk.file.GetCount(); i++) {
  			String file = SourcePath(n, pk.file[i]);
  			String data = LoadFile(file);
  			CParser p(data);
  			String r;
  			const char *b = data;
  			while(!p.IsEof()) {
  				p.Spaces();
  				const char *w = p.GetPtr();
  				if(p.Id("s_") && p.Char('(') && p.IsId()) {
  					String id = p.ReadId();
  					int q = repository.map.Find(id);
  					if(q >= 0) {
  						LngEntry& e = repository.map[q];
  						q = e.text.Find(LNG_enUS);
  						if(q >= 0) {
  							r.Cat(b, w);
  							r.Cat("t_(");
  							r.Cat(AsCString(e.text[q]));
  							b = p.GetPtr();
  						}
  					}
  				}
  				else
  					p.SkipTerm();
  			}
  			r.Cat(b);
  			if(r != data)
  				::SaveFile(file, r);
  		}
  	}
  
  	FileCursor();
  }
  